-
Notifications
You must be signed in to change notification settings - Fork 70
Fix treatment of early retirement in myopic mode #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
783f8ee
b791de2
7d59af0
e56a29f
6318324
9dc8ad7
f6d67fb
e5c2f31
a3ef5bf
f98efce
e375b1d
fb13b81
d685f4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pyomo.environ import value | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| from temoa.components.utils import get_adjusted_existing_capacity | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| from collections.abc import Iterable | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -52,15 +54,31 @@ def model_process_life_indices( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| the periods in which a process is active, distinct from TechLifeFracIndices that | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| returns indices only for processes that EOL mid-period. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return model.active_activity_rptv | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| (r, model.time_exist.last(), t, v) for r, t, v in model.existing_capacity.sparse_keys() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = indices | model.active_activity_rptv | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return indices | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def lifetime_tech_indices(model: TemoaModel) -> set[tuple[Region, Technology]]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Based on the efficiency parameter's indices, this function returns the set of | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| process indices that may be specified in the lifetime_tech parameter. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = {(r, t) for r, _, t, _, _ in set(model.efficiency.sparse_keys())} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = indices | {(r, t) for r, t, _ in model.existing_capacity.sparse_keys()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return indices | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def lifetime_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Vintage]]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Based on the efficiency parameter's indices, this function returns the set of | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| process indices that may be specified in the lifetime_process parameter. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = {(r, t, v) for r, i, t, v, o in set(model.efficiency.sparse_keys())} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = {(r, t, v) for r, _, t, v, _ in set(model.efficiency.sparse_keys())} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| indices = indices | set(model.existing_capacity.sparse_keys()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return indices | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -424,14 +442,18 @@ def check_existing_capacity(model: TemoaModel) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning(msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if t not in model.tech_all: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| adjusted_cap = get_adjusted_existing_capacity(model, r, t, v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if adjusted_cap <= 0.0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Was retired in a previous period (myopic mode) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| p = model.time_optimize.first() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| life = value(model.lifetime_process[r, t, v]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (r, t, v) not in model.process_periods and v + life > model.time_optimize.first(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (r, t, v) not in model.process_periods and v + life > p: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| surviving_cap = adjusted_cap * value(model.lifetime_survival_curve[r, p, t, v]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| msg = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| f'Existing capacity {r, t, v} with lifetime {life} and capacity {cap} ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'should extend into future periods but it is not in process periods. ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Was it included in the Efficiency table?' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| f'Existing capacity {r, t, v} with lifetime {life} and surviving capacity ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| f'{surviving_cap} should extend into future periods but is not an active ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'process. It may be missing from the Efficiency table or have too little ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'capacity to output and carry forward if running in myopic mode.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error(msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning(msg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+451
to
+459
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Keep fail-fast validation for material surviving capacity. After line 451, a positive adjusted existing capacity that should survive but is absent from Proposed fix if (r, t, v) not in model.process_periods and v + life > p:
surviving_cap = adjusted_cap * value(model.lifetime_survival_curve[r, p, t, v])
+ if surviving_cap <= 0.0:
+ continue
msg = (
f'Existing capacity {r, t, v} with lifetime {life} and surviving capacity '
f'{surviving_cap} should extend into future periods but is not an active '
'process. It may be missing from the Efficiency table or have too little '
'capacity to output and carry forward if running in myopic mode.'
)
- logger.warning(msg)
+ raise ValueError(msg)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -87,3 +87,20 @@ def get_capacity_factor( | |||||||||||||||||||||||||||||||||||
| if model.is_capacity_factor_process[r, t, v]: | ||||||||||||||||||||||||||||||||||||
| return value(model.capacity_factor_process[r, s, d, t, v]) | ||||||||||||||||||||||||||||||||||||
| return value(model.capacity_factor_tech[r, s, d, t]) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def get_adjusted_existing_capacity( | ||||||||||||||||||||||||||||||||||||
| model: TemoaModel, r: Region, t: Technology, v: Vintage | ||||||||||||||||||||||||||||||||||||
| ) -> float: | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| Returns the built existing capacity adjusted for any early retirements. | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Needed for early retirements in myopic mode. Takes into account survival curves | ||||||||||||||||||||||||||||||||||||
| and any early retirements that may have occurred prior to this planning step. | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| capacity_adjustment = sum( | ||||||||||||||||||||||||||||||||||||
| value(model.retired_existing_capacity[r, _p, t, v]) | ||||||||||||||||||||||||||||||||||||
| / (value(model.lifetime_survival_curve[r, _p, t, v]) or 1) | ||||||||||||||||||||||||||||||||||||
| for _p in model.time_exist | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+101
to
+104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Do not mask zero survival factors when carrying retirements forward. Line 103 turns a zero survival fraction into Proposed fix- capacity_adjustment = sum(
- value(model.retired_existing_capacity[r, _p, t, v])
- / (value(model.lifetime_survival_curve[r, _p, t, v]) or 1)
- for _p in model.time_exist
- )
+ capacity_adjustment = 0.0
+ for _p in model.time_exist:
+ retired_capacity = value(model.retired_existing_capacity[r, _p, t, v])
+ if not retired_capacity:
+ continue
+
+ survival_fraction = value(model.lifetime_survival_curve[r, _p, t, v])
+ if survival_fraction == 0:
+ raise ValueError(
+ f'Cannot adjust retired capacity for {(r, _p, t, v)} '
+ 'with zero survival fraction.'
+ )
+ capacity_adjustment += retired_capacity / survival_fraction📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| return value(model.existing_capacity[r, t, v]) - capacity_adjustment | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -288,6 +288,14 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: | |
| is_period_filtered=False, # Custom loader handles all logic | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.retired_existing_capacity, | ||
| table='output_retired_capacity', | ||
| columns=['region', 'period', 'tech', 'vintage', 'cap_early'], | ||
| custom_loader_name='_load_retired_existing_capacity', | ||
| is_period_filtered=False, # Custom loader handles all logic | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.cost_invest, | ||
| table='cost_invest', | ||
|
|
@@ -419,26 +427,23 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: | |
| component=model.lifetime_tech, | ||
| table='lifetime_tech', | ||
| columns=['region', 'tech', 'lifetime'], | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| custom_loader_name='_load_lifetime_tech', | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.lifetime_process, | ||
| table='lifetime_process', | ||
| columns=['region', 'tech', 'vintage', 'lifetime'], | ||
| validator_name='viable_rtv', | ||
| validation_map=(0, 1, 2), | ||
| custom_loader_name='_load_lifetime_process', | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.lifetime_survival_curve, | ||
| table='lifetime_survival_curve', | ||
| columns=['region', 'period', 'tech', 'vintage', 'fraction'], | ||
| validator_name='viable_rtv', | ||
| validation_map=(0, 2, 3), | ||
| custom_loader_name='_load_lifetime_survival_curve', | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
|
|
@@ -649,6 +654,66 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: | |
| validation_map=(0, 1, 2), | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_growth_capacity, | ||
| table='limit_growth_capacity', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_growth_new_capacity, | ||
| table='limit_growth_new_capacity', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_growth_new_capacity_delta, | ||
| table='limit_growth_new_capacity_delta', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_degrowth_capacity, | ||
| table='limit_degrowth_capacity', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_degrowth_new_capacity, | ||
| table='limit_degrowth_new_capacity', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
| LoadItem( | ||
| component=model.limit_degrowth_new_capacity_delta, | ||
| table='limit_degrowth_new_capacity_delta', | ||
| columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], | ||
| index_length=3, | ||
| validator_name='viable_rt', | ||
| validation_map=(0, 1), | ||
| is_period_filtered=False, | ||
| is_table_required=False, | ||
| ), | ||
|
Comment on lines
+657
to
+716
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Six near-identical growth/degrowth All six differ only in ♻️ Sketchgrowth_specs = [
(model.limit_growth_capacity, 'limit_growth_capacity'),
(model.limit_growth_new_capacity, 'limit_growth_new_capacity'),
(model.limit_growth_new_capacity_delta, 'limit_growth_new_capacity_delta'),
(model.limit_degrowth_capacity, 'limit_degrowth_capacity'),
(model.limit_degrowth_new_capacity, 'limit_degrowth_new_capacity'),
(model.limit_degrowth_new_capacity_delta, 'limit_degrowth_new_capacity_delta'),
]
manifest += [
LoadItem(
component=component,
table=table,
columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'],
index_length=3,
validator_name='viable_rt',
validation_map=(0, 1),
is_period_filtered=False,
is_table_required=False,
)
for component, table in growth_specs
]🤖 Prompt for AI Agents |
||
| LoadItem( | ||
| component=model.limit_resource, | ||
| table='limit_resource', | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.