The decarbonisation of energy systems is one of the main challenges of our century. Finding the optimal technological mix to achieve that goal is a major concern for policy and decision makers alike. These problems are known as energy system planning and sizing and are often tackled by Mixed Integer Linear Programming (MILP). In order to solve them, one first has to encode the problem using a modeling environment. The modeling tool reads the problem and converts the information into an equivalent intermediate representation understandable by a solver. The solver then takes the intermediate representation as input, solves it and returns a solution.
In previous work (Berger et al.) we highlighted the fact that energy system planning and sizing problems very often possess a time dimension and a natural block decomposable structure where, for example, each technology can be seen as a block. Exploiting this structure has several advantages, including simplifying model encoding, enabling model re-use and facilitating the use of structure-exploiting solution algorithms, among others. Although a couple of solvers can take advantage of such structure, comparatively few modelling environments can handle and exploit it consistently from problem encoding to problem solving. This effectively limits the ability of practitioners to do so. In short, the modeler needed to possess the following attributes,
- be open-source, in order to guarantee an easy access to everyone and promote transparency,
- be stand-alone, to ensure an easy deployment,
- support the encoding of any MILP,
- allow any hierarchical block structure to be exposed and exploited,
- facilitate the encoding and construction of time-indexed models,
- make it easy to re-use and combine components and models,
- and, interfacing with commercial and open-source solvers, including structure-exploiting ones
Based on these specifications, we developed a new modeling language and associated tool called the Graph-Based Optimization Modeling Language (GBOML). GBOML implements a hierarchical hypergraph abstraction of optimization problems. It provides language constructs to represent such hierarchical structure and facilitate the encoding of time-indexed models. More precisely, structured MILPs are encoded as Graphs made-up of nodes and hyperedges where each node can itself be composed of sub-nodes and sub-hyperedges. Each node has its own parameters, variables, constraints and objectives. Each hyperedge has its own set parameters and constraints that link several variables from different nodes. The language combines the expressiveness of algebraic modeling languages making the expression of any mixed integer linear problem possible, with features of object-oriented modeling in order to facilitate problem formulation and model re-use.
The GBOML parser, developed in Python, is open-source (MIT licence) and depends solely on Numpy, Scipy and PLY (three very stable libraries). It turns GBOML input files into hierarchical graph data structures representing optimization models. The GBOML parser takes advantage of the structure by using it to speed up model generation, expose problem structure to specialised solvers and simplify post-processing. The associated tool provides both a command-line interface and a Python API. It interfaces with a variety of open-source and commercial solvers, including structure-exploiting ones. GBOML has recently been published in the Journal of Open Source Software.
To illustrate how problems are encoded in GBOML, we consider the problem of finding the optimal capacity of solar panels and battery storage to install in order to satisfy a known electricity demand profile for every hour of the year. In this toy example, we have two nodes, the solar panels and a battery, and one hyperedge, the power balance. The problem can be encoded in GBOML as,
T = 8760; // planning horizon (hours)
capex = 600; // annualised capital expenditure per unit capacity
capacity_factor = import « pv_gen.csv »; // normalised generation profile
internal: capacity; // capacity of solar PV plant
external: power[T]; // power output of solar PV plant
capacity >= 0;
power[t] >= 0;
power[t] <= capacity_factor[t] * capacity;
min: capex * capacity;
capex = 150; // annualised capital expenditure per unit capacity
internal: capacity; // energy capacity of battery storage system
internal: energy[T]; // energy stored in battery storage system
external: power[T]; // power flow in/out of battery storage system
capacity >= 0;
energy[t] >= 0;
energy[t] <= capacity;
energy[t+1] == energy[t] + power[t];
min: capex * capacity;
electrical_load = import « electrical_load.csv »;
SOLAR_PV.power[t] == electrical_load[t] + BATTERY.power[t];
You can try this example on google colab.