diff --git a/data_files/temoa_schema.sql b/data_files/temoa_schema.sql index a1a4d328..7a295795 100644 --- a/data_files/temoa_schema.sql +++ b/data_files/temoa_schema.sql @@ -381,6 +381,32 @@ CREATE TABLE "MaxActivity" ( FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), FOREIGN KEY("tech") REFERENCES "technologies"("tech") ); +CREATE TABLE IF NOT EXISTS "MinAnnualCapacityFactor" ( + "regions" text, + "periods" integer, + "tech" text, + "output_comm" text, + "min_acf" real CHECK("min_acf" >= 0 AND "min_acf" <= 1), + "source" text, + "min_acf_notes" text, + PRIMARY KEY("regions","periods","tech"), + FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name") +); +CREATE TABLE IF NOT EXISTS "MaxAnnualCapacityFactor" ( + "regions" text, + "periods" integer, + "tech" text, + "output_comm" text, + "max_acf" real CHECK("max_acf" >= 0 AND "max_acf" <= 1), + "source" text, + "max_acf_notes" text, + PRIMARY KEY("regions","periods","tech"), + FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), + FOREIGN KEY("tech") REFERENCES "technologies"("tech"), + FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name") +); CREATE TABLE "LifetimeTech" ( "regions" text, "tech" text, diff --git a/temoa_model/temoa_config.py b/temoa_model/temoa_config.py index 87e4790f..5be85665 100644 --- a/temoa_model/temoa_config.py +++ b/temoa_model/temoa_config.py @@ -146,10 +146,12 @@ def query_table (t_properties, f): ['param','TechInputSplitAverage', '', '', 4], ['param','MinCapacity', '', '', 3], ['param','MaxCapacity', '', '', 3], - ['param', 'MinNewCapacity', '', '', 3], - ['param', 'MaxNewCapacity', '', '', 3], + ['param','MinNewCapacity', '', '', 3], + ['param','MaxNewCapacity', '', '', 3], ['param','MaxActivity', '', '', 3], ['param','MinActivity', '', '', 3], + ['param','MinAnnualCapacityFactor', '', '', 4], + ['param','MaxAnnualCapacityFactor', '', '', 4], ['param','MaxResource', '', '', 2], ['param','GrowthRateMax', '', '', 2], ['param','GrowthRateSeed', '', '', 2], diff --git a/temoa_model/temoa_model.py b/temoa_model/temoa_model.py index e1701997..95e3c11b 100755 --- a/temoa_model/temoa_model.py +++ b/temoa_model/temoa_model.py @@ -216,6 +216,8 @@ def temoa_create_model(name="Temoa"): M.MaxCapacitySum = Param(M.time_optimize) # for techs in tech_capacity M.MaxActivity = Param(M.RegionalGlobalIndices, M.time_optimize, M.tech_all) M.MinActivity = Param(M.RegionalGlobalIndices, M.time_optimize, M.tech_all) + M.MinAnnualCapacityFactor = Param(M.RegionalGlobalIndices, M.time_optimize, M.tech_all, M.commodity_carrier) + M.MaxAnnualCapacityFactor = Param(M.RegionalGlobalIndices, M.time_optimize, M.tech_all, M.commodity_carrier) M.GrowthRateMax = Param(M.RegionalIndices, M.tech_all) M.GrowthRateSeed = Param(M.RegionalIndices, M.tech_all) M.EmissionLimit = Param(M.RegionalGlobalIndices, M.time_optimize, M.commodity_emissions) @@ -505,6 +507,20 @@ def temoa_create_model(name="Temoa"): M.MinCapacitySetConstraint_rp, rule=MinCapacitySet_Constraint ) + M.MinAnnualCapacityFactorConstraint_rpto = Set( + dimen=4, initialize=lambda M: M.MinAnnualCapacityFactor.sparse_iterkeys() + ) + M.MinAnnualCapacityFactorConstraint = Constraint( + M.MinAnnualCapacityFactorConstraint_rpto, rule=MinAnnualCapacityFactor_Constraint + ) + + M.MaxAnnualCapacityFactorConstraint_rpto = Set( + dimen=4, initialize=lambda M: M.MaxAnnualCapacityFactor.sparse_iterkeys() + ) + M.MaxAnnualCapacityFactorConstraint = Constraint( + M.MaxAnnualCapacityFactorConstraint_rpto, rule=MaxAnnualCapacityFactor_Constraint + ) + M.TechInputSplitConstraint_rpsditv = Set( dimen=7, initialize=TechInputSplitConstraintIndices ) diff --git a/temoa_model/temoa_rules.py b/temoa_model/temoa_rules.py index c3c07afb..1e512884 100644 --- a/temoa_model/temoa_rules.py +++ b/temoa_model/temoa_rules.py @@ -1943,6 +1943,100 @@ def MinCapacitySet_Constraint(M, p): expr = aggcap >= min_cap return expr +def MinAnnualCapacityFactor_Constraint(M, r, p, t, o): + r""" +The MinAnnualCapacityFactor sets a lower bound on the annual capacity factor +from a specific technology. The first portion of the constraint pertains to +technologies with variable output at the time slice level, and the second portion +pertains to technologies with constant annual output belonging to the +:code:`tech_annual` set. +.. math:: + :label: MinAnnualCapacityFactor + \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \ge MINCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} + \forall \{r, p, t, o\} \in \Theta_{\text{MinAnnualCapacityFactor}} + \sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \ge MINCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} + \forall \{r, p, t, o \in T^{a}\} \in \Theta_{\text{MinAnnualCapacityFactor}} +""" + # r can be an individual region (r='US'), or a combination of regions separated by comma (r='Mexico,US,Canada'), or 'global'. + # if r == 'global', the constraint is system-wide + if r == 'global': + reg = M.regions + else: + reg = [r] + + try: + activity_rpt = sum( + M.V_FlowOut[r, p, s, d, S_i, t, S_v, o] + for r in reg if ',' not in r + for S_v in M.processVintages[r, p, t] + for S_i in M.processInputs[r, p, t, S_v] + for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] + for s in M.time_season + for d in M.time_of_day + ) + except: + activity_rpt = sum( + M.V_FlowOutAnnual[r, p, S_i, t, S_v, o] + for r in reg if ',' not in r + for S_v in M.processVintages[r, p, t] + for S_i in M.processInputs[r, p, t, S_v] + for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] + ) + + max_possible_activity_rpt = M.V_CapacityAvailableByPeriodAndTech[r, p, t] * M.CapacityToActivity[r, t] + min_annual_cf = value(M.MinAnnualCapacityFactor[r, p, t, o]) + expr = activity_rpt >= min_annual_cf * max_possible_activity_rpt + return expr + + +def MaxAnnualCapacityFactor_Constraint(M, r, p, t, o): + r""" + The MaxAnnualCapacityFactor sets an upper bound on the annual capacity factor + from a specific technology. The first portion of the constraint pertains to + technologies with variable output at the time slice level, and the second portion + pertains to technologies with constant annual output belonging to the + :code:`tech_annual` set. + .. math:: + :label: MaxAnnualCapacityFactor + \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \le MAXCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} + \forall \{r, p, t, o\} \in \Theta_{\text{MaxAnnualCapacityFactor}} + \sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \ge MAXCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} + \forall \{r, p, t, o \in T^{a}\} \in \Theta_{\text{MaxAnnualCapacityFactor}} + """ + # r can be an individual region (r='US'), or a combination of regions separated by comma (r='Mexico,US,Canada'), or 'global'. + # if r == 'global', the constraint is system-wide + if r == 'global': + reg = M.regions + else: + reg = [r] + + try: + activity_rpt = sum( + M.V_FlowOut[r, p, s, d, S_i, t, S_v, o] + for r in reg if ',' not in r + for S_v in M.processVintages[r, p, t] + for S_i in M.processInputs[r, p, t, S_v] + for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] + for s in M.time_season + for d in M.time_of_day + ) + except: + activity_rpt = sum( + M.V_FlowOutAnnual[r, p, S_i, t, S_v, o] + for r in reg if ',' not in r + for S_v in M.processVintages[r, p, t] + for S_i in M.processInputs[r, p, t, S_v] + for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] + ) + + max_possible_activity_rpt = M.V_CapacityAvailableByPeriodAndTech[r, p, t] * M.CapacityToActivity[r, t] + max_annual_cf = value(M.MaxAnnualCapacityFactor[r, p, t, o]) + expr = activity_rpt <= max_annual_cf * max_possible_activity_rpt + return expr + + + + def TechInputSplit_Constraint(M, r, p, s, d, i, t, v): r"""