Development and code structure

Note

In brief — A developer’s map of the GAMS codebase: how the source is split between the shared core/ and the modules/NN_Name/ folders, how main.gms assembles the model phase by phase and fans each phase out to every module, how a module’s behaviour is selected through realizations, and the object-naming convention you must follow. It closes with the FIXME-comment workflow for turning code notes into tracked issues.

OPEN-PROM is a recursive, modular energy-system model written in GAMS. This page is the practical view of how that code is laid out and assembled; it is not a description of the model’s economics or equations.

See also

For the science — what the model represents, the gap-and-substitution mechanism, sectors, prices and supply — see Model overview.

Folder structure

The codebase is organised around two directories.

core/ holds the shared backbone used by every run. Its files are named for the assembly phases they contribute to:

File

Role

sets.gms

sets for years, countries, technologies and other key dimensions

declarations.gms

declares variables, parameters and scalars

input.gms

reads and prepares input data for the equation system

equations.gms

core equations (including qDummyObj, see below)

preloop.gms

bounds, initial values and runtime settings before solving

solve.gms

the recursive solve loop (years, countries, solver attempts)

postsolve.gms

post-processing, output writing, and fixing solved values to carry into the next year

modules/NN_Name/ holds the sector and cross-sector modules, each in its own numbered subfolder:

Module

Module

01_Transport

07_Emissions

02_Industry

08_Prices

03_RestOfEnergy

09_Heat

04_PowerGeneration

10_Curves

05_Hydrogen

11_Economy

06_CO2

The global dispatcher that assembles all modules is modules/include.gms, located in the top-level modules/ directory.

The phase system

main.gms does not assemble one complete module at a time. It builds the model phase by phase, in this order:

sets → declarations → input → equations → preloop → solve → postsolve

For each phase, main.gms first includes the core/ file for that phase, then calls modules/include.gms, which fans the same phase out to every module via $batinclude. Each module realization then includes only the file that matches the current phase.

The key mental model: the hierarchy is phase-based, not sector-based. main.gms selects a phase, modules/include.gms propagates it, and every active realization contributes its slice of that phase. A phase is a code-assembly concept, not a model-year concept.

Tip

Keep the two kinds of “loop” distinct. The phase order above is how the code is assembled at compile time. At run time the model iterates an outer loop over years, a middle loop over countries/regions, and an inner loop over solver attempts — that is where the recursive, year-by-year solve actually happens.

The solve phase deserves a closer look. The solver call in core/solve.gms is:

solve openprom using nlp minimizing vDummyObj;

In a normal run the equation qDummyObj simply fixes vDummyObj = 1: the NLP solver is used because the equation system is nonlinear, not because the model minimises a planner objective — the solve is searching for a numerically closed, feasible system for the active year and region. In calibration mode (Calibration=MatCalibration) the same qDummyObj becomes a real fitting objective: selected maturity-related parameters become decision variables and the solve minimises the mismatch between model outputs and calibration targets. The model structure is unchanged; only the objective the solve is pointed at differs.

Modules and realizations

Each module folder contains:

  • module.gms — selects the active realization for that module via a global switch and conditional includes;

  • one or more realization subfolders (e.g. legacy, simple, technology, heat, economy, LearningCurves);

  • inside each realization, the per-phase files (declarations.gms, equations.gms, input.gms, preloop.gms, postsolve.gms) plus a realization.gms that dispatches on the active phase.

Not every module offers the same realization names. For example, Transport switches between legacy and simple, Industry between legacy and technology, PowerGeneration between legacy and simple, while Heat uses heat, Economy uses economy, and Curves uses LearningCurves.

The active realizations in the current default setup are:

Module

Realization

Module

Realization

Transport

simple

Emissions

legacy

Industry

technology

Prices

legacy

RestOfEnergy

legacy

Heat

heat

PowerGeneration

simple

Curves

off

Hydrogen

legacy

Economy

economy

CO2

legacy

To change what a module does, the realization switches are set near the bottom of main.gms, under the “MODULE REALIZATION SWITCHES” header (e.g. $setGlobal Transport simple):

  • switch realization — point the module’s global switch at a different existing realization; or

  • edit the active realization — change the per-phase files inside the realization currently selected.

Edit module.gms itself only when adding a brand-new realization (a new subfolder plus a branch in the conditional include). The same module.gms then routes the assembly to the matching phase file via realization.gms.

main.gms also defines the run-level configuration: solver attempts, research vs development mode, time horizon and scenario, calibration mode, the active realizations, and country selection through fCountries.

Default active vs dormant features

Several pieces of model logic are present in the source but not exercised by the default switch settings. Know which is which before you reason about a default run.

Feature

Default state

Notes

Power learning curves

off

Curves defaults to off (main.gms). The endogenous capacity-cost learning term in 04_PowerGeneration/simple is wrapped in $ifthen.curves "%Curves%" == "LearningCurves" and only contributes when Curves=LearningCurves; otherwise the cost multiplier is fixed at 1.

Hydrogen infrastructure equations

dormant

The “Hydrogen Infrastructure” block in 05_Hydrogen/legacy/equations.gms (infrastructure area, distribution price, total H₂ cost) is structurally complete but enclosed in $ontext/$offtext, so GAMS skips it at compile time. The hydrogen production equations around it are active.

MAgPIE soft-link

off

softLinkMAgPIE defaults to off; the iterative task-7 coupling with MAgPIE (fixed biomass price, AFOLU emissions from MAgPIE’s .mif) is an opt-in mode.

Land-use emulator

globiom

landUseEmulator defaults to globiom, i.e. the pre-fitted supply/emission curves are active by default; set it to legacy to disable the emulator (static biomass price, exogenous land emissions).

Warning

The hydrogen infrastructure equations are inactive: editing them changes nothing until the surrounding $ontext/$offtext is removed. Likewise, references to learning-curve cost effects only apply when Curves=LearningCurves.

Naming convention

Object names encode their scope through a prefix; follow it for every new object. The type letters are:

  • Q / q — equation (capital Q = participates in several equations; lowercase q = used in only one place);

  • V / v — variable (same capitalisation rule);

  • i — input data.

The scope is encoded around the type letter:

  • Vm / im — model-wide variable/input, shared across more than one module (declared in core/);

  • Vxx / ixx — module-specific, where xx is the module number (e.g. V01ActivGoodsTransp, i04PlantLifetime);

  • V / i — core-only.

Equations must begin with the two-digit module code: QxxEquationName. Only promote an object to Vm/im when it is genuinely shared by more than one module.

Tip

Both the core/ layer and the individual modules carry real model logic — modules are not empty wrappers. When editing, keep model logic separate from the workflow/wrapper logic of the R harness, and document changes thoroughly.

Developer workflow

Bugs and follow-ups can be tracked directly from the source — FIXME issues from code comments: a specially formatted comment is parsed and turned into a GitHub issue automatically, which keeps the team’s workflow streamlined. The comment must start with FIXME:, followed by a short, descriptive summary that becomes the issue title; a second line may assign your GitHub username to author (optional, but strongly encouraged):

* FIXME: This is a test issue that was generated automatically
* author=derevirn
VExportsFake.FX(runCy,EFS,YTIME)$(not IMPEF(EFS)) = 0;

A few practical points:

  • FIXME comments are converted to issues only after a successful pull request is merged to main.

  • The generated issue on the Issues page includes a snippet of the surrounding code and a link to the file, so the bug is easy to locate.

  • Deleting the FIXME comment from main is meant to close the issue automatically — but this has proven somewhat unreliable, so confirm the closure manually.