/
tutorial.py
114 lines (94 loc) · 3.59 KB
/
tutorial.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import logging
from contextlib import contextmanager
from functools import partial
from message_ix import Scenario
from message_ix.report import Key, Reporter, computations
log = logging.getLogger(__name__)
PLOTS = [
("activity", computations.stacked_bar, "out:nl-t-ya", "GWa"),
("capacity", computations.stacked_bar, "CAP:nl-t-ya", "GW"),
("removal capacity", computations.stacked_bar, "CAP:nl-t-ya", "tCO2/yr"),
("demand", computations.stacked_bar, "demand:n-c-y", "GWa"),
("emission", computations.stacked_bar, "emi:nl-t-ya", "tCO2"),
("extraction", computations.stacked_bar, "EXT:n-c-g-y", "GW"),
("new capacity", computations.stacked_bar, "CAP_NEW:nl-t-yv", "GWa"),
("prices", computations.stacked_bar, "PRICE_COMMODITY:n-c-y", "¢/kW·h"),
]
def prepare_plots(rep: Reporter, input_costs="$/GWa") -> None:
"""Prepare `rep` to generate plots for tutorial energy models.
Makes available several keys:
- ``plot activity``
- ``plot demand``
- ``plot extraction``
- ``plot fossil supply curve``
- ``plot capacity``
- ``plot removal capacity``
- ``plot emission``
- ``plot new capacity``
- ``plot prices``
To control the contents of each plot, use :meth:`.set_filters` on `rep`.
"""
# Conversion factors between input units and plotting units
# TODO use exact units in all tutorials
# TODO allow the correct units to pass through reporting
cost_unit_conv = {
"$/GWa": 1.0,
"$/MWa": 1e3,
"$/kWa": 1e6,
}.get(input_costs, 1.0)
# Basic setup of the reporter
rep.configure(units={"replace": {"-": ""}})
# Add one node to the reporter for each plot
for title, func, key_str, units in PLOTS:
# Convert the string to a Key object so as to reference its .dims
key = Key.from_str_or_key(key_str)
# Operation for the reporter
comp = partial(
# The function to use, e.g. stacked_bar()
func,
# Other keyword arguments to the plotting function
dims=key.dims,
units=units,
title=f"Energy System {title.title()}",
cf=1.0 if title != "prices" else (cost_unit_conv * 100 / 8760),
stacked=title != "prices",
)
# Add the computation under a key like "plot activity"
rep.add(f"plot {title}", (comp, key))
rep.add(
"plot fossil supply curve",
(
partial(
computations.plot_cumulative,
labels=("Fossil supply", "Resource volume", "Cost"),
),
"resource_volume:n-g",
"resource_cost:n-g-y",
),
)
@contextmanager
def solve_modified(base: Scenario, new_name: str):
"""Context manager for a cloned scenario.
At the end of the block, the modified Scenario yielded by :func:`solve_modified` is
committed, set as default, and solved. Use in a ``with:`` statement to make small
modifications and leave a variable in the current scope with the solved scenario.
Examples
--------
>>> with solve_modified(base_scen, "new name") as s:
... s.add_par( ... ) # Modify the scenario
... # `s` is solved at the end of the block
Yields
------
.Scenario
Cloned from `base`, with the scenario name `new_name` and no solution.
"""
s = base.clone(
scenario=new_name,
annotation=f"Cloned by solve_modified() from {repr(base.scenario)}",
keep_solution=False,
)
s.check_out()
yield s
s.commit("Commit by solve_modified() at end of 'with:' statement")
s.set_as_default()
s.solve()