diff --git a/data_files/temoa_schema.sql b/data_files/temoa_schema.sql index 58852650..2755cc17 100644 --- a/data_files/temoa_schema.sql +++ b/data_files/temoa_schema.sql @@ -77,6 +77,12 @@ CREATE TABLE IF NOT EXISTS "tech_groups" ( FOREIGN KEY("tech") REFERENCES "technologies"("tech"), FOREIGN KEY("group_name") REFERENCES "groups"("group_name") ); +CREATE TABLE "tech_retirement" ( + "tech" text, + "notes" TEXT, + PRIMARY KEY("tech"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech") +); CREATE TABLE "sector_labels" ( "sector" text, PRIMARY KEY("sector") @@ -175,8 +181,21 @@ CREATE TABLE "RampUp" ( PRIMARY KEY("regions", "tech"), FOREIGN KEY("tech") REFERENCES "technologies"("tech") ); - CREATE TABLE "Output_V_Capacity" ( + "regions" text, + "scenario" text, + "sector" text, + "t_periods" integer, + "tech" text, + "vintage" integer, + "capacity" real, + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") +); +CREATE TABLE "Output_V_NewCapacity" ( "regions" text, "scenario" text, "sector" text, @@ -188,6 +207,20 @@ CREATE TABLE "Output_V_Capacity" ( FOREIGN KEY("tech") REFERENCES "technologies"("tech"), FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") ); +CREATE TABLE "Output_V_RetiredCapacity" ( + "regions" text, + "scenario" text, + "sector" text, + "t_periods" integer, + "tech" text, + "vintage" integer, + "capacity" real, + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") +); CREATE TABLE "Output_VFlow_Out" ( "regions" text, "scenario" text, diff --git a/data_files/temoa_schema.sqlite b/data_files/temoa_schema.sqlite index d8f73b6c..2bd24f46 100644 Binary files a/data_files/temoa_schema.sqlite and b/data_files/temoa_schema.sqlite differ diff --git a/data_files/temoa_test_system.sql b/data_files/temoa_test_system.sql index 880aafb5..35767f2a 100644 --- a/data_files/temoa_test_system.sql +++ b/data_files/temoa_test_system.sql @@ -97,6 +97,12 @@ CREATE TABLE "tech_annual" ( PRIMARY KEY("tech"), FOREIGN KEY("tech") REFERENCES "technologies"("tech") ); +CREATE TABLE "tech_retirement" ( + "tech" text, + "notes" TEXT, + PRIMARY KEY("tech"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech") +); CREATE TABLE "sector_labels" ( "sector" text, PRIMARY KEY("sector") @@ -231,14 +237,42 @@ CREATE TABLE "Output_V_Capacity" ( "regions" text, "scenario" text, "sector" text, + "t_periods" integer, "tech" text, "vintage" integer, "capacity" real, - PRIMARY KEY("regions","scenario","tech","vintage"), + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") +); +CREATE TABLE "Output_V_NewCapacity" ( + "regions" text, + "scenario" text, + "sector" text, + "tech" text, + "vintage" integer, + "capacity" real, + PRIMARY KEY("regions","scenario","tech","vintage"), FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") ); +CREATE TABLE "Output_V_RetiredCapacity" ( + "regions" text, + "scenario" text, + "sector" text, + "t_periods" integer, + "tech" text, + "vintage" integer, + "capacity" real, + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") +); CREATE TABLE "Output_VFlow_Out" ( "regions" text, "scenario" text, @@ -358,7 +392,7 @@ CREATE TABLE "Output_CapacityByPeriodAndTech" ( ); CREATE TABLE "MyopicBaseyear" ( "year" real - "notes" text + "notes" text ); CREATE TABLE "MinGenGroupWeight" ( "regions" text, @@ -1034,7 +1068,7 @@ CREATE TABLE "MaxResource" ( CREATE TABLE "LinkedTechs" ( "primary_region" text, "primary_tech" text, - "emis_comm" text, + "emis_comm" text, "linked_tech" text, "tech_linked_notes" text, FOREIGN KEY("primary_tech") REFERENCES "technologies"("tech"), diff --git a/data_files/temoa_test_system.sqlite b/data_files/temoa_test_system.sqlite index 5a7bb560..c83a410c 100644 Binary files a/data_files/temoa_test_system.sqlite and b/data_files/temoa_test_system.sqlite differ diff --git a/data_files/temoa_utopia.sql b/data_files/temoa_utopia.sql index 48debe3c..43ecbffc 100644 --- a/data_files/temoa_utopia.sql +++ b/data_files/temoa_utopia.sql @@ -98,6 +98,12 @@ CREATE TABLE "tech_annual" ( PRIMARY KEY("tech"), FOREIGN KEY("tech") REFERENCES "technologies"("tech") ); +CREATE TABLE "tech_retirement" ( + "tech" text, + "notes" TEXT, + PRIMARY KEY("tech"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech") +); CREATE TABLE "sector_labels" ( "sector" text, PRIMARY KEY("sector") @@ -210,13 +216,41 @@ CREATE TABLE "Output_V_Capacity" ( "regions" text, "scenario" text, "sector" text, + "t_periods" integer, "tech" text, "vintage" integer, "capacity" real, + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") +); +CREATE TABLE "Output_V_NewCapacity" ( + "regions" text, + "scenario" text, + "sector" text, + "tech" text, + "vintage" integer, + "capacity" real, + PRIMARY KEY("regions","scenario","tech","vintage"), + FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") +); +CREATE TABLE "Output_V_RetiredCapacity" ( + "regions" text, + "scenario" text, + "sector" text, + "t_periods" integer, + "tech" text, + "vintage" integer, + "capacity" real, + FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - PRIMARY KEY("regions","scenario","tech","vintage") + FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + PRIMARY KEY("regions","scenario","t_periods","tech","vintage") ); CREATE TABLE "Output_VFlow_Out" ( "regions" text, diff --git a/data_files/temoa_utopia.sqlite b/data_files/temoa_utopia.sqlite index d8d95515..cac159f4 100644 Binary files a/data_files/temoa_utopia.sqlite and b/data_files/temoa_utopia.sqlite differ diff --git a/temoa_model/pformat_results.py b/temoa_model/pformat_results.py index 81114c22..ce60b7c2 100644 --- a/temoa_model/pformat_results.py +++ b/temoa_model/pformat_results.py @@ -235,16 +235,40 @@ def collect_result_data( cgroup, clist, epsilon): # Extract optimal decision variable values related to capacity: if hasattr(options, 'file_location') and os.path.join('temoa_model', 'config_sample_myopic') not in options.file_location: - for r, t, v in m.V_Capacity: - val = value( m.V_Capacity[r, t, v] ) + for r, p, t, v in m.V_Capacity: + val = value( m.V_Capacity[r, p, t, v] ) if abs(val) < capacity_epsilon: continue - svars['V_Capacity'][r, t, v] = val + svars['V_Capacity'][r, p, t, v] = val else: - for r, t, v in m.V_Capacity: + for r, p, t, v in m.V_Capacity: + if p in m.time_optimize: + val = value( m.V_Capacity[r, p, t, v] ) + if abs(val) < capacity_epsilon: continue + svars['V_Capacity'][r, p, t, v] = val + + if hasattr(options, 'file_location') and os.path.join('temoa_model', 'config_sample_myopic') not in options.file_location: + for r, t, v in m.V_NewCapacity: + val = value( m.V_NewCapacity[r, t, v] ) + if abs(val) < capacity_epsilon: continue + svars['V_NewCapacity'][r, t, v] = val + else: + for r, t, v in m.V_NewCapacity: if v in m.time_optimize: - val = value( m.V_Capacity[r, t, v] ) + val = value( m.V_NewCapacity[r, t, v] ) + if abs(val) < capacity_epsilon: continue + svars['V_NewCapacity'][r, t, v] = val + + if hasattr(options, 'file_location') and os.path.join('temoa_model', 'config_sample_myopic') not in options.file_location: + for r, p, t, v in m.V_RetiredCapacity: + val = value( m.V_RetiredCapacity[r, p, t, v] ) + if abs(val) < capacity_epsilon: continue + svars['V_RetiredCapacity'][r, p, t, v] = val + else: + for r, p, t, v in m.V_RetiredCapacity: + if p in m.time_optimize: + val = value( m.V_RetiredCapacity[r, p, t, v] ) if abs(val) < capacity_epsilon: continue - svars['V_Capacity'][r, t, v] = val + svars['V_RetiredCapacity'][r, p, t, v] = val for r, p, t in m.V_CapacityAvailableByPeriodAndTech: val = value( m.V_CapacityAvailableByPeriodAndTech[r, p, t] ) @@ -260,7 +284,7 @@ def collect_result_data( cgroup, clist, epsilon): for r, t, v in m.CostInvest.sparse_iterkeys(): # Returns only non-zero values - icost = value( m.V_Capacity[r, t, v] ) + icost = value( m.V_NewCapacity[r, t, v] ) if abs(icost) < epsilon: continue icost *= value( m.CostInvest[r, t, v] )*( ( @@ -281,7 +305,7 @@ def collect_result_data( cgroup, clist, epsilon): for r, p, t, v in m.CostFixed.sparse_iterkeys(): - fcost = value( m.V_Capacity[r, t, v] ) + fcost = value( m.V_Capacity[r, p, t, v] ) if abs(fcost) < epsilon: continue fcost *= value( m.CostFixed[r, p, t, v] ) @@ -332,11 +356,11 @@ def collect_result_data( cgroup, clist, epsilon): # assumption 3: Unlike other output tables in which Ri-Rj and Rj-Ri entries # are allowed in the region column, for the Output_Costs table the region #to the right of the hyphen sign gets the costs. - for i in m.RegionalExchangeCapacityConstraint_rrtv.iterkeys(): + for i in m.RegionalExchangeCapacityConstraint_rrptv.iterkeys(): reg_dir1 = i[0]+"-"+i[1] reg_dir2 = i[1]+"-"+i[0] - tech = i[2] - vintage = i[3] + tech = i[3] + vintage = i[4] key = (reg_dir1, tech, vintage) try: act_dir1 = value (sum(m.V_FlowOut[reg_dir1, p, s, d, S_i, tech, vintage, S_o] @@ -429,6 +453,8 @@ def make_var_list ( variables ): "V_FlowOut" : "Output_VFlow_Out", \ "V_Curtailment" : "Output_Curtailment", \ "V_Capacity" : "Output_V_Capacity", \ + "V_NewCapacity" : "Output_V_NewCapacity", \ + "V_RetiredCapacity" : "Output_V_RetiredCapacity", \ "V_CapacityAvailableByPeriodAndTech" : "Output_CapacityByPeriodAndTech", \ "V_EmissionActivityByPeriodAndProcess" : "Output_Emissions", \ "Objective" : "Output_Objective", \ diff --git a/temoa_model/temoa_config.py b/temoa_model/temoa_config.py index b4ca2529..1d40cb07 100644 --- a/temoa_model/temoa_config.py +++ b/temoa_model/temoa_config.py @@ -128,6 +128,7 @@ def query_table (t_properties, f): ['set', 'tech_groups', '', '', 2], ['set', 'tech_annual', '', '', 0], ['set', 'tech_variable', '', '', 0], + ['set', 'tech_retirement', '', '', 0], ['set', 'groups', '', '', 0], ['param','LinkedTechs', '', '', 3], ['param','SegFrac', '', '', 2], @@ -211,6 +212,8 @@ def query_table (t_properties, f): cur.execute("DELETE FROM Output_VFlow_In WHERE scenario="+"'"+str(options.scenario)+"'") cur.execute("DELETE FROM Output_VFlow_Out WHERE scenario="+"'"+str(options.scenario)+"'") cur.execute("DELETE FROM Output_V_Capacity WHERE scenario="+"'"+str(options.scenario)+"'") + cur.execute("DELETE FROM Output_V_NewCapacity WHERE scenario="+"'"+str(options.scenario)+"'") + cur.execute("DELETE FROM Output_V_RetiredCapacity WHERE scenario="+"'"+str(options.scenario)+"'") cur.execute("DELETE FROM Output_Curtailment WHERE scenario="+"'"+str(options.scenario)+"'") cur.execute("DELETE FROM Output_Duals WHERE scenario="+"'"+str(options.scenario)+"'") cur.execute("VACUUM") diff --git a/temoa_model/temoa_initialize.py b/temoa_model/temoa_initialize.py index 750d8e05..7a4ce2c9 100644 --- a/temoa_model/temoa_initialize.py +++ b/temoa_model/temoa_initialize.py @@ -940,6 +940,16 @@ def LifetimeLoanProcessIndices ( M ): def CapacityVariableIndices ( M ): return M.activeCapacity_rtv +def RetiredCapacityVariableIndices ( M ): + return set( + (r, p, t, v) + + for r,p,t in M.processVintages.keys() + if t in M.tech_retirement + for v in M.processVintages[ r, p, t ] + if p > v + ) + def CapacityAvailableVariableIndices ( M ): return M.activeCapacityAvailable_rpt @@ -1071,7 +1081,7 @@ def BaseloadDiurnalConstraintIndices ( M ): def RegionalExchangeCapacityConstraintIndices ( M ): indices = set( - (r_e, r_i, t, v) + (r_e, r_i, p, t, v) for r_e, p, i in M.exportRegions.keys() for r_i, t, v, o in M.exportRegions[r_e, p, i] diff --git a/temoa_model/temoa_model.py b/temoa_model/temoa_model.py index 9b7e48fd..ef847bfa 100755 --- a/temoa_model/temoa_model.py +++ b/temoa_model/temoa_model.py @@ -80,6 +80,7 @@ def temoa_create_model(name="Temoa"): M.tech_groups = Set(within=M.RegionalIndices * M.groups * M.tech_all) M.tech_annual = Set(within=M.tech_all) # Define techs with constant output M.tech_variable = Set(within=M.tech_all) # Define techs for use with TechInputSplitAverage constraint, where techs have variable annual output but the user wishes to constrain them annually + M.tech_retirement = Set(within=M.tech_all) # Define techs for which economic retirement is an option # Define commodity-related sets M.commodity_demand = Set() @@ -285,8 +286,14 @@ def temoa_create_model(name="Temoa"): # Derived decision variables - M.CapacityVar_rtv = Set(dimen=3, initialize=CapacityVariableIndices) - M.V_Capacity = Var(M.CapacityVar_rtv, domain=NonNegativeReals) + M.CapacityVar_rptv = Set(dimen=4, initialize=CostFixedIndices) + M.V_Capacity = Var(M.CapacityVar_rptv, domain=NonNegativeReals) + + M.NewCapacityVar_rtv = Set(dimen=3, initialize=CapacityVariableIndices) + M.V_NewCapacity = Var(M.NewCapacityVar_rtv, domain=NonNegativeReals, initialize=0) + + M.RetiredCapacityVar_rptv = Set(dimen=4, initialize=RetiredCapacityVariableIndices) + M.V_RetiredCapacity = Var(M.RetiredCapacityVar_rptv, domain=NonNegativeReals, initialize=0) M.CapacityAvailableVar_rpt = Set( dimen=3, initialize=CapacityAvailableVariableIndices @@ -322,11 +329,11 @@ def temoa_create_model(name="Temoa"): M.CapacityAvailableVar_rpt, rule=CapacityAvailableByPeriodAndTech_Constraint ) - M.ExistingCapacityConstraint_rtv = Set( - dimen=3, initialize=lambda M: M.ExistingCapacity.sparse_iterkeys() + M.RetiredCapacityConstraint = Constraint( + M.RetiredCapacityVar_rptv, rule=RetiredCapacity_Constraint ) - M.ExistingCapacityConstraint = Constraint( - M.ExistingCapacityConstraint_rtv, rule=ExistingCapacity_Constraint + M.AdjustedCapacityConstraint = Constraint( + M.CostFixed_rptv, rule=AdjustedCapacity_Constraint ) # Declare core model constraints that ensure proper system functioning @@ -370,11 +377,11 @@ def temoa_create_model(name="Temoa"): M.BaseloadDiurnalConstraint_rpsdtv, rule=BaseloadDiurnal_Constraint ) - M.RegionalExchangeCapacityConstraint_rrtv = Set( - dimen=4, initialize=RegionalExchangeCapacityConstraintIndices + M.RegionalExchangeCapacityConstraint_rrptv = Set( + dimen=5, initialize=RegionalExchangeCapacityConstraintIndices ) M.RegionalExchangeCapacityConstraint = Constraint( - M.RegionalExchangeCapacityConstraint_rrtv, rule=RegionalExchangeCapacity_Constraint) + M.RegionalExchangeCapacityConstraint_rrptv, rule=RegionalExchangeCapacity_Constraint) # This set works for all the storage-related constraints M.StorageConstraints_rpsdtv = Set(dimen=6, initialize=StorageVariableIndices) diff --git a/temoa_model/temoa_myopic.py b/temoa_model/temoa_myopic.py index afd4767c..70ed8a99 100644 --- a/temoa_model/temoa_myopic.py +++ b/temoa_model/temoa_myopic.py @@ -223,16 +223,18 @@ def myopic_db_generator_solver ( self ): cur_org.execute(query) # --------------------------------------------------------------- - # Add the buildups from the previous period to the ExistingCapacity + # Add the buildups and retirements from the previous period to the ExistingCapacity # table. The data is stored in the Output_V_Capacity of the con_org # --------------------------------------------------------------- if i!=(N-1): df_new_ExistingCapacity = pd.read_sql_query("SELECT regions, tech, vintage, capacity FROM Output_V_Capacity \ WHERE scenario="+"'"+str(self.options.scenario)+"' AND \ - vintage < "+str(time_periods[i-(N-1)][0])+";", con_org) + t_periods <= "+str(time_periods[i-(N-1)][0])+";", con_org) df_new_ExistingCapacity.columns = ['regions','tech','vintage','exist_cap'] - df_new_ExistingCapacity.to_sql('ExistingCapacity',con, if_exists='append', index=False) + # Need to remove technologies that have been age-based retired. + + df_new_ExistingCapacity.to_sql('ExistingCapacity',con, if_exists='replace', index=False) #Create a copy of the first time period vintages for the two current vintage #to prevent infeasibility (if it is not an 'existing' vintage in the @@ -260,14 +262,15 @@ def myopic_db_generator_solver ( self ): iterval+=1 if iterval>10: break - # Discard the results associated with time_periods[i-N][0] P time_periods[i-N][0] period in rolling horizon fashion. Otherwise, UNIQUE CONSTRAINT error is thrown. # Re Output_Costs, a delete is not needed because in pformat_results.py, future periods costs get added to what is already in the table cur_org.execute("DELETE FROM Output_CapacityByPeriodAndTech WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) cur_org.execute("DELETE FROM Output_Emissions WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) cur_org.execute("DELETE FROM Output_VFlow_In WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) cur_org.execute("DELETE FROM Output_VFlow_Out WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) - cur_org.execute("DELETE FROM Output_V_Capacity WHERE scenario="+"'"+str(self.options.scenario)+"' AND vintage>"+str(time_periods[i-N][0])) + cur_org.execute("DELETE FROM Output_V_Capacity WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) + cur_org.execute("DELETE FROM Output_V_NewCapacity WHERE scenario="+"'"+str(self.options.scenario)+"' AND vintage>"+str(time_periods[i-N][0])) + cur_org.execute("DELETE FROM Output_V_RetiredCapacity WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) cur_org.execute("DELETE FROM Output_Curtailment WHERE scenario="+"'"+str(self.options.scenario)+"' AND t_periods>"+str(time_periods[i-N][0])) con_org.commit() diff --git a/temoa_model/temoa_rules.py b/temoa_model/temoa_rules.py index 089b2e20..47ceb328 100644 --- a/temoa_model/temoa_rules.py +++ b/temoa_model/temoa_rules.py @@ -30,6 +30,26 @@ # and constraints below. # --------------------------------------------------------------- +def AdjustedCapacity_Constraint(M, r, p, t, v): + r""" +This constraint updates the capacity of a process by taking into account retirements. +For a given :code:`(r,p,t,v)` index, this constraint sets the capacity equal to +the amount installed in period :code:`v` and subtracts from it any and all retirements +that occured up until the period in question, :code:`p`. +""" + if t not in M.tech_retirement: + if v in M.time_exist: + return M.V_Capacity[r, p, t, v] == M.ExistingCapacity[r, t, v] + else: + return M.V_Capacity[r, p, t, v] == M.V_NewCapacity[r, t, v] + + else: + retired_cap = sum(M.V_RetiredCapacity[r, S_p, t, v] for S_p in M.time_optimize if S_p <= p and S_p > v) + if v in M.time_exist: + return M.V_Capacity[r, p, t, v] == M.ExistingCapacity[r, t, v] - retired_cap + else: + return M.V_Capacity[r, p, t, v] == M.V_NewCapacity[r, t, v] - retired_cap + def Capacity_Constraint(M, r, p, s, d, t, v): r""" This constraint ensures that the capacity of a given process is sufficient @@ -81,7 +101,7 @@ def Capacity_Constraint(M, r, p, s, d, t, v): return value(M.CapacityFactorProcess[r, s, d, t, v]) \ * value(M.CapacityToActivity[r, t]) * value(M.SegFrac[s, d]) \ * value(M.ProcessLifeFrac[r, p, t, v]) \ - * M.V_Capacity[r, t, v] == useful_activity + sum( \ + * M.V_Capacity[r, p, t, v] == useful_activity + sum( \ M.V_Curtailment[r, p, s, d, S_i, t, v, S_o] \ for S_i in M.processInputs[r, p, t, v] \ for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i]) @@ -90,7 +110,7 @@ def Capacity_Constraint(M, r, p, s, d, t, v): * value(M.CapacityToActivity[r, t]) \ * value(M.SegFrac[s, d]) \ * value(M.ProcessLifeFrac[r, p, t, v]) \ - * M.V_Capacity[r, t, v] >= useful_activity + * M.V_Capacity[r, p, t, v] >= useful_activity def CapacityAnnual_Constraint(M, r, p, t, v): @@ -130,7 +150,7 @@ def CapacityAnnual_Constraint(M, r, p, t, v): return CF \ * value(M.CapacityToActivity[r, t]) \ * value(M.ProcessLifeFrac[r, p, t, v]) \ - * M.V_Capacity[r, t, v] >= activity_rptv + * M.V_Capacity[r, p, t, v] >= activity_rptv def ActivityByTech_Constraint(M, t): @@ -194,36 +214,40 @@ def CapacityAvailableByPeriodAndTech_Constraint(M, r, p, t): .. math:: :label: CapacityAvailable - \textbf{CAPAVL}_{r, p, t} = \sum_{V} {PLF}_{r, p, t, v} \cdot \textbf{CAP}_{r, t, v} + \textbf{CAPAVL}_{r, p, t} = \sum_{v, p_i \leq p} {PLF}_{r, p, t, v} \cdot \left ( \textbf{CAP}_{r, t, v} - \textbf{CAPRET}_{r, p_i, t, v} \right ) \\ \forall p \in \text{P}^o, r \in R, t \in T """ cap_avail = sum( - value(M.ProcessLifeFrac[r, p, t, S_v]) * M.V_Capacity[r, t, S_v] + value(M.ProcessLifeFrac[r, p, t, S_v]) * M.V_Capacity[r, p, t, S_v] for S_v in M.processVintages[r, p, t] ) expr = M.V_CapacityAvailableByPeriodAndTech[r, p, t] == cap_avail return expr -def ExistingCapacity_Constraint(M, r, t, v): +def RetiredCapacity_Constraint(M, r, p, t, v): r""" -Temoa treats existing capacity installed prior to the beginning of the model's -optimization horizon as regular processes that require the same parameter -specification as do new vintage technologies, except for the :code:`CostInvest` -parameter. This constraint sets the capacity of processes for model periods -that exist prior to the optimization horizon to user-specified values. +Temoa allows for the economic retirement of technologies presented in the +:code:`tech_retirement` set. This constraint sets the upper limit of retired +capacity to the total installed capacity. +In the equation below, we set the upper bound of the retired capacity to the +previous period's total installed capacity (CAPAVL) .. math:: - :label: ExistingCapacity - - \textbf{CAP}_{r, t, v} = ECAP_{r, t, v} + :label: RetiredCapacity - \forall \{r, t, v\} \in \Theta_{\text{ExistingCapacity}} + \textbf{CAPRET}_{r, p, t, v} \leq \sum_{V} {PLF}_{r, p, t, v} \cdot \textbf{CAP}_{r, t, v} + \\ + \forall \{r, p, t, v\} \in \Theta_{\text{RetiredCapacity}} """ - expr = M.V_Capacity[r, t, v] == M.ExistingCapacity[r, t, v] + if p == M.time_optimize.first(): + cap_avail = M.ExistingCapacity[r, t, v] + else: + cap_avail = M.V_Capacity[r, M.time_optimize.prev(p), t, v] + expr = M.V_RetiredCapacity[r, p, t, v] <= cap_avail return expr # --------------------------------------------------------------- @@ -320,7 +344,7 @@ def PeriodCost_rule(M, p): loan_costs = sum( - M.V_Capacity[r, S_t, S_v] + M.V_NewCapacity[r, S_t, S_v] * ( value(M.CostInvest[r, S_t, S_v]) * value(M.LoanAnnualize[r, S_t, S_v]) @@ -343,7 +367,7 @@ def PeriodCost_rule(M, p): ) fixed_costs = sum( - M.V_Capacity[r, S_t, S_v] + M.V_Capacity[r, p, S_t, S_v] * ( value(M.CostFixed[r, p, S_t, S_v]) * ( @@ -825,7 +849,7 @@ def BaseloadDiurnal_Constraint(M, r, p, s, d, t, v): return expr -def RegionalExchangeCapacity_Constraint(M, r_e, r_i, t, v): +def RegionalExchangeCapacity_Constraint(M, r_e, r_i, p, t, v): r""" This constraint ensures that the process (t,v) connecting regions @@ -842,7 +866,7 @@ def RegionalExchangeCapacity_Constraint(M, r_e, r_i, t, v): \forall \{r_e, r_i, t, v\} \in \Theta_{\text{RegionalExchangeCapacity}} """ - expr = M.V_Capacity[r_e+"-"+r_i, t, v] == M.V_Capacity[r_i+"-"+r_e, t, v] + expr = M.V_Capacity[r_e+"-"+r_i, p, t, v] == M.V_Capacity[r_i+"-"+r_e, p, t, v] return expr @@ -982,7 +1006,7 @@ def StorageEnergyUpperBound_Constraint(M, r, p, s, d, t, v): """ energy_capacity = ( - M.V_Capacity[r, t, v] + M.V_Capacity[r, p, t, v] * M.CapacityToActivity[r, t] * (M.StorageDuration[r, t] / 8760) * M.SegFracPerSeason[s] * 365 @@ -1019,7 +1043,7 @@ def StorageChargeRate_Constraint(M, r, p, s, d, t, v): # Maximum energy charge in each time slice max_charge = ( - M.V_Capacity[r, t, v] + M.V_Capacity[r, p, t, v] * M.CapacityToActivity[r, t] * M.SegFrac[s, d] * value(M.ProcessLifeFrac[r, p, t, v]) @@ -1056,7 +1080,7 @@ def StorageDischargeRate_Constraint(M, r, p, s, d, t, v): # Maximum energy discharge in each time slice max_discharge = ( - M.V_Capacity[r, t, v] + M.V_Capacity[r, p, t, v] * M.CapacityToActivity[r, t] * M.SegFrac[s, d] * value(M.ProcessLifeFrac[r, p, t, v]) @@ -1101,7 +1125,7 @@ def StorageThroughput_Constraint(M, r, p, s, d, t, v): throughput = charge + discharge max_throughput = ( - M.V_Capacity[r, t, v] + M.V_Capacity[r, p, t, v] * M.CapacityToActivity[r, t] * M.SegFrac[s, d] * value(M.ProcessLifeFrac[r, p, t, v]) @@ -1138,11 +1162,12 @@ def StorageInit_Constraint( M, r, t, v ): s = M.time_season.first() energy_capacity = ( - M.V_Capacity[r, t, v] + M.V_Capacity[r, p, 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 * value(M.ProcessLifeFrac[r, v, t, v]) + for p in M.time_optimize if p >= v ) expr = M.V_StorageInit[r, t, v] == energy_capacity * M.StorageInitFrac[r, t, v] @@ -1215,7 +1240,7 @@ def RampUpDay_Constraint(M, r, p, s, d, t, v): activity_sd / value(M.SegFrac[s, d]) - activity_sd_prev / value(M.SegFrac[s, d_prev]) ) / value(M.CapacityToActivity[r,t]) - expr_right = M.V_Capacity[r, t, v] * value(M.RampUp[r, t]) + expr_right = M.V_Capacity[r, p, t, v] * value(M.RampUp[r, t]) expr = expr_left <= expr_right else: return Constraint.Skip @@ -1266,7 +1291,7 @@ def RampDownDay_Constraint(M, r, p, s, d, t, v): activity_sd / value(M.SegFrac[s, d]) - activity_sd_prev / value(M.SegFrac[s, d_prev]) ) / value(M.CapacityToActivity[r,t]) - expr_right = -(M.V_Capacity[r, t, v] * value(M.RampDown[r, t])) + expr_right = -(M.V_Capacity[r, p, t, v] * value(M.RampDown[r, t])) expr = expr_left >= expr_right else: return Constraint.Skip @@ -1320,7 +1345,7 @@ def RampUpSeason_Constraint(M, r, p, s, t, v): activity_sd_first / M.SegFrac[s, d_first] - activity_s_prev_d_last / M.SegFrac[s_prev, d_last] ) / value(M.CapacityToActivity[r,t]) - expr_right = M.V_Capacity[r, t, v] * value(M.RampUp[r, t]) + expr_right = M.V_Capacity[r, p, t, v] * value(M.RampUp[r, t]) expr = expr_left <= expr_right else: return Constraint.Skip @@ -1375,7 +1400,7 @@ def RampDownSeason_Constraint(M, r, p, s, t, v): activity_sd_first / value(M.SegFrac[s, d_first]) - activity_s_prev_d_last / value(M.SegFrac[s_prev, d_last]) ) / value(M.CapacityToActivity[r,t]) - expr_right = -(M.V_Capacity[r, t, v] * value(M.RampDown[r, t])) + expr_right = -(M.V_Capacity[r, p, t, v] * value(M.RampDown[r, t])) expr = expr_left >= expr_right else: return Constraint.Skip @@ -1489,7 +1514,7 @@ def ReserveMargin_Constraint(M, r, p, s, d): cap_avail = sum( value(M.CapacityCredit[r, p, t, v]) * M.ProcessLifeFrac[r, p, t, v] - * M.V_Capacity[r, t, v] + * M.V_Capacity[r, p, t, v] * value(M.CapacityToActivity[r, t]) * value(M.SegFrac[s, d]) for t in M.tech_reserve @@ -1521,7 +1546,7 @@ def ReserveMargin_Constraint(M, r, p, s, d): cap_avail += sum( value(M.CapacityCredit[r1r2, p, t, v]) * M.ProcessLifeFrac[r1r2, p, t, v] - * M.V_Capacity[r1r2, t, v] + * M.V_Capacity[r1r2, p, t, v] * value(M.CapacityToActivity[r1r2, t]) * value(M.SegFrac[s, d]) for t in M.tech_reserve @@ -1939,7 +1964,7 @@ def MaxNewCapacity_Constraint(M, r, p, t): \textbf{CAP}_{r, t, p} \le MAX_{r, p, t} """ max_cap = value(M.MaxNewCapacity[r, p, t]) - expr = M.V_Capacity[r, t, p] <= max_cap + expr = M.V_NewCapacity[r, t, p] <= max_cap return expr def MaxCapacity_Constraint(M, r, p, t): @@ -2024,7 +2049,7 @@ def MinNewCapacity_Constraint(M, r, p, t): \textbf{CAP}_{r, t, p} \ge MIN_{r, p, t} """ min_cap = value(M.MinNewCapacity[r, p, t]) - expr = M.V_Capacity[r, t, p] >= min_cap + expr = M.V_NewCapacity[r, t, p] >= min_cap return expr @@ -2066,7 +2091,7 @@ def MinNewCapacityGroup_Constraint(M, r, p, g): """ min_new_cap = value(M.MinNewCapacityGroup[r, p, g]) agg_new_cap = sum( - M.V_Capacity[r, t, p] + M.V_NewCapacity[r, t, p] for _r, _g, t in M.tech_groups if _r == r and _g == g and (r, p, t) in M.V_CapacityAvailableByPeriodAndTech.keys() ) expr = agg_new_cap >= min_new_cap @@ -2079,7 +2104,7 @@ def MaxNewCapacityGroup_Constraint(M, r, p, g): """ max_new_cap = value(M.MaxNewCapacityGroup[r, p, g]) agg_new_cap = sum( - M.V_Capacity[r, t, p] + M.V_NewCapacity[r, t, p] for _r, _g, t in M.tech_groups if _r == r and _g == g and (r, p, t) in M.V_CapacityAvailableByPeriodAndTech.keys() ) expr = max_new_cap >= agg_new_cap @@ -2257,11 +2282,11 @@ def MinNewCapacityShare_Constraint(M, r, p, t, g): that no less than 10% of new LDV purchases in a given year must be of a certain type. """ - capacity_t = M.V_Capacity[r, t, p] + capacity_t = M.V_NewCapacity[r, t, p] capacity_group = sum( - M.V_Capacity[r, S_t, p] + M.V_NewCapacity[r, S_t, p] for (S_r, S_g, S_t) in M.tech_groups.keys() - if S_r == r and S_g == g and (r, S_t, p) in M.V_Capacity.keys() + if S_r == r and S_g == g and (r, S_t, p) in M.V_NewCapacity.keys() ) min_cap_share = value(M.MinNewCapacityShare[r, p, t, g]) @@ -2278,11 +2303,11 @@ def MaxNewCapacityShare_Constraint(M, r, p, t, g): that no more than 10% of LDV purchases in a given year must be of a certain type. """ - capacity_t = M.V_Capacity[r, t, p] + capacity_t = M.V_NewCapacity[r, t, p] capacity_group = sum( - M.V_Capacity[r, S_t, p] + M.V_NewCapacity[r, S_t, p] for (S_r, S_g, S_t) in M.tech_groups.keys() - if S_r == r and S_g == g and (r, S_t, p) in M.V_Capacity.keys() + if S_r == r and S_g == g and (r, S_t, p) in M.V_NewCapacity.keys() ) max_cap_share = value(M.MaxNewCapacityShare[r, p, t, g])