Skip to content
Open
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
4 changes: 4 additions & 0 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Adjust any imports like the following:
All changes
-----------

- Add subannual generic relation constraints to MESSAGE (:pull:`NNNN`).
New parameters ``relation_upper_time``, ``relation_lower_time``, ``relation_activity_time``
bound ``REL_TIME(r, n, y, h)`` per time slice, complementing the annual ``relation_*`` parameters.

- :mod:`message_ix` is tested and compatible with `Python 3.14 <https://www.python.org/downloads/release/python-3140/>`__ (:pull:`985`).
- Support for Python 3.9 is dropped (:pull:`985`), as it has reached end-of-life.
- :mod:`message_ix` is tested and compatible with `Pandas 3.0.0 <https://pandas.pydata.org/pandas-docs/stable/whatsnew/v3.0.0.html>`_,
Expand Down
21 changes: 20 additions & 1 deletion message_ix/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,16 @@ def enforce(scenario: "ixmp.Scenario") -> None:
# handled in JDBCBackend. For the moment, this code does not backstop that
# behaviour.
# TODO Extend to handle all masks, e.g. for new backends.
for par_name in ("capacity_factor",):
#
# The JDBC backend composes ``is_relation_*`` for the annual parameters but
# not the subannual ``_time`` variants, so their flag sets are composed here.
# Composition is key-based, so a zero-valued bound (``relation_upper_time = 0``,
# i.e. "REL_TIME <= 0") is preserved.
for par_name in (
"capacity_factor",
"relation_lower_time",
"relation_upper_time",
):
# Name of the corresponding set
set_name = f"is_{par_name}"

