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

Adding the option to solve concurrently for all regions in MACRO #808

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All changes
- Support for Python 3.8 is dropped (:pull:`881`), as it has reached end-of-life.
- Add :meth:`.Reporter.add_sankey` and :mod:`.tools.sankey` to create Sankey diagrams from solved scenarios (:pull:`770`).
The :file:`westeros_sankey.ipynb` :ref:`tutorial <tutorial-westeros>` shows how to use this feature.
- Add the :py:`concurrent=...` model option to :class:`.MACRO` (:pull:`808`).
- Add option to :func:`.util.copy_model` from a non-default location of model files (:pull:`877`).

.. _v3.9.0:
Expand Down
8 changes: 8 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ Model classes
:exclude-members: items
:show-inheritance:

The MACRO class solves only the MACRO model in “standalone” mode—that is, without MESSAGE.
It is also invoked from :class:`.MESSAGE_MACRO` to process *model_options* to control the behaviour of MACRO:

- **concurrent** (:class:`int` or :class:`float`, either :py:`0` or :py:`1`).
This corresponds to the GAMS compile-time variable ``MACRO_CONCURRENT``.
If set to :py:`0` (the default), MACRO is solved in a loop, once for each node in the Scenario.
If set to :py:`1`, MACRO is solved only once, for all nodes simultaneously.

.. autoattribute:: items
:no-value:

Expand Down
48 changes: 29 additions & 19 deletions message_ix/model/MACRO/macro_solve.gms
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,40 @@ C.FX(node_macro, macro_base_period) = c0(node_macro) ;
I.FX(node_macro, macro_base_period) = i0(node_macro) ;
EC.FX(node_macro, macro_base_period) = y0(node_macro) - i0(node_macro) - c0(node_macro) ;

* ------------------------------------------------------------------------------
* solving the model region by region
* ------------------------------------------------------------------------------
$IFTHEN %MACRO_CONCURRENT% == "0"

node_active(node) = no ;
DISPLAY "Solve MACRO for each node in sequence";

LOOP(node $ node_macro(node),
node_active(node) = NO ;

node_active(node_macro) = no ;
node_active(node) = YES;
* DISPLAY node_active ;
LOOP(node$node_macro(node),
node_active(node_macro) = NO ;
node_active(node) = YES ;
* DISPLAY node_active ;

* ------------------------------------------------------------------------------
* solve statement
* ------------------------------------------------------------------------------
SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP ;

* Write model status summary for the current node
* status(node,'modelstat') = MESSAGE_MACRO.modelstat ;
* status(node,'solvestat') = MESSAGE_MACRO.solvestat ;
* status(node,'resUsd') = MESSAGE_MACRO.resUsd ;
* status(node,'objEst') = MESSAGE_MACRO.objEst ;
* status(node,'objVal') = MESSAGE_MACRO.objVal ;
);

$ELSE

DISPLAY "Solve MACRO for all nodes concurrently";

SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP ;
node_active(node_macro) = YES;

* write model status summary (by node)
* status(node,'modelstat') = MESSAGE_MACRO.modelstat ;
* status(node,'solvestat') = MESSAGE_MACRO.solvestat ;
* status(node,'resUsd') = MESSAGE_MACRO.resUsd ;
* status(node,'objEst') = MESSAGE_MACRO.objEst ;
* status(node,'objVal') = MESSAGE_MACRO.objVal ;
SOLVE MESSAGE_MACRO MAXIMIZING UTILITY USING NLP;

) ;
* Write model status summary for all nodes
* status('all','modelstat') = MESSAGE_MACRO.modelstat;
* status('all','solvestat') = MESSAGE_MACRO.solvestat;
* status('all','resUsd') = MESSAGE_MACRO.resUsd;
* status('all','objEst') = MESSAGE_MACRO.objEst;
* status('all','objVal') = MESSAGE_MACRO.objVal;

$ENDIF
21 changes: 21 additions & 0 deletions message_ix/model/MACRO/setup.gms
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
* A scenario name is mandatory to load the gdx file - abort the run if not specified or file does not exist
$IF NOT SET in $ABORT "no input data file provided!"
$IF NOT EXIST '%in%' $ABORT "input GDX file '%in%' does not exist!"
$IF NOT SET out $SETGLOBAL out "output/MsgOutput.gdx"

