For the LP framework we chose PuLP for its beginner friendly fluent and natural sounding model definition. See this tutorial for a more detailed introduction into LP with PuLP or this this comparison of some popular Python LP frameworks. Understanding and debugging the output of an LP solver is non-obvious for casual users. Hence, being able to translate the mathematical model into what looks like natural python code can help to prevent mistakes during model setup.
The PuLP framework can interface with different external solver backends out of which the CBC solver is packaged with the PuLP distribution.
As input data, we can get time-series data with hourly granularity for a given year for the total load (electricity consumption) as well as the total forecasted production potential for solar, onshore and offshore wind with an hourly granularity as well. This grid-level granularity data essentially represents the demand and production capacity of a given grid-zone (typically a country) in aggregate.
The key question to ask the model is by how much would the existing capacity for each of the renewable source categories need to be scaled up such that it could satisfy the current demand in combination with an assumed storage system.
The objective of the model is to minimize the annual cost which can satisfy the operational balancing constraints during each hourly interval.
Since the forecasting data provides estimates of electricity production volume for each hour, we are using the levelized cost of electricity in Euro per MWh for each of the source types to determine to total cost of electricity produced, without knowing what would be installed generation capacity required. We are making the assumption that the current production profile or a country or region could be scaled up linearly while maintaining the same hourly distribution of generation over time.
For the storage model we are going to estimate the charge & discharge power and storage capacity respectively that would need to be installed to satisfy the constraints and are using and capital and operating cost estimate to translate these installed capacities into an annualised cost assuming a 3% interest rate over 15 or 25 year amortisation periods for the capital cost. The storage system is broken up into lithium-ion battery inspired short-duration storage (SDS) and H2 inspired long-duration storage (LDS) respectively. Which means the SDS systems has a single, shared charge and discharge capacity, while the LDS can be charged and discharged independently using different power conversion systems.
Which all this results in the following hard-coded cost assumptions:
To express the cost function requires the definition of a few global variables for things like the global capacity and cost allocations, as well as time-series indexed variables to express constraints for each of the 8760 hourly intervals in a year.
The system constraints are relatively simple: the power balance requires that for each interval, the sum of all power generation and consumption needs to balance out. For each storage system the SOC constraint requires that the state of charge in the next interval is the current state of charge adjusted for any charge and discharge - taking into account losses.
At any time, capacity constraints need to be respected, i.e. state of charge cannot exceed the storage capacity or charge and discarge power cannot exceed the respective power capacity limits.
Storage should be a closed, cyclical system over the year which is arbitrarily enforced by making the state of charge after the last interval to be the same as the original one.
Finally the availability or coverage constraint defines what percentage of overall demand must be satisfied by the system.
The load and production forecast data which are used as the constant parameters in the linear inequality constraints for each interval in the time-series can be obtained from ENTSO-E API. We resample the time series from potentially 15min granularity into hourly to facilitate an easy conversion between power levels and energy (1 MW for 1h -> 1MWh).