From 0c4600a96b09255d65c17860813c6ebf231cd4bf Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 13:53:32 -0400 Subject: [PATCH 1/6] Make sense of these capacity indices sets --- temoa/components/capacity.py | 106 +++++++++++++++-------------------- temoa/components/costs.py | 11 +--- temoa/core/model.py | 15 ++--- 3 files changed, 52 insertions(+), 80 deletions(-) diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py index 5b8cac0f..ab8665c1 100644 --- a/temoa/components/capacity.py +++ b/temoa/components/capacity.py @@ -156,7 +156,7 @@ def get_capacity_factor( # ============================================================================ -def capacity_variable_indices( +def new_capacity_variable_indices( model: TemoaModel, ) -> set[tuple[Region, Technology, Vintage]] | None: return model.new_capacity_rtv @@ -184,6 +184,12 @@ def annual_retirement_variable_indices( } +def capacity_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]] | None: + return model.active_capacity_rptv + + def capacity_available_variable_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology]] | None: @@ -193,77 +199,57 @@ def capacity_available_variable_indices( def regional_exchange_capacity_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Region, Period, Technology, Vintage]]: - indices: set[tuple[Region, Region, Period, Technology, Vintage]] = set() - for r_e, p, i in model.export_regions: - for r_i, t, v, _o in model.export_regions[r_e, p, i]: - indices.add((r_e, r_i, p, t, v)) - - return indices + return { + (r_to, r_from, p, t, v) + for r_from, p, i in model.export_regions + for r_to, t, v, _o in model.export_regions[r_from, p, i] + } def capacity_annual_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, Vintage]]: - capacity_indices: set[tuple[Region, Period, Technology, Vintage]] = set() - if model.active_activity_rptv: - for r, p, t, v in model.active_activity_rptv: - if t in model.tech_annual and t not in model.tech_demand: - if t not in model.tech_uncap: - capacity_indices.add((r, p, t, v)) - else: - return set() - - return capacity_indices + return { + (r, p, t, v) + for r, p, t, v in model.active_capacity_rptv + if t in model.tech_annual and t not in model.tech_demand + } def capacity_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: - capacity_indices: set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] = set() - if model.active_activity_rptv: - for r, p, t, v in model.active_activity_rptv: - if t not in model.tech_annual or t in model.tech_demand: - if t not in model.tech_uncap: - if t not in model.tech_storage: - for s in model.time_season: - for d in model.time_of_day: - capacity_indices.add((r, p, s, d, t, v)) - else: - return set() - - return capacity_indices - - -@deprecated('switched over to validator... this set is typically VERY empty') -def capacity_factor_process_indices( - model: TemoaModel, -) -> set[tuple[Region, Season, TimeOfDay, Technology, Vintage]]: - indices: set[tuple[Region, Season, TimeOfDay, Technology, Vintage]] = set() - for r, _i, t, v, _o in model.efficiency.sparse_keys(): - for s in model.time_season: - for d in model.time_of_day: - indices.add((r, s, d, t, v)) - return indices + return { + (r, p, s, d, t, v) + for r, p, t, v in model.active_capacity_rptv + for s in model.time_season + for d in model.time_of_day + if t not in model.tech_annual or t in model.tech_demand + if t not in model.tech_storage + } def capacity_factor_tech_indices( model: TemoaModel, ) -> set[tuple[Region, Season, TimeOfDay, Technology]]: - all_cfs: set[tuple[Region, Season, TimeOfDay, Technology]] = set() - if model.active_capacity_available_rpt: # in case every tech in the model is unlim_cap - for r, _p, t in model.active_capacity_available_rpt: - for s in model.time_season: - for d in model.time_of_day: - all_cfs.add((r, s, d, t)) - else: - return set() - return all_cfs + return { + (r, s, d, t) + for r, _p, t in model.active_capacity_available_rpt + for s in model.time_season + for d in model.time_of_day + } -def capacity_available_variable_indices_vintage( +@deprecated('switched over to validator... this set is typically VERY empty') +def capacity_factor_process_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Technology, Vintage]] | None: - return model.active_capacity_available_rptv +) -> set[tuple[Region, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, s, d, t, v) + for r, _i, t, v, _o in model.efficiency.sparse_keys() + for s in model.time_season + for d in model.time_of_day + } # ============================================================================ @@ -660,18 +646,14 @@ def create_capacity_and_retirement_sets(model: TemoaModel) -> None: # Create active capacity index sets from the now-populated process_vintages model.new_capacity_rtv = { (r, t, v) - for r, p, t in model.process_vintages - for v in model.process_vintages[r, p, t] + for r, t, v in model.process_periods if t not in model.tech_uncap and v in model.time_optimize } model.active_capacity_available_rpt = { - (r, p, t) - for r, p, t in model.process_vintages - if model.process_vintages[r, p, t] and t not in model.tech_uncap + (r, p, t) for r, p, t in model.process_vintages if t not in model.tech_uncap } - model.active_capacity_available_rptv = { + model.active_capacity_rptv = { (r, p, t, v) - for r, p, t in model.process_vintages + for r, p, t in model.active_capacity_available_rpt for v in model.process_vintages[r, p, t] - if t not in model.tech_uncap } diff --git a/temoa/components/costs.py b/temoa/components/costs.py index 1c91d78e..640af09a 100644 --- a/temoa/components/costs.py +++ b/temoa/components/costs.py @@ -97,18 +97,11 @@ def get_loan_life(model: TemoaModel, r: Region, t: Technology, v: Vintage) -> in def cost_fixed_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, Vintage]]: - # we pull the unlimited capacity techs from this index. They cannot have fixed costs - if model.active_activity_rptv: - return { - (r, p, t, v) for r, p, t, v in model.active_activity_rptv if t not in model.tech_uncap - } - return set() + return model.active_capacity_rptv def cost_variable_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, Vintage]]: - if model.active_activity_rptv: - return model.active_activity_rptv - return set() + return model.active_activity_rptv def lifetime_loan_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Vintage]]: diff --git a/temoa/core/model.py b/temoa/core/model.py index 4ce2a027..4675b4ac 100755 --- a/temoa/core/model.py +++ b/temoa/core/model.py @@ -132,7 +132,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.new_capacity_rtv: t.NewCapacitySet = set() self.active_capacity_available_rpt: t.ActiveCapacityAvailableSet = set() - self.active_capacity_available_rptv: t.ActiveCapacityAvailableVintageSet = set() + self.active_capacity_rptv: t.ActiveCapacityAvailableVintageSet = set() self.group_region_active_flow_rpt: t.GroupRegionActiveFlowSet = ( set() # Set of valid group-region, period, tech indices ) @@ -538,10 +538,10 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.initialize_efficiency_variable = BuildAction(rule=technology.check_efficiency_variable) # Define technology cost parameters - # dev note: the cost_fixed_rptv isn't truly needed, but it is included in a constraint, so - # let it go for now self.cost_fixed_rptv = Set(dimen=4, initialize=costs.cost_fixed_indices) self.cost_fixed = Param(self.cost_fixed_rptv) + self.cost_variable_rptv = Set(dimen=4, initialize=costs.cost_variable_indices) + self.cost_variable = Param(self.cost_variable_rptv) self.cost_invest_rtv = Set( within=self.regional_indices * self.tech_all * self.time_optimize @@ -556,9 +556,6 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.cost_invest_rtv, initialize=costs.param_loan_annualize_rule ) - self.cost_variable_rptv = Set(dimen=4, initialize=costs.cost_variable_indices) - self.cost_variable = Param(self.cost_variable_rptv) - self.cost_emission_rpe = Set( within=self.regions * self.time_optimize * self.commodity_emissions ) @@ -784,10 +781,10 @@ def __init__(self, *args: object, **kwargs: object) -> None: ) # Derived decision variables - self.capacity_var_rptv = Set(dimen=4, initialize=costs.cost_fixed_indices) + self.capacity_var_rptv = Set(dimen=4, initialize=capacity.capacity_variable_indices) self.v_capacity = Var(self.capacity_var_rptv, domain=NonNegativeReals) - self.new_capacity_var_rtv = Set(dimen=3, initialize=capacity.capacity_variable_indices) + self.new_capacity_var_rtv = Set(dimen=3, initialize=capacity.new_capacity_variable_indices) self.v_new_capacity = Var(self.new_capacity_var_rtv, domain=NonNegativeReals, initialize=0) self.retired_capacity_var_rptv = Set( @@ -856,7 +853,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: ['Starting adjusted_capacity_constraint'], rule=progress_check ) self.adjusted_capacity_constraint = Constraint( - self.cost_fixed_rptv, rule=capacity.adjusted_capacity_constraint + self.capacity_var_rptv, rule=capacity.adjusted_capacity_constraint ) self.progress_marker_5 = BuildAction(['Finished Capacity Constraints'], rule=progress_check) From 9691349c97e090f0d6e52c3115a01eed60434c9a Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 13:56:17 -0400 Subject: [PATCH 2/6] Use the efficient CFP lookup --- temoa/components/capacity.py | 11 ++--------- temoa/components/reserves.py | 8 ++++---- temoa/components/utils.py | 9 +++++++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py index ab8665c1..b5dcee1d 100644 --- a/temoa/components/capacity.py +++ b/temoa/components/capacity.py @@ -18,6 +18,8 @@ from deprecated import deprecated from pyomo.environ import value +from .utils import get_capacity_factor + if TYPE_CHECKING: from temoa.core.model import TemoaModel from temoa.types import ( @@ -142,15 +144,6 @@ def get_default_capacity_factor( return value(model.capacity_factor_tech[r, s, d, t]) -def get_capacity_factor( - model: TemoaModel, r: Region, s: Season, d: TimeOfDay, t: Technology, v: Vintage -) -> float: - if model.is_capacity_factor_process[r, t, v]: - return value(model.capacity_factor_process[r, s, d, t, v]) - else: - return value(model.capacity_factor_tech[r, s, d, t]) - - # ============================================================================ # PYOMO INDEX SETS # ============================================================================ diff --git a/temoa/components/reserves.py b/temoa/components/reserves.py index 1a65f5ad..8b9f769b 100644 --- a/temoa/components/reserves.py +++ b/temoa/components/reserves.py @@ -15,7 +15,7 @@ from pyomo.environ import Constraint, value -from .utils import get_variable_efficiency +from .utils import get_capacity_factor, get_variable_efficiency if TYPE_CHECKING: from temoa.core.model import TemoaModel @@ -100,7 +100,7 @@ def reserve_margin_dynamic( available = sum( model.v_capacity[r, p, t, v] * value(model.reserve_capacity_derate[r, s, t, v]) - * value(model.capacity_factor_process[r, s, d, t, v]) + * get_capacity_factor(model, r, s, d, t, v) * value(model.capacity_to_activity[r, t]) * value(model.segment_fraction[s, d]) for (t, v) in model.process_reserve_periods[r, p] @@ -149,7 +149,7 @@ def reserve_margin_dynamic( available += sum( model.v_capacity[r1r2, p, t, v] * value(model.reserve_capacity_derate[r1r2, s, t, v]) - * value(model.capacity_factor_process[r1r2, s, d, t, v]) + * get_capacity_factor(model, r1r2, s, d, t, v) * value(model.capacity_to_activity[r1r2, t]) * value(model.segment_fraction[s, d]) for (t, v) in model.process_reserve_periods[r1r2, p] @@ -160,7 +160,7 @@ def reserve_margin_dynamic( available -= sum( model.v_capacity[r1r2, p, t, v] * value(model.reserve_capacity_derate[r1r2, s, t, v]) - * value(model.capacity_factor_process[r1r2, s, d, t, v]) + * get_capacity_factor(model, r1r2, s, d, t, v) * value(model.capacity_to_activity[r1r2, t]) * value(model.segment_fraction[s, d]) for (t, v) in model.process_reserve_periods[r1r2, p] diff --git a/temoa/components/utils.py b/temoa/components/utils.py index c26ccbaf..d42555f2 100644 --- a/temoa/components/utils.py +++ b/temoa/components/utils.py @@ -80,3 +80,12 @@ def get_variable_efficiency( ) else: return value(model.efficiency[r, i, t, v, o]) + + +def get_capacity_factor( + model: TemoaModel, r: Region, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> float: + if model.is_capacity_factor_process[r, t, v]: + return value(model.capacity_factor_process[r, s, d, t, v]) + else: + return value(model.capacity_factor_tech[r, s, d, t]) From 4770898ebde526e2085221f0e7766d8ab4baaa7b Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 14:02:40 -0400 Subject: [PATCH 3/6] Discovered that capacity factor constraints for storage techs were silently ignored! --- temoa/components/storage.py | 3 ++- tests/legacy_test_values.py | 3 ++- tests/test_full_runs.py | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/temoa/components/storage.py b/temoa/components/storage.py index 4f1aea8d..bd0c5ac6 100644 --- a/temoa/components/storage.py +++ b/temoa/components/storage.py @@ -18,7 +18,7 @@ from pyomo.environ import Constraint, value -from .utils import Operator, get_variable_efficiency, operator_expression +from .utils import Operator, get_capacity_factor, get_variable_efficiency, operator_expression if TYPE_CHECKING: from temoa.core.model import TemoaModel @@ -547,6 +547,7 @@ def storage_throughput_constraint( max_throughput = ( model.v_capacity[r, p, t, v] * value(model.capacity_to_activity[r, t]) + * get_capacity_factor(model, r, s, d, t, v) * value(model.segment_fraction[s, d]) ) expr = throughput <= max_throughput diff --git a/tests/legacy_test_values.py b/tests/legacy_test_values.py index 2852819f..af31669c 100644 --- a/tests/legacy_test_values.py +++ b/tests/legacy_test_values.py @@ -44,7 +44,8 @@ class ExpectedVals(Enum): # increased after rework of inter-season sequencing # reduced after changing fixed costs from MLP to PL # reduced by <1 after changing season definition (segfrac no longer rounded) - ExpectedVals.OBJ_VALUE: 34710.6730, + # increased by 143 after activating CF constraints for storage techs + ExpectedVals.OBJ_VALUE: 34853.6835, ExpectedVals.EFF_DOMAIN_SIZE: 12312, ExpectedVals.EFF_INDEX_SIZE: 64, # reduced 3/27: unlim_cap techs now employed. diff --git a/tests/test_full_runs.py b/tests/test_full_runs.py index de54159e..01ff2153 100644 --- a/tests/test_full_runs.py +++ b/tests/test_full_runs.py @@ -121,7 +121,8 @@ def test_myopic_utopia( # reduced after removing ancient 1-year shift bug from objective function # increased after rework of inter-season sequencing # reduced by <1 after changing season definition (segfrac no longer rounded) - assert invest_sum == pytest.approx(11004.3544), 'sum of investment costs did not match expected' + # decreased by 41 after activating CF constraints for storage techs + assert invest_sum == pytest.approx(10963.1018), 'sum of investment costs did not match expected' con.close() @@ -141,7 +142,8 @@ def test_stochastic_utopia( # Stochastic Expected Value for current utopia configuration # reduced by <1 after changing season definition (segfrac no longer rounded) - expected_obj = 34389.1352 + # increased by 145 after activating CF constraints for storage techs + expected_obj = 34534.1822 assert sequencer.stochastic_sequencer is not None assert sequencer.stochastic_sequencer.objective_value == pytest.approx(expected_obj, rel=1e-5) From 3bfc489178598b2fb32e57727a084883920c0e80 Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 14:04:27 -0400 Subject: [PATCH 4/6] Unify formatting and remove never-true (and mypy triggering) None types from sets --- temoa/components/commodities.py | 11 +++------ temoa/components/costs.py | 4 +--- temoa/components/emissions.py | 8 ++----- temoa/components/limits.py | 34 +++++++++----------------- temoa/components/operations.py | 20 ++++------------ temoa/components/reserves.py | 4 +--- temoa/components/storage.py | 15 ++++-------- temoa/types/set_types.py | 42 ++++++++++++++++----------------- 8 files changed, 49 insertions(+), 89 deletions(-) diff --git a/temoa/components/commodities.py b/temoa/components/commodities.py index 4a4694fc..eb668e6b 100644 --- a/temoa/components/commodities.py +++ b/temoa/components/commodities.py @@ -136,7 +136,7 @@ def demand_activity_constraint_indices( demand commodity (and no annual techs co-serve it), the flow variables are fixed directly and DAC indices are omitted. """ - indices = { + return { (r, p, s, d, t, v, dem) for r, p, dem in model.demand_constraint_rpc if (r, p, dem) not in model.singleton_demands @@ -145,7 +145,6 @@ def demand_activity_constraint_indices( for s in model.time_season for d in model.time_of_day } - return indices def commodity_balance_constraint_indices( @@ -153,7 +152,7 @@ def commodity_balance_constraint_indices( ) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity]]: # Generate indices only for those commodities that are produced by # technologies with varying output at the time slice level. - indices = { + return { (r, p, s, d, c) for r, p, c in model.commodity_balance_rpc # r in this line includes interregional transfer combinations (not needed). @@ -163,15 +162,13 @@ def commodity_balance_constraint_indices( for d in model.time_of_day } - return indices - def annual_commodity_balance_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Commodity]]: # Generate indices only for those commodities that are produced by # technologies with constant annual output. - indices = { + return { (r, p, c) for r, p, c in model.commodity_balance_rpc # r in this line includes interregional transfer combinations (not needed). @@ -179,8 +176,6 @@ def annual_commodity_balance_constraint_indices( and c in model.commodity_annual } - return indices - # ============================================================================ # PYOMO CONSTRAINT RULES diff --git a/temoa/components/costs.py b/temoa/components/costs.py index 640af09a..8d1f2943 100644 --- a/temoa/components/costs.py +++ b/temoa/components/costs.py @@ -113,9 +113,7 @@ def lifetime_loan_process_indices(model: TemoaModel) -> set[tuple[Region, Techno because in myopic mode, previously optimized vintages remain active in later windows and their data must be accepted by the param's index set. """ - indices = {(r, t, v) for r, i, t, v, o in model.efficiency.sparse_keys()} - - return indices + return {(r, t, v) for r, i, t, v, o in model.efficiency.sparse_keys()} # ============================================================================ diff --git a/temoa/components/emissions.py b/temoa/components/emissions.py index 4ee6669d..4d6d908b 100644 --- a/temoa/components/emissions.py +++ b/temoa/components/emissions.py @@ -38,20 +38,18 @@ def emission_activity_indices( model: TemoaModel, ) -> set[tuple[Region, Commodity, Commodity, Technology, Vintage, Commodity]]: - indices = { + return { (r, e, i, t, v, o) for r, i, t, v, o in model.efficiency.sparse_keys() for e in model.commodity_emissions if r in model.regions # omit any exchange/groups } - return indices - def linked_tech_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, Commodity]]: - linkedtech_indices = { + return { (r, p, s, d, t, v, e) for r, t, e in model.linked_techs.sparse_keys() for p in model.time_optimize @@ -62,8 +60,6 @@ def linked_tech_constraint_indices( for d in model.time_of_day } - return linkedtech_indices - # ============================================================================ # PYOMO CONSTRAINT RULES diff --git a/temoa/components/limits.py b/temoa/components/limits.py index 4d2cd095..f1e7087a 100644 --- a/temoa/components/limits.py +++ b/temoa/components/limits.py @@ -64,26 +64,23 @@ def limit_tech_input_split_constraint_indices( def limit_tech_input_split_annual_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Commodity, Technology, Vintage, str]]: - indices = { + return { (r, p, i, t, v, op) for r, p, i, t, op in model.input_split_annual_vintages if t in model.tech_annual for v in model.input_split_annual_vintages[r, p, i, t, op] } - return indices - def limit_tech_input_split_average_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Commodity, Technology, Vintage, str]]: - indices = { + return { (r, p, i, t, v, op) for r, p, i, t, op in model.input_split_annual_vintages if t not in model.tech_annual for v in model.input_split_annual_vintages[r, p, i, t, op] } - return indices def limit_tech_output_split_constraint_indices( @@ -114,89 +111,81 @@ def limit_tech_output_split_constraint_indices( def limit_tech_output_split_annual_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: - indices = { + return { (r, p, t, v, o, op) for r, p, t, o, op in model.output_split_annual_vintages if t in model.tech_annual for v in model.output_split_annual_vintages[r, p, t, o, op] } - return indices def limit_tech_output_split_average_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: - indices = { + return { (r, p, t, v, o, op) for r, p, t, o, op in model.output_split_annual_vintages if t not in model.tech_annual for v in model.output_split_annual_vintages[r, p, t, o, op] } - return indices def limit_growth_capacity_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_growth_capacity.sparse_keys() for p in model.time_optimize } - return indices def limit_degrowth_capacity_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_degrowth_capacity.sparse_keys() for p in model.time_optimize } - return indices def limit_growth_new_capacity_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_growth_new_capacity.sparse_keys() for p in model.time_optimize } - return indices def limit_degrowth_new_capacity_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_degrowth_new_capacity.sparse_keys() for p in model.time_optimize } - return indices def limit_growth_new_capacity_delta_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_growth_new_capacity_delta.sparse_keys() for p in model.time_optimize } - return indices def limit_degrowth_new_capacity_delta_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, str]]: - indices = { + return { (r, p, t, op) for r, t, op in model.limit_degrowth_new_capacity_delta.sparse_keys() for p in model.time_optimize } - return indices def limit_seasonal_capacity_factor_constraint_indices( @@ -213,7 +202,7 @@ def limit_seasonal_capacity_factor_constraint_indices( def limit_annual_capacity_factor_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: - indices = { + return { (r, p, t, v, o, op) for r, t, v, o, op in model.limit_annual_capacity_factor_constraint_rtvo for _r in geography.gather_group_regions(model, r) @@ -221,7 +210,6 @@ def limit_annual_capacity_factor_indices( for p in model.time_optimize if o in model.process_outputs.get((_r, p, _t, v), []) } - return indices # ============================================================================ diff --git a/temoa/components/operations.py b/temoa/components/operations.py index 8274d690..2e6b3721 100644 --- a/temoa/components/operations.py +++ b/temoa/components/operations.py @@ -32,7 +32,7 @@ def baseload_diurnal_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: - indices = { + return { (r, p, s, d, t, v) for r, p, t in model.baseload_vintages for v in model.baseload_vintages[r, p, t] @@ -40,13 +40,11 @@ def baseload_diurnal_constraint_indices( for d in model.time_of_day } - return indices - def ramp_up_day_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: - indices = { + return { (r, p, s, d, t, v) for r, p, t in model.ramp_up_vintages for v in model.ramp_up_vintages[r, p, t] @@ -54,13 +52,11 @@ def ramp_up_day_constraint_indices( for d in model.time_of_day } - return indices - def ramp_down_day_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: - indices = { + return { (r, p, s, d, t, v) for r, p, t in model.ramp_down_vintages for v in model.ramp_down_vintages[r, p, t] @@ -68,8 +64,6 @@ def ramp_down_day_constraint_indices( for d in model.time_of_day } - return indices - def ramp_up_season_constraint_indices( model: TemoaModel, @@ -80,7 +74,7 @@ def ramp_up_season_constraint_indices( return set() # s, s_next indexing ensures we dont build redundant constraints - indices = { + return { (r, p, s, s_next, t, v) for r, p, t in model.ramp_up_vintages for v in model.ramp_up_vintages[r, p, t] @@ -89,8 +83,6 @@ def ramp_up_season_constraint_indices( if s_next != model.time_next[s, model.time_of_day.last()][0] } - return indices - def ramp_down_season_constraint_indices( model: TemoaModel, @@ -101,7 +93,7 @@ def ramp_down_season_constraint_indices( return set() # s, s_next indexing ensures we dont build redundant constraints - indices = { + return { (r, p, s, s_next, t, v) for r, p, t in model.ramp_down_vintages for v in model.ramp_down_vintages[r, p, t] @@ -110,8 +102,6 @@ def ramp_down_season_constraint_indices( if s_next != model.time_next[s, model.time_of_day.last()][0] } - return indices - # ============================================================================ # PYOMO CONSTRAINT RULES diff --git a/temoa/components/reserves.py b/temoa/components/reserves.py index 8b9f769b..ff969db6 100644 --- a/temoa/components/reserves.py +++ b/temoa/components/reserves.py @@ -30,7 +30,7 @@ def reserve_margin_indices(model: TemoaModel) -> set[tuple[Region, Period, Season, TimeOfDay]]: - indices = { + return { (r, p, s, d) for r in model.planning_reserve_margin.sparse_keys() for p in model.time_optimize @@ -39,8 +39,6 @@ def reserve_margin_indices(model: TemoaModel) -> set[tuple[Region, Period, Seaso for d in model.time_of_day } - return indices - # ============================================================================ # HELPER FUNCTIONS FOR CONSTRAINT LOGIC diff --git a/temoa/components/storage.py b/temoa/components/storage.py index bd0c5ac6..0ed979fb 100644 --- a/temoa/components/storage.py +++ b/temoa/components/storage.py @@ -49,14 +49,11 @@ def seasonal_storage_level_variable_indices( def seasonal_storage_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: - if model.seasonal_storage_level_indices_rpstv: - indices = { - (r, p, s, d, t, v) - for r, p, s, t, v in model.seasonal_storage_level_indices_rpstv - for d in model.time_of_day - } - return indices - return set() + return { + (r, p, s, d, t, v) + for r, p, s, t, v in model.seasonal_storage_level_indices_rpstv + for d in model.time_of_day + } def storage_init_variable_indices( @@ -69,8 +66,6 @@ def storage_init_variable_indices( empirically improved barrier solve time ~25% on a 16-region national model. The mechanism is not fully understood. """ - if not model.storage_level_indices_rpsdtv: - return set() return { (r, p, s, t, v) for r, p, s, _d, t, v in model.storage_level_indices_rpsdtv diff --git a/temoa/types/set_types.py b/temoa/types/set_types.py index d910db32..57bd1831 100644 --- a/temoa/types/set_types.py +++ b/temoa/types/set_types.py @@ -11,27 +11,27 @@ ) # Set types for sparse indexing -ActiveFlowSet = ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -) -ActiveFlowAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] | None -ActiveFlexSet = ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -) -ActiveFlexAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] | None -ActiveFlowInStorageSet = ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -) -ActiveCurtailmentSet = ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -) -ActiveActivitySet = set[tuple[Region, Period, Technology, Vintage]] | None -StorageLevelIndicesSet = set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] | None -SeasonalStorageLevelIndicesSet = set[tuple[Region, Period, Season, Technology, Vintage]] | None -NewCapacitySet = set[tuple[Region, Technology, Vintage]] | None -ActiveCapacityAvailableSet = set[tuple[Region, Period, Technology]] | None -ActiveCapacityAvailableVintageSet = set[tuple[Region, Period, Technology, Vintage]] | None -GroupRegionActiveFlowSet = set[tuple[Region, Period, Technology]] | None +ActiveFlowSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveFlowAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] +ActiveFlexSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveFlexAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] +ActiveFlowInStorageSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveCurtailmentSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveActivitySet = set[tuple[Region, Period, Technology, Vintage]] +StorageLevelIndicesSet = set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] +SeasonalStorageLevelIndicesSet = set[tuple[Region, Period, Season, Technology, Vintage]] +NewCapacitySet = set[tuple[Region, Technology, Vintage]] +ActiveCapacityAvailableSet = set[tuple[Region, Period, Technology]] +ActiveCapacityAvailableVintageSet = set[tuple[Region, Period, Technology, Vintage]] +GroupRegionActiveFlowSet = set[tuple[Region, Period, Technology]] CommodityBalancedSet = set[tuple[Region, Period, Commodity]] SingletonDemandsSet = set[tuple[Region, Period, Commodity]] From 7a3d7742cb66227dbe59d1aece0250ff943c6b5d Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 15:42:32 -0400 Subject: [PATCH 5/6] Remove some more unnecessary None types --- temoa/components/capacity.py | 6 +++--- temoa/components/flows.py | 16 ++++------------ temoa/components/storage.py | 6 +++--- temoa/components/technology.py | 2 +- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py index b5dcee1d..ada38fa6 100644 --- a/temoa/components/capacity.py +++ b/temoa/components/capacity.py @@ -151,7 +151,7 @@ def get_default_capacity_factor( def new_capacity_variable_indices( model: TemoaModel, -) -> set[tuple[Region, Technology, Vintage]] | None: +) -> set[tuple[Region, Technology, Vintage]]: return model.new_capacity_rtv @@ -179,13 +179,13 @@ def annual_retirement_variable_indices( def capacity_variable_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Technology, Vintage]] | None: +) -> set[tuple[Region, Period, Technology, Vintage]]: return model.active_capacity_rptv def capacity_available_variable_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Technology]] | None: +) -> set[tuple[Region, Period, Technology]]: return model.active_capacity_available_rpt diff --git a/temoa/components/flows.py b/temoa/components/flows.py index a6db3e9a..656848a6 100644 --- a/temoa/components/flows.py +++ b/temoa/components/flows.py @@ -41,9 +41,7 @@ def flow_variable_indices( model: TemoaModel, -) -> ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -): +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: return model.active_flow_rpsditvo @@ -55,9 +53,7 @@ def flow_variable_annual_indices( def flex_variable_indices( model: TemoaModel, -) -> ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -): +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: return model.active_flex_rpsditvo @@ -69,17 +65,13 @@ def flex_variable_annual_indices( def flow_in_storage_variable_indices( model: TemoaModel, -) -> ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -): +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: return model.active_flow_in_storage_rpsditvo def curtailment_variable_indices( model: TemoaModel, -) -> ( - set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] | None -): +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: return model.active_curtailment_rpsditvo diff --git a/temoa/components/storage.py b/temoa/components/storage.py index 0ed979fb..eb3daf71 100644 --- a/temoa/components/storage.py +++ b/temoa/components/storage.py @@ -36,13 +36,13 @@ def storage_level_variable_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] | None: +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: return model.storage_level_indices_rpsdtv def seasonal_storage_level_variable_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Season, Technology, Vintage]] | None: +) -> set[tuple[Region, Period, Season, Technology, Vintage]]: return model.seasonal_storage_level_indices_rpstv @@ -75,7 +75,7 @@ def storage_init_variable_indices( def storage_constraint_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] | None: +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: return model.storage_level_indices_rpsdtv diff --git a/temoa/components/technology.py b/temoa/components/technology.py index 10945de5..b6577d89 100644 --- a/temoa/components/technology.py +++ b/temoa/components/technology.py @@ -46,7 +46,7 @@ def gather_group_techs(model: TemoaModel, t_or_g: Technology) -> Iterable[Techno def model_process_life_indices( model: TemoaModel, -) -> set[tuple[Region, Period, Technology, Vintage]] | None: +) -> set[tuple[Region, Period, Technology, Vintage]]: """ Returns the set of sensical (region, period, tech, vintage) tuples. The tuple indicates the periods in which a process is active, distinct from TechLifeFracIndices that From b28e08233aaaa6858f0114324b721fcd443e387f Mon Sep 17 00:00:00 2001 From: Davey Elder Date: Fri, 27 Mar 2026 15:42:48 -0400 Subject: [PATCH 6/6] Redundant else returns --- temoa/components/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/temoa/components/utils.py b/temoa/components/utils.py index d42555f2..2fd417a3 100644 --- a/temoa/components/utils.py +++ b/temoa/components/utils.py @@ -78,8 +78,7 @@ def get_variable_efficiency( return value(model.efficiency[r, i, t, v, o]) * value( model.efficiency_variable[r, s, d, i, t, v, o] ) - else: - return value(model.efficiency[r, i, t, v, o]) + return value(model.efficiency[r, i, t, v, o]) def get_capacity_factor( @@ -87,5 +86,4 @@ def get_capacity_factor( ) -> float: if model.is_capacity_factor_process[r, t, v]: return value(model.capacity_factor_process[r, s, d, t, v]) - else: - return value(model.capacity_factor_tech[r, s, d, t]) + return value(model.capacity_factor_tech[r, s, d, t])