* MACRO mode. This can take 3 possible values:
*
* - "none": MACRO is not run, MESSAGE is run in stand-alone mode.
* - "linked": MESSAGE and MACRO are run in linked/iterative mode.
* This value is set in MESSAGE-MACRO_run.gms.
* - "standalone": MACRO is run without MESSAGE.
* This value is set in MACRO_run.gms
$IF NOT SET macromode $ABORT "The global setting/command line option --macromode must be set"

* Option to solve MACRO either…
*
* - 0: in sequence
* - 1: in parallel
*
* See macro_solve.gms
$IF NOT SET MACRO_CONCURRENT $SETGLOBAL MACRO_CONCURRENT "0"
11 changes: 3 additions & 8 deletions message_ix/model/MACRO_run.gms
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
* a scenario name is mandatory to load the gdx file - abort the run if not specified or file does not exist
$IF NOT SET in $ABORT "no input data file provided!"
$IF NOT EXIST '%in%' $ABORT "input GDX file '%in%' does not exist!"

** option to run MACRO standalone or interactively linked (iterating) with MESSAGE **
*$SETGLOBAL macromode "linked"
* Run MACRO in stand-alone mode, without MESSAGE.
* To run coupled with MESSAGE, use MESSAGE-MACRO_run.gms instead of this file.
$SETGLOBAL macromode "standalone"

$INCLUDE MACRO/setup.gms
$INCLUDE MACRO/macro_data_load.gms
$INCLUDE MACRO/macro_core.gms
$INCLUDE MACRO/macro_calibration.gms
$INCLUDE MACRO/macro_reporting.gms

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

4 changes: 4 additions & 0 deletions message_ix/model/MESSAGE-MACRO_run.gms
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ $INCLUDE includes/copyright.gms
* (or ``model\output\MsgOutput.gdx`` if ``--out`` is not provided).
***

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

$INCLUDE MACRO/setup.gms
$INCLUDE MACRO/macro_data_load.gms
$INCLUDE MACRO/macro_core.gms

Expand Down
10 changes: 6 additions & 4 deletions message_ix/model/MESSAGE_master.gms
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ $ONGLOBAL
** scenario/case selection - this must match the name of the MsgData_<%%%>.gdx input data file **
$SETGLOBAL data "<your datafile name here>"

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

** define the time horizon over which the model optimizes (perfect foresight, myopic or rolling horizon) **
* perfect foresight - 0
Expand Down
13 changes: 13 additions & 0 deletions message_ix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,8 +912,21 @@
f"{self.name} requires GAMS >= {self.GAMS_min_version}; found {version}"
)

# Additional command-line arguments to GAMS
solve_args = []
try:
concurrent = str(kwargs.pop("concurrent"))
except KeyError:
pass
else:
if concurrent not in ("0", "1"):
raise ValueError(f"{concurrent = }")

Check warning on line 923 in message_ix/models.py

View check run for this annotation

Codecov / codecov/patch

message_ix/models.py#L923

Added line #L923 was not covered by tests
solve_args.append(f"--MACRO_CONCURRENT={concurrent}")

super().__init__(*args, **kwargs)

self.solve_args.extend(solve_args)

@classmethod
def initialize(cls, scenario, with_data=False):
"""Initialize the model structure."""
Expand Down
14 changes: 12 additions & 2 deletions message_ix/tests/test_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,19 @@ def test_calibrate(westeros_solved, w_data_path):
assert not end_grow.isnull().any()


def test_calibrate_roundtrip(westeros_solved, w_data_path):
@pytest.mark.parametrize(
"kwargs",
(
{}, # Default concurrent=0
dict(concurrent=0), # Explicit value, same as default
dict(concurrent=1),
),
)
def test_calibrate_roundtrip(westeros_solved, w_data_path, kwargs):
# this is a regression test with values observed on May 23, 2024
with_macro = westeros_solved.add_macro(w_data_path, check_convergence=True)
with_macro = westeros_solved.add_macro(
w_data_path, check_convergence=True, **kwargs
)
aeei = with_macro.par("aeei")["value"].values
npt.assert_allclose(
aeei,
Expand Down
Loading