Skip to content
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

Add .tools.res_marg #277

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 9 additions & 5 deletions doc/api/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ General purpose modeling tools (:mod:`.tools`)
- Codes for retrieving data from specific data sources and adapting it for use with :mod:`message_ix_models`.
- Codes for modifying scenarios; although tools for building models should go in :mod:`message_ix_models.model`.

.. currentmodule:: message_ix_models.tools

On other pages:

- :doc:`tools-costs`

.. autosummary::
:toctree: _autosummary
:template: autosummary-module.rst
:recursive:

res_marg

On this page:

.. contents::
:local:
:backlinks: none

.. currentmodule:: message_ix_models.tools

.. automodule:: message_ix_models.tools
:members:

.. currentmodule:: message_ix_models.tools.exo_data

Exogenous data (:mod:`.tools.exo_data`)
Expand Down
1 change: 1 addition & 0 deletions message_ix_models/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ def _log_threads(k: int, n: int):
"message_ix_models.report.cli",
"message_ix_models.model.material.cli",
"message_ix_models.testing.cli",
"message_ix_models.tools.res_marg",
"message_ix_models.util.pooch",
]

Expand Down
26 changes: 26 additions & 0 deletions message_ix_models/tests/tools/test_res_marg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest

from message_ix_models.tools.res_marg import main


def test_cli(mix_models_cli) -> None:
"""Run :func:`.res_marg.main` via its command-line interface."""
command = [
"--model=model_name",
"--scenario=scenario_name",
"--version=123",
"res-marg",
]

# Fails: the model name, scenario name, and version do not exist
with pytest.raises(RuntimeError):
mix_models_cli.assert_exit_0(command)


@pytest.mark.xfail(reason="Function does not run on the snapshot")
def test_main(loaded_snapshot) -> None:
"""Run :func:`.res_marg.main` on the snapshot scenarios."""
scen = loaded_snapshot

Check warning on line 23 in message_ix_models/tests/tools/test_res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tests/tools/test_res_marg.py#L23

Added line #L23 was not covered by tests

# Function runs
main(scen, None)

Check warning on line 26 in message_ix_models/tests/tools/test_res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tests/tools/test_res_marg.py#L26

Added line #L26 was not covered by tests
114 changes: 114 additions & 0 deletions message_ix_models/tools/res_marg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Update the reserve margin.

:func:`main` can also be invoked using the CLI command
:program:`mix-models --url=… res-marg`.
"""

from typing import TYPE_CHECKING

import click

if TYPE_CHECKING:
from message_ix import Scenario

from message_ix_models import Context


def main(scen: "Scenario", contin: float = 0.2) -> None:
"""Update the reserve margin.

For a given scenario, regional reserve margin (=peak load factor) values are updated
based on the electricity demand in the industry and res/comm sector.

This is based on the approach described in Johnsonn et al. (2017):
DOI: https://doi.org/10.1016/j.eneco.2016.07.010 (see section 2.2.1. Firm capacity
requirement)

Parameters
----------
scen :
Scenario to which changes should be applied.
contin :
Backup capacity for contingency reasons as percentage of peak capacity (default
20%).
"""
demands = scen.par("demand")
demands = (

Check warning on line 36 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L35-L36

Added lines #L35 - L36 were not covered by tests
demands[demands.commodity.isin(["i_spec", "rc_spec"])]
.set_index(["node", "commodity", "year", "level", "time", "unit"])
.sort_index()
)
input_eff = (

Check warning on line 41 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L41

Added line #L41 was not covered by tests
scen.par("input", {"technology": ["elec_t_d"]})
.set_index(
[
"node_loc",
"year_act",
"year_vtg",
"commodity",
"level",
"mode",
"node_origin",
"technology",
"time",
"time_origin",
"unit",
]
)
.sort_index()
)

with scen.transact("Update reserve-margin constraint"):
for reg in demands.index.get_level_values("node").unique():
if "_GLB" in reg:
continue
for year in demands.index.get_level_values("year").unique():
rc_spec = float(

Check warning on line 66 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L61-L66

Added lines #L61 - L66 were not covered by tests
demands.loc[reg, "rc_spec", year, "useful", "year"].iloc[0].value
)
i_spec = float(

Check warning on line 69 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L69

Added line #L69 was not covered by tests
demands.loc[reg, "i_spec", year, "useful", "year"].iloc[0].value
)
inp = float(

Check warning on line 72 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L72

Added line #L72 was not covered by tests
input_eff.loc[
reg,
year,
year,
"electr",
"secondary",
"M1",
reg,
"elec_t_d",
"year",
"year",
]
.iloc[0]
.value
)
val = (

Check warning on line 88 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L88

Added line #L88 was not covered by tests
((i_spec * 1.0 + rc_spec * 2.0) / (i_spec + rc_spec))
* (1.0 + contin)
* inp
* -1.0
)
scen.add_par(

Check warning on line 94 in message_ix_models/tools/res_marg.py

View check run for this annotation

Codecov / codecov/patch

message_ix_models/tools/res_marg.py#L94

Added line #L94 was not covered by tests
"relation_activity",
{
"relation": ["res_marg"],
"node_rel": [reg],
"year_rel": [year],
"node_loc": [reg],
"technology": ["elec_t_d"],
"year_act": [year],
"mode": ["M1"],
"value": [val],
"unit": ["GWa"],
},
)


@click.command("res-marg")
@click.pass_obj
def cli(ctx: "Context"):
"""Reserve margin calculation."""
main(ctx.get_scenario())
Loading