Expand Down Expand Up @@ -296,6 +305,8 @@ def run(self, scenario: "ixmp.Scenario") -> None:
_set("cat_tec", "type_tec t")
_set("cat_year", "type_year y")
_set("is_capacity_factor", "nl t yv ya h")
_set("is_relation_lower_time", "r nr yr h")
_set("is_relation_upper_time", "r nr yr h")
_set("level_renewable", "l")
_set("level_resource", "l")
_set("level_stocks", "l")
Expand Down Expand Up @@ -397,11 +408,14 @@ def run(self, scenario: "ixmp.Scenario") -> None:
par("ref_new_capacity", "nl t yv")
par("ref_relation", "r nr yr")
par("relation_activity", "r nr yr nl t ya m")
par("relation_activity_time", "r nr yr nl t ya m h")
par("relation_cost", "r nr yr")
par("relation_lower", "r nr yr")
par("relation_lower_time", "r nr yr h")
par("relation_new_capacity", "r nr yr t")
par("relation_total_capacity", "r nr yr t")
par("relation_upper", "r nr yr")
par("relation_upper_time", "r nr yr h")
par("reliability_factor", "n t ya c l h q")
par("renewable_capacity_factor", "n c g l y")
par("renewable_potential", "n c g l y")
Expand Down Expand Up @@ -497,6 +511,11 @@ def run(self, scenario: "ixmp.Scenario") -> None:
"r nr yr",
"Auxiliary variable for left-hand side of user-defined relations",
)
var(
"REL_TIME",
"r nr yr h",
"Auxiliary variable for left-hand side of user-defined subannual relations",
)
var(
"REN",
"n t c g y h",
Expand Down
4 changes: 4 additions & 0 deletions message_ix/model/MESSAGE/data_load.gms
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ $LOAD level_resource, level_renewable
$LOAD lvl_spatial, lvl_temporal, map_spatial_hierarchy, map_temporal_hierarchy
$LOAD map_node, map_time, map_commodity, map_resource, map_stocks, map_tec, map_tec_time, map_tec_mode
$LOAD is_capacity_factor
$LOAD is_relation_upper_time, is_relation_lower_time
$LOAD map_land, map_relation
$LOAD type_tec, cat_tec, type_year, cat_year, type_emission, cat_emission, type_tec_land
$LOAD inv_tec, renewable_tec
Expand Down Expand Up @@ -125,11 +126,14 @@ Execute_load '%in%',
peak_load_factor,
rating_bin,
relation_activity,
relation_activity_time,
relation_cost,
relation_lower,
relation_lower_time,
relation_new_capacity,
relation_total_capacity,
relation_upper,
relation_upper_time,
reliability_factor,
renewable_capacity_factor,
renewable_potential,
Expand Down
66 changes: 66 additions & 0 deletions message_ix/model/MESSAGE/model_core.gms
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Variables
EMISS(node,emission,type_tec,year_all) aggregate emissions by technology type and land-use model emulator
* auxiliary variable for left-hand side of relations (linear constraints)
REL(relation,node,year_all) auxiliary variable for left-hand side of user-defined relations
* auxiliary variable for left-hand side of subannual relations (linear constraints at time-slice resolution)
REL_TIME(relation,node,year_all,time) auxiliary variable for left-hand side of user-defined subannual relations
* change in the content of storage device
STORAGE_CHARGE(node,tec,mode,level,commodity,year_all,time) charging of storage in each time slice (negative for discharge)
;
Expand Down Expand Up @@ -307,6 +309,9 @@ Equations
RELATION_EQUIVALENCE auxiliary equation to simplify the implementation of relations
RELATION_CONSTRAINT_UP upper bound of relations (linear constraints)
RELATION_CONSTRAINT_LO lower bound of relations (linear constraints)
RELATION_EQUIVALENCE_TIME auxiliary equation to simplify the implementation of subannual relations
RELATION_CONSTRAINT_UP_TIME upper bound of subannual relations (linear constraints at time-slice resolution)
RELATION_CONSTRAINT_LO_TIME lower bound of subannual relations (linear constraints at time-slice resolution)
STORAGE_CHANGE change in the state of charge of storage
STORAGE_BALANCE balance of the state of charge of storage
STORAGE_BALANCE_INIT balance of the state of charge of storage at sub-annual time slices with initial storage content
Expand Down Expand Up @@ -2331,6 +2336,67 @@ RELATION_CONSTRAINT_LO(relation,node,year)$( is_relation_lower(relation,node,yea
%SLACK_RELATION_BOUND_LO% + SLACK_RELATION_BOUND_LO(relation,node,year)
=G= relation_lower(relation,node,year) ;

***
* Subannual generic relations
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* The subannual generic relations mirror :ref:`equation_relation_equivalence`, :ref:`equation_relation_constraint_up` and :ref:`equation_relation_constraint_lo` at subannual time-slice resolution.
* They share the annual-relation convention:
* generic linear constraints intended for development and testing, where specific features should use purpose-built equations.
* The subannual variants are activated when scenarios populate ``relation_activity_time``, ``relation_upper_time`` or ``relation_lower_time``;
* scenarios without these parameters are unaffected.
*
* .. _equation_relation_equivalence_time:
*
* Equation RELATION_EQUIVALENCE_TIME
* """"""""""""""""""""""""""""""""""
* .. math::
* \text{REL\_TIME}_{r,n,y,h} = \sum_{t,n^L,y',m} \ \text{relation\_activity\_time}_{r,n,y,n^L,t,y',m,h} \\
* \cdot \Big( \sum_{y^V \leq y'} \text{ACT}_{n^L,t,y^V,y',m,h}
* + \text{historical\_activity}_{n^L,t,y',m,h} \Big)
*
* ``REL_TIME`` is the subannual analogue of ``REL``.
* The subannual relation carries only the activity term:
* the capacity-side factors :math:`\text{relation\_new\_capacity}` and :math:`\text{relation\_total\_capacity}` stay annual,
* because capacity itself has no subannual dimension in |MESSAGEix|.
***

RELATION_EQUIVALENCE_TIME(relation,node,year,time)..
REL_TIME(relation,node,year,time)
=E=
SUM(tec,
SUM((location,year_all2,mode)$( map_tec_act(location,tec,year_all2,mode,time) ),
relation_activity_time(relation,node,year,location,tec,year_all2,mode,time)
* ( SUM(vintage$( map_tec_lifetime(location,tec,vintage,year_all2) ),
ACT(location,tec,vintage,year_all2,mode,time) )
+ historical_activity(location,tec,year_all2,mode,time) )
)
) ;

***
* .. _equation_relation_constraint_up_time:
*
* Equation RELATION_CONSTRAINT_UP_TIME
* """"""""""""""""""""""""""""""""""""
* .. math::
* \text{REL\_TIME}_{r,n,y,h} \leq \text{relation\_upper\_time}_{r,n,y,h}
***
RELATION_CONSTRAINT_UP_TIME(relation,node,year,time)$( is_relation_upper_time(relation,node,year,time) )..
REL_TIME(relation,node,year,time)
=L= relation_upper_time(relation,node,year,time) ;

***
* .. _equation_relation_constraint_lo_time:
*
* Equation RELATION_CONSTRAINT_LO_TIME
* """"""""""""""""""""""""""""""""""""
* .. math::
* \text{REL\_TIME}_{r,n,y,h} \geq \text{relation\_lower\_time}_{r,n,y,h}
***
RELATION_CONSTRAINT_LO_TIME(relation,node,year,time)$( is_relation_lower_time(relation,node,year,time) )..
REL_TIME(relation,node,year,time)
=G= relation_lower_time(relation,node,year,time) ;

*----------------------------------------------------------------------------------------------------------------------*
***
* .. _gams-storage:
Expand Down
15 changes: 15 additions & 0 deletions message_ix/model/MESSAGE/parameter_def.gms
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,18 @@ Parameters
* - ``relation`` | ``node_rel`` | ``year_rel`` | ``tec``
* * - relation_activity
* - ``relation`` | ``node_rel`` | ``year_rel`` | ``node_loc`` | ``tec`` | ``year_act`` | ``mode``
* * - relation_upper_time
* - ``relation`` | ``node_rel`` | ``year_rel`` | ``time``
* * - relation_lower_time
* - ``relation`` | ``node_rel`` | ``year_rel`` | ``time``
* * - relation_activity_time
* - ``relation`` | ``node_rel`` | ``year_rel`` | ``node_loc`` | ``tec`` | ``year_act`` | ``mode`` | ``time``
*
* The ``_time`` variants extend the generic-relation mechanism to subannual time slices.
* They are independent of the annual variants:
* an annual ``relation_upper(r,n,y)`` constrains :math:`\text{REL}_{r,n,y}` summed over all time,
* while ``relation_upper_time(r,n,y,h)`` constrains the per-slice :math:`\text{REL\_TIME}_{r,n,y,h}`.
* Scenarios may use either or both.
*
***

Expand All @@ -877,6 +889,9 @@ Parameters
relation_new_capacity(relation,node,year_all,tec) new capacity factor (multiplier) of generic relation
relation_total_capacity(relation,node,year_all,tec) total capacity factor (multiplier) of generic relation
relation_activity(relation,node,year_all,node,tec,year_all,mode) activity factor (multiplier) of generic relation
relation_upper_time(relation,node,year_all,time) upper bound of generic relation at subannual time slice
relation_lower_time(relation,node,year_all,time) lower bound of generic relation at subannual time slice
relation_activity_time(relation,node,year_all,node,tec,year_all,mode,time) activity factor (multiplier) of generic relation at subannual time slice
;

*----------------------------------------------------------------------------------------------------------------------*
Expand Down
2 changes: 2 additions & 0 deletions message_ix/model/MESSAGE/sets_maps_def.gms
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@ Sets

is_relation_upper(relation,node,year_all) flag whether upper bounds exists for generic relation
is_relation_lower(relation,node,year_all) flag whether lower bounds exists for generic relation
is_relation_upper_time(relation,node,year_all,time) flag whether upper bound exists for generic relation at subannual time slice
is_relation_lower_time(relation,node,year_all,time) flag whether lower bound exists for generic relation at subannual time slice
;

*----------------------------------------------------------------------------------------------------------------------*
Expand Down
Loading
Loading