Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 32 additions & 24 deletions temoa/components/capacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,11 @@ def annual_retirement_constraint(
\\\text{where EOL when } p \leq v + LTP_{r,t,v} < p + LEN_p
"""

p_end = p + value(model.period_length[p])
eol_year = v + value(model.lifetime_process[r, t, v])

## Get the capacity at the start of this period
if p == v + value(model.lifetime_process[r, t, v]):
if p == eol_year:
# Exact EOL. No v_capacity or v_retired_capacity for this period.
if p == model.time_optimize.first():
# Must be existing capacity. Apply survival curve to existing cap
Expand All @@ -307,30 +310,35 @@ def annual_retirement_constraint(
)

## Get the capacity at the end of this period
if p <= v + value(model.lifetime_process[r, t, v]) < p + value(model.period_length[p]):
# EOL so capacity ends on zero
if p <= eol_year < p_end:
# EOL so capacity ends on zero no matter what
cap_end = 0
elif p == model.time_optimize.last() or p_end == eol_year:
# No v_capacity or v_retired_capacity for next period so just continue down the
# survival curve. If eol_year = p_end then eol would be dumped in the next period
cap_end = (
cap_begin
* value(model.lifetime_survival_curve[r, p_end, t, v])
/ value(model.lifetime_survival_curve[r, p, t, v])
)
else:
# Mid-life period, ending capacity is beginning capacity of next period
p_next = model.time_future.next(p)

if p == model.time_optimize.last() or p_next == v + value(model.lifetime_process[r, t, v]):
# No v_capacity or v_retired_capacity for next period so just continue down the
# survival curve
cap_end = (
cap_begin
* value(model.lifetime_survival_curve[r, p_next, t, v])
/ value(model.lifetime_survival_curve[r, p, t, v])
)
else:
# Get the next period's beginning capacity
cap_end = (
model.v_capacity[r, p_next, t, v]
* value(model.lifetime_survival_curve[r, p_next, t, v])
/ value(model.process_life_frac[r, p_next, t, v])
)
# Get the next period's beginning capacity
p_next = model.time_optimize.next(p)
cap_end = (
model.v_capacity[r, p_next, t, v]
* value(model.lifetime_survival_curve[r, p_next, t, v])
/ value(model.process_life_frac[r, p_next, t, v])
)
# next v_capacity also accounts for decision retirement so need to undo that again
p_next_end = p_next + value(model.period_length[p_next])
if t in model.tech_retirement and p_next_end <= eol_year:
cap_end += model.v_retired_capacity[r, p_next, t, v]

# v_capacity already accounts for decision retirement so need to undo that for beginning cap
if t in model.tech_retirement and p_end <= eol_year:
cap_begin += model.v_retired_capacity[r, p, t, v]

annualised_retirement = (cap_begin - cap_end) / model.period_length[p]
annualised_retirement = (cap_begin - cap_end) / value(model.period_length[p])
return model.v_annual_retirement[r, p, t, v] == annualised_retirement


Expand Down Expand Up @@ -546,10 +554,10 @@ def adjusted_capacity_constraint(
if t in model.tech_retirement:
early_retirements = sum(
model.v_retired_capacity[r, S_p, t, v]
/ value(model.lifetime_survival_curve[r, S_p, t, v])
/ value(model.lifetime_survival_curve[r, S_p, t, v]) # relative survival since then
for S_p in model.time_optimize
if v < S_p <= p
and S_p < v + value(model.lifetime_process[r, t, v]) - value(model.period_length[S_p])
and S_p + value(model.period_length[S_p]) <= v + value(model.lifetime_process[r, t, v])
)

remaining_capacity = (built_capacity - early_retirements) * value(
Expand Down
4 changes: 2 additions & 2 deletions temoa/components/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from __future__ import annotations

from enum import Enum
from enum import StrEnum
from logging import getLogger
from typing import TYPE_CHECKING

Expand All @@ -33,7 +33,7 @@
logger = getLogger(__name__)


class Operator(str, Enum):
class Operator(StrEnum):
EQUAL = 'e'
LESS_EQUAL = 'le'
GREATER_EQUAL = 'ge'
Expand Down
24 changes: 10 additions & 14 deletions temoa/data_io/component_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,9 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.cost_invest,
table='cost_invest',
columns=['region', 'tech', 'vintage', 'cost'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 1, 2),
custom_loader_name='_load_cost_invest',
is_period_filtered=False, # Custom loader handles this
is_period_filtered=False,
is_table_required=False,
),
LoadItem(
Expand All @@ -322,10 +321,9 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.loan_rate,
table='loan_rate',
columns=['region', 'tech', 'vintage', 'rate'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 1, 2),
custom_loader_name='_load_loan_rate',
is_period_filtered=False, # Custom loader handles this
is_period_filtered=False,
is_table_required=False,
),
# =========================================================================
Expand Down Expand Up @@ -448,7 +446,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.loan_lifetime_process,
table='loan_lifetime_process',
columns=['region', 'tech', 'vintage', 'lifetime'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 1, 2),
is_period_filtered=False,
is_table_required=False,
Expand Down Expand Up @@ -566,9 +564,8 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.emission_embodied,
table='emission_embodied',
columns=['region', 'emis_comm', 'tech', 'vintage', 'value'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 2, 3),
custom_loader_name='_load_emission_embodied',
is_period_filtered=False,
is_table_required=False,
),
Expand All @@ -585,9 +582,8 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.construction_input,
table='construction_input',
columns=['region', 'input_comm', 'tech', 'vintage', 'value'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 2, 3),
custom_loader_name='_load_construction_input',
is_period_filtered=False,
is_table_required=False,
),
Expand Down Expand Up @@ -615,7 +611,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.limit_new_capacity,
table='limit_new_capacity',
columns=['region', 'tech_or_group', 'vintage', 'operator', 'new_cap'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 1, 2),
is_period_filtered=False,
is_table_required=False,
Expand All @@ -632,7 +628,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
component=model.limit_new_capacity_share,
table='limit_new_capacity_share',
columns=['region', 'sub_group', 'super_group', 'vintage', 'operator', 'share'],
validator_name='viable_rtv',
validator_name='viable_rtv_new',
validation_map=(0, 1, 3),
is_period_filtered=False,
is_table_required=False,
Expand Down Expand Up @@ -665,7 +661,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]:
LoadItem(
component=model.limit_seasonal_capacity_factor,
table='limit_seasonal_capacity_factor',
columns=['region', 'season', 'tech', 'operator', 'factor'],
columns=['region', 'season', 'tech_or_group', 'operator', 'factor'],
validator_name='viable_rt',
validation_map=(0, 2),
is_period_filtered=False,
Expand Down
57 changes: 3 additions & 54 deletions temoa/data_io/hybrid_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ def __init__(self, db_connection: Connection, config: TemoaConfig) -> None:
self.viable_vintages: ViableSet | None = None
self.viable_ritvo: ViableSet | None = None
self.viable_rtvo: ViableSet | None = None
self.viable_rtv_new: ViableSet | None = None
self.viable_rpt: ViableSet | None = None
self.viable_rpto: ViableSet | None = None
self.viable_rtv: ViableSet | None = None
self.viable_rtv_eol: ViableSet | None = None
self.viable_rt: ViableSet | None = None
self.viable_rpit: ViableSet | None = None
self.viable_rtt: ViableSet | None = None
Expand Down Expand Up @@ -470,6 +472,7 @@ def _build_efficiency_dataset(
filts = self.manager.build_filters(tech_groups)
self.viable_ritvo = filts['ritvo']
self.viable_rtvo = filts['rtvo']
self.viable_rtv_new = filts['rtv_new']
self.viable_rpt = filts['rpt']
self.viable_rtv = filts['rtv']
self.viable_rt = filts['rt']
Expand Down Expand Up @@ -627,60 +630,6 @@ def _load_existing_capacity(
tech_exist_data = sorted({(row[1],) for row in rows_to_load})
self._load_component_data(data, model.tech_exist, tech_exist_data)

def _load_cost_invest(
self,
data: dict[str, object],
raw_data: Sequence[tuple[object, ...]],
filtered_data: Sequence[tuple[object, ...]],
) -> None:
"""Handles myopic period filtering for cost_invest."""
model = TemoaModel()
base_year = self.myopic_index.base_year if self.myopic_index else None
data_to_load = [
row for row in filtered_data if base_year is None or cast('int', row[2]) >= base_year
]
self._load_component_data(data, model.cost_invest, data_to_load)

def _load_loan_rate(
self,
data: dict[str, object],
raw_data: Sequence[tuple[object, ...]],
filtered_data: Sequence[tuple[object, ...]],
) -> None:
"""Handles myopic period filtering for loan_rate."""
model = TemoaModel()
mi = self.myopic_index
data_to_load = [row for row in filtered_data if not mi or row[2] >= mi.base_year] # type: ignore[operator]
self._load_component_data(data, model.loan_rate, data_to_load)

def _load_construction_input(
self,
data: dict[str, object],
raw_data: Sequence[tuple[object, ...]],
filtered_data: Sequence[tuple[object, ...]],
) -> None:
"""Handles myopic period filtering for construction_input."""
model = TemoaModel()
base_year = self.myopic_index.base_year if self.myopic_index else None
data_to_load = [
row for row in filtered_data if base_year is None or cast('int', row[3]) >= base_year
]
self._load_component_data(data, model.construction_input, data_to_load)

def _load_emission_embodied(
self,
data: dict[str, object],
raw_data: Sequence[tuple[object, ...]],
filtered_data: Sequence[tuple[object, ...]],
) -> None:
"""Handles myopic period filtering for emission_embodied."""
model = TemoaModel()
base_year = self.myopic_index.base_year if self.myopic_index else None
data_to_load = [
row for row in filtered_data if base_year is None or cast('int', row[3]) >= base_year
]
self._load_component_data(data, model.emission_embodied, data_to_load)

# --- Singleton and Configuration-based Components ---
def _load_global_discount_rate(
self,
Expand Down
20 changes: 19 additions & 1 deletion temoa/model_checking/commodity_network_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from collections import defaultdict
from collections.abc import Iterable
from logging import getLogger
from typing import Any
from typing import Any, cast

from temoa.core.config import TemoaConfig
from temoa.model_checking.commodity_graph import visualize_graph
Expand Down Expand Up @@ -143,6 +143,11 @@ def build_filters(self, tech_groups: defaultdict[str, set[str]]) -> dict[str, Vi
valid_elements['ic'].add(edge_tuple.input_comm)
valid_elements['oc'].add(edge_tuple.output_comm)

if cast('int', p) == cast('int', edge_tuple.vintage):
valid_elements['rtv_new'].add(
(edge_tuple.region, edge_tuple.tech, edge_tuple.vintage)
)

for tech_group in tech_groups.get(edge_tuple.tech, {}):
valid_elements['rtvo'].add(
(
Expand All @@ -162,6 +167,11 @@ def build_filters(self, tech_groups: defaultdict[str, set[str]]) -> dict[str, Vi
(edge_tuple.region, p, tech_group, edge_tuple.output_comm)
)

if cast('int', p) == cast('int', edge_tuple.vintage):
valid_elements['rtv_new'].add(
(edge_tuple.region, tech_group, edge_tuple.vintage)
)

# Good processes that we dont want in the network diagram
for r, p, t, v in self.orig_data.silent_rptv:
valid_elements['rpt'].add((r, p, t))
Expand All @@ -170,6 +180,9 @@ def build_filters(self, tech_groups: defaultdict[str, set[str]]) -> dict[str, Vi
valid_elements['t'].add(t)
valid_elements['v'].add(v)

if cast('int', p) == cast('int', v):
valid_elements['rtv_new'].add((r, t, v))

return {
'ritvo': ViableSet(
elements=valid_elements['ritvo'],
Expand All @@ -181,6 +194,11 @@ def build_filters(self, tech_groups: defaultdict[str, set[str]]) -> dict[str, Vi
exception_loc=0,
exception_vals=ViableSet.REGION_REGEXES,
),
'rtv_new': ViableSet(
elements=valid_elements['rtv_new'],
exception_loc=0,
exception_vals=ViableSet.REGION_REGEXES,
),
'rtv': ViableSet(
elements=valid_elements['rtv'],
exception_loc=0,
Expand Down
4 changes: 2 additions & 2 deletions temoa/types/solver_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

from collections.abc import Mapping
from enum import Enum
from enum import Enum, StrEnum
from typing import TYPE_CHECKING, Any, Protocol

if TYPE_CHECKING:
Expand All @@ -18,7 +18,7 @@
PyomoTerminationCondition = Any


class SolverStatusEnum(str, Enum):
class SolverStatusEnum(StrEnum):
"""
Enumeration of possible solver status values.

Expand Down
4 changes: 2 additions & 2 deletions temoa/types/validation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"""

from dataclasses import dataclass
from enum import Enum
from enum import StrEnum
from typing import Any


class ValidationSeverity(str, Enum):
class ValidationSeverity(StrEnum):
"""
Enumeration of validation message severity levels.

Expand Down
Loading