Skip to content

Commit 1f32d58

Browse files
authored
Merge pull request #808 from daymontas1/macro_update
Add an option to solve MACRO concurrently for all regions
2 parents 6ee45ca + 842067e commit 1f32d58

File tree

9 files changed

+105
-33
lines changed

9 files changed

+105
-33
lines changed

RELEASE_NOTES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ All changes
3030

3131
- Some MESSAGEix :doc:`tutorials <tutorials>` are runnable with the :class:`.IXMP4Backend` introduced in :mod:`ixmp` version 3.11 (:pull:`894`, :pull:`941`).
3232
See `Support roadmap for ixmp4 <https://github.com/iiasa/message_ix/discussions/939>`__ for details.
33+
- Add the :py:`concurrent=...` model option to :class:`.MACRO` (:pull:`808`).
3334
- Adjust use of :ref:`type_tec <mapping-sets>` in :ref:`equation_emission_equivalence` (:pull:`930`, :issue:`929`, :pull:`935`).
3435

3536
This change reduces the size of the ``EMISS`` variable,

doc/api.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ Model classes
260260
:exclude-members: items
261261
:show-inheritance:
262262

263+
The MACRO class solves only the MACRO model in “standalone” mode—that is, without MESSAGE.
264+
It is also invoked from :class:`.MESSAGE_MACRO` to process *model_options* to control the behaviour of MACRO:
265+
266+
- **concurrent** (:class:`int` or :class:`float`, either :py:`0` or :py:`1`).
267+
This corresponds to the GAMS compile-time variable ``MACRO_CONCURRENT``.
268+
If set to :py:`0` (the default), MACRO is solved in a loop, once for each node in the Scenario.
269+
If set to :py:`1`, MACRO is solved only once, for all nodes simultaneously.
270+
263271
.. autoattribute:: items
264272
:no-value:
265273

message_ix/model/MACRO/macro_solve.gms

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,40 @@ C.FX(node_macro, macro_base_period) = c0(node_macro) ;
5555
I.FX(node_macro, macro_base_period) = i0(node_macro) ;
5656
EC.FX(node_macro, macro_base_period) = y0(node_macro) - i0(node_macro) - c0(node_macro) ;
5757

58-
* ------------------------------------------------------------------------------
59-
* solving the model region by region
60-
* ------------------------------------------------------------------------------
58+
$IFTHEN %MACRO_CONCURRENT% == "0"
6159

62-
node_active(node) = no ;
60+
DISPLAY "Solve MACRO for each node in sequence";
6361

64-
LOOP(node $ node_macro(node),
62+
node_active(node) = NO ;
6563

66-
node_active(node_macro) = no ;
67-
node_active(node) = YES;
68-
* DISPLAY node_active ;
64+
LOOP(node$node_macro(node),
65+
node_active(node_macro) = NO ;
66+
node_active(node) = YES ;
67+
* DISPLAY node_active ;
6968

70-
* ------------------------------------------------------------------------------
71-
* solve statement
72-
* ------------------------------------------------------------------------------
69+
SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP ;
70+
71+
* Write model status summary for the current node
72+
* status(node,'modelstat') = MESSAGE_MACRO.modelstat ;
73+
* status(node,'solvestat') = MESSAGE_MACRO.solvestat ;
74+
* status(node,'resUsd') = MESSAGE_MACRO.resUsd ;
75+
* status(node,'objEst') = MESSAGE_MACRO.objEst ;
76+
* status(node,'objVal') = MESSAGE_MACRO.objVal ;
77+
);
78+
79+
$ELSE
80+
81+
DISPLAY "Solve MACRO for all nodes concurrently";
82+
83+
node_active(node_macro) = YES;
7384

74-
SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP ;
85+
SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP;
7586

76-
* write model status summary (by node)
77-
* status(node,'modelstat') = MESSAGE_MACRO.modelstat ;
78-
* status(node,'solvestat') = MESSAGE_MACRO.solvestat ;
79-
* status(node,'resUsd') = MESSAGE_MACRO.resUsd ;
80-
* status(node,'objEst') = MESSAGE_MACRO.objEst ;
81-
* status(node,'objVal') = MESSAGE_MACRO.objVal ;
87+
* Write model status summary for all nodes
88+
* status('all','modelstat') = MESSAGE_MACRO.modelstat;
89+
* status('all','solvestat') = MESSAGE_MACRO.solvestat;
90+
* status('all','resUsd') = MESSAGE_MACRO.resUsd;
91+
* status('all','objEst') = MESSAGE_MACRO.objEst;
92+
* status('all','objVal') = MESSAGE_MACRO.objVal;
8293

83-
) ;
94+
$ENDIF

message_ix/model/MACRO/setup.gms

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
* A scenario name is mandatory to load the gdx file - abort the run if not specified or file does not exist
2+
$IF NOT SET in $ABORT "no input data file provided!"
3+
$IF NOT EXIST '%in%' $ABORT "input GDX file '%in%' does not exist!"
4+
$IF NOT SET out $SETGLOBAL out "output/MsgOutput.gdx"
5+
6+
* MACRO mode. This can take 3 possible values:
7+
*
8+
* - "none": MACRO is not run, MESSAGE is run in stand-alone mode.
9+
* - "linked": MESSAGE and MACRO are run in linked/iterative mode.
10+
* This value is set in MESSAGE-MACRO_run.gms.
11+
* - "standalone": MACRO is run without MESSAGE.
12+
* This value is set in MACRO_run.gms
13+
$IF NOT SET macromode $ABORT "The global setting/command line option --macromode must be set"
14+
15+
* Option to solve MACRO either…
16+
*
17+
* - 0: in sequence
18+
* - 1: in parallel
19+
*
20+
* See macro_solve.gms
21+
$IF NOT SET MACRO_CONCURRENT $SETGLOBAL MACRO_CONCURRENT "0"

