# temoa/components/emissions.py
"""
Defines the components of the Temoa model related to emissions accounting.
This module is responsible for:
- Defining index sets for emission-related parameters and constraints.
- Defining the constraint rule for 'linked technologies', a special case where
an emission commodity (e.g., captured CO2) is also treated as a physical
input to a downstream process (e.g., synthetic fuel production).
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from pyomo.core import quicksum
from pyomo.environ import value
if TYPE_CHECKING:
from temoa.core.model import TemoaModel
from temoa.types import ExprLike
from temoa.types.core_types import (
Commodity,
Period,
Region,
Season,
Technology,
TimeOfDay,
Vintage,
)
# ============================================================================
# PYOMO INDEX SET FUNCTIONS
# ============================================================================
def emission_activity_indices(
model: TemoaModel,
) -> set[tuple[Region, Commodity, Commodity, Technology, Vintage, Commodity]]:
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
}
def linked_tech_constraint_indices(
model: TemoaModel,
) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, Commodity]]:
return {
(r, p, s, d, t, v, e)
for r, t, e in model.linked_techs.sparse_keys()
for p in model.time_optimize
if (r, p, t) in model.process_vintages
for v in model.process_vintages[r, p, t]
if model.active_activity_rptv and (r, p, t, v) in model.active_activity_rptv
for s in model.time_season
for d in model.time_of_day
}
# ============================================================================
# PYOMO CONSTRAINT RULES
# ============================================================================
[docs]
def linked_emissions_tech_constraint(
model: TemoaModel,
r: Region,
p: Period,
s: Season,
d: TimeOfDay,
t: Technology,
v: Vintage,
e: Commodity,
) -> ExprLike:
r"""
This constraint can be used for carbon capture technologies that produce
CO2 as an emissions commodity, but the CO2 also serves as a physical
input commodity to a downstream process, such as synthetic fuel production.
To accomplish this, a dummy technology is linked to the CO2-producing
technology, converting the emissions activity into a physical commodity
amount as follows:
.. math::
:label: linked_emissions_tech
- \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} \cdot EAC_{r, e, i, t, v, o}
= \sum_{I, O} \textbf{FO}_{r, p, s, d, i, \hat{t}, v, o}
\forall \{r, p, s, d, t, v, e\} \in \Theta_{\text{linked\_techs}}
where :math:`\hat{t} = LT_{r,t,e}` is the linked technology for primary technology
:math:`t` and emissions commodity :math:`e`. For annual technologies
(:math:`t \in T^a` or :math:`\hat{t} \in T^a`), the corresponding
:math:`\textbf{FOA}` variable scaled by :math:`DSD` (for end use demand techs)
or :math:`SEG` (for all other annual techs) is used instead.
The relationship between the primary and linked technologies is given
in the :code:`linked_techs` table. It is implicit that
the primary region corresponds to the linked technology as well. The lifetimes
of the primary and linked technologies should be specified and identical.
"""
if t in model.tech_annual:
primary_flow = quicksum(
(
value(model.demand_specific_distribution[r, s, d, S_o])
if S_o in model.commodity_demand
else value(model.segment_fraction[s, d])
)
* model.v_flow_out_annual[r, p, S_i, t, v, S_o]
* value(model.emission_activity[r, e, S_i, t, v, S_o])
for S_i in model.process_inputs[r, p, t, v]
for S_o in model.process_outputs_by_input[r, p, t, v, S_i]
)
else:
primary_flow = quicksum(
model.v_flow_out[r, p, s, d, S_i, t, v, S_o]
* value(model.emission_activity[r, e, S_i, t, v, S_o])
for S_i in model.process_inputs[r, p, t, v]
for S_o in model.process_outputs_by_input[r, p, t, v, S_i]
)
linked_t = value(model.linked_techs[r, t, e])
# linked_flow = sum(
# M.v_flow_out[r, p, s, d, S_i, linked_t, v, S_o]
# for S_i in M.processInputs[r, p, linked_t, v]
# for S_o in M.process_outputs_by_input[r, p, linked_t, v, S_i]
# )
if linked_t in model.tech_annual:
linked_flow = quicksum(
(
value(model.demand_specific_distribution[r, s, d, S_o])
if S_o in model.commodity_demand
else value(model.segment_fraction[s, d])
)
* model.v_flow_out_annual[r, p, S_i, linked_t, v, S_o]
for S_i in model.process_inputs[r, p, linked_t, v]
for S_o in model.process_outputs_by_input[r, p, linked_t, v, S_i]
)
else:
linked_flow = quicksum(
model.v_flow_out[r, p, s, d, S_i, linked_t, v, S_o]
for S_i in model.process_inputs[r, p, linked_t, v]
for S_o in model.process_outputs_by_input[r, p, linked_t, v, S_i]
)
return -primary_flow == linked_flow