message_ix/model/MACRO_run.gms

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
* a scenario name is mandatory to load the gdx file - abort the run if not specified or file does not exist
2-
$IF NOT SET in $ABORT "no input data file provided!"
3-
$IF NOT EXIST '%in%' $ABORT "input GDX file '%in%' does not exist!"
4-
5-
** option to run MACRO standalone or interactively linked (iterating) with MESSAGE **
6-
*$SETGLOBAL macromode "linked"
1+
* Run MACRO in stand-alone mode, without MESSAGE.
2+
* To run coupled with MESSAGE, use MESSAGE-MACRO_run.gms instead of this file.
73
$SETGLOBAL macromode "standalone"
84

5+
$INCLUDE MACRO/setup.gms
96
$INCLUDE MACRO/macro_data_load.gms
107
$INCLUDE MACRO/macro_core.gms
118
$INCLUDE MACRO/macro_calibration.gms
129
$INCLUDE MACRO/macro_reporting.gms
1310

1411
* dump all input data, processed data and results to a gdx file (with additional comment as name extension if provided)
15-
$IF NOT SET out $SETGLOBAL out "output/MsgOutput.gdx"
1612
execute_unload "%out%"
17-

message_ix/model/MESSAGE-MACRO_run.gms

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ $INCLUDE includes/copyright.gms
2626
* (or ``model\output\MsgOutput.gdx`` if ``--out`` is not provided).
2727
***
2828

29+
* Run MESSAGE and MACRO in linked/iterative mode mode.
30+
* To run MACRO alone, use MACRO_run.gms instead of this file.
31+
* To run MESSAGE alone, use MESSAGE_run.gms or MESSAGE_master.gms instead of this file.
2932
$SETGLOBAL macromode "linked"
3033
$EOLCOM #
3134
$INCLUDE MESSAGE/model_setup.gms
@@ -34,6 +37,7 @@ $INCLUDE MESSAGE/model_setup.gms
3437
* load additional equations and parameters for MACRO *
3538
*----------------------------------------------------------------------------------------------------------------------*
3639

40+
$INCLUDE MACRO/setup.gms
3741
$INCLUDE MACRO/macro_data_load.gms
3842
$INCLUDE MACRO/macro_core.gms
3943

message_ix/model/MESSAGE_master.gms

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ $ONGLOBAL
1212
** scenario/case selection - this must match the name of the MsgData_<%%%>.gdx input data file **
1313
$SETGLOBAL data "<your datafile name here>"
1414

15-
** MACRO mode
16-
* "none": MESSAGEix is run in stand-alone mode
17-
* "linked": MESSAGEix-MACRO is run in iterative mode **
18-
$SETGLOBAL macromode "none"
15+
* MACRO mode. This can take 3 possible values, only 2 of which are usable with this file:
16+
*
17+
* - "none": MACRO is not run, MESSAGE is run in stand-alone mode.
18+
* - "linked": MESSAGE and MACRO are run in linked/iterative mode.
19+
* - "standalone": MACRO is run without MESSAGE. Not valid when using this file; use MACRO_run.gms instead.
20+
$IF NOT SET macromode $SETGLOBAL macromode "none"
1921

2022
** define the time horizon over which the model optimizes (perfect foresight, myopic or rolling horizon) **
2123
* perfect foresight - 0

message_ix/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,21 @@ def __init__(self, *args, **kwargs):
10851085
f"{self.name} requires GAMS >= {self.GAMS_min_version}; found {version}"
10861086
)
10871087

1088+
# Additional command-line arguments to GAMS
1089+
solve_args = []
1090+
try:
1091+
concurrent = str(kwargs.pop("concurrent"))
1092+
except KeyError:
1093+
pass
1094+
else:
1095+
if concurrent not in ("0", "1"):
1096+
raise ValueError(f"{concurrent = }")
1097+
solve_args.append(f"--MACRO_CONCURRENT={concurrent}")
1098+
10881099
super().__init__(*args, **kwargs)
10891100

1101+
self.solve_args.extend(solve_args)
1102+
10901103
@classmethod
10911104
def initialize(cls, scenario, with_data=False):
10921105
"""Initialize the model structure."""

message_ix/tests/test_macro.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,29 @@ def test_calibrate(westeros_solved: Scenario, w_data_path: Path) -> None:
315315
assert not end_grow.isnull().any()
316316

317317

318-
def test_calibrate_roundtrip(westeros_solved: Scenario, w_data_path: Path) -> None:
318+
@pytest.mark.parametrize(
319+
"kwargs",
320+
(
321+
{}, # Default concurrent=0
322+
dict(concurrent=0), # Explicit value, same as default
323+
dict(concurrent=1),
324+
pytest.param(
325+
dict(concurrent=2),
326+
marks=pytest.mark.xfail(raises=ValueError, reason="Invalid value"),
327+
),
328+
),
329+
)
330+
def test_calibrate_roundtrip(
331+
westeros_solved: Scenario, w_data_path: Path, kwargs
332+
) -> None:
319333
"""Ensure certain values occur after checking convergence.
320334
321335
The specific values used here were re-checked in :pull:`924`.
322336
"""
323-
with_macro = westeros_solved.add_macro(w_data_path, check_convergence=True)
337+
# this is a regression test with values observed on May 23, 2024
338+
with_macro = westeros_solved.add_macro(
339+
w_data_path, check_convergence=True, **kwargs
340+
)
324341
npt.assert_allclose(
325342
with_macro.par("aeei")["value"].values,
326343
1e-3 * np.array([20.0, -7.5674349, 43.659505, 21.182828]),

0 commit comments

Comments
 (0)