A minimal working example
An energy system model optimizes technological chains to meet a given
(exogenous) demand for a commodity (e.g. electricity, heat, etc.) using
given resources (e.g. fuels, renewable energy sources, etc.) and
available technological options (e.g. power plants, heat pumps, etc.).
The minimal model must have a definition of the final demand and
available resources.
Let’s start with the package settings and continue with the minimal
model definition.
Check which solver is set
my_solver <- get_default_solver()
print(my_solver)
#> $name
#> [1] "glpk"
#>
#> $lang
#> [1] "GLPK"
(Optional) change default solver.
set_default_solver(solver_options$julia_highs)
Check/set directory to store scenarios using the
set_scenarios_path
and get_scenarios_path
functions.
get_scenarios_path()
#> [1] "scenarios/"
The following minimal model consists of a commodity, a supply, and a
demand object.
The commodity object is defined using the newCommodity
constructor-function. The commodity object has a name and a
description.
# define a commodity
COM <- newCommodity("COM")
The supply object is defined using the newSupply
constructor-function. The supply object has a name, a description, a
commodity it supplies, and availability data. The availability data is a
data frame with a time-series of costs or prices of the commodity.
# Declare a domestic supply (market) for the commodity
SUP <- newSupply(
name = "SUP",
desc = "Domestic supply of commodity",
commodity = "COM",
availability = data.frame(
cost = 10 # costs or prices of the commodity
)
)
This simple supply object sets no physical limits on the availability
of the commodity. Only the price is set.
Schematic representation of the supply object shows an output arrow
with the name of the supply object and the commodity it supplies. No
inputs are required to “produce” the commodity in the model.
draw(SUP)
Demand is an opposite object to supply. It represents the final consumption of a commodity in the model.
DEM <- newDemand(
name = "DEM",
desc = "Final demand for commodity",
commodity = "COM",
dem = data.frame(
slice = "ANNUAL", # time-slices are defined in the calendar object
dem = 100
)
)
draw(DEM)
The minimal model can be built now from the created objects. There is
a commodity that is supplied and demanded. The commodity has a price,
and the demand is exogenously set. The optimization problem is quite
simple: minimize the costs of supplying the demand. No technological
alternatives are available yet in the model to choose from. No
constraints are set on resources.
Let’s create a model object and solve it. Several settings are
required to parameterize the model and create a model object:
- region
- a name of a geographical region where the model
is defined.
- data
- a repository object that stores all data objects
used in the model.
- calendar
- a calendar object that defines the time
structure of the model.
- horizon
- a horizon object that defines the time horizon
of the model.
- discount
- a discount rate used to compute the present
value of costs in different time periods.
The repository object is created by combining all data objects used
in the model in all scenarios or a group of scenarios. Additional data
(model objects and settings) can be added to a particular
scenario.
# create a repository object to store the core model data
world_repo <- newRepository(name = "world_repo", COM, SUP, DEM)
# check content of the repository by type/class
summary(world_repo)
#> commodity demand supply
#> 1 1 1
# check names of the repository objects
names(world_repo)
#> [1] "COM" "SUP" "DEM"
The calendar object defines the time structure of the model:
time-frames and time-slices. Time-frames are names of time periods that
include time-slices. Time-slices are minimal time units in a particular
time-frame. Time-frames have nested structure. The top level is a year,
with name ANNUAL
. This is a default and if no sub-anual
resolutions are set, the simplest time structure.
hello_calendar <- newCalendar() # default calendar with no sub-annual resolutions
Horizon object defines the time horizon of the model. It is a
sequence of milestone years and periods for each milestone. The example
below defines a planning horizon from 2025 to 2050 with 5-year
intervals.
One-year horizon:
planning_2025 <- newHorizon(2025)
planning_2025
#> An object of class "horizon"
#> Slot "name":
#> character(0)
#>
#> Slot "desc":
#> character(0)
#>
#> Slot "period":
#> [1] 2025
#>
#> Slot "intervals":
#> start mid end
#> <int> <int> <int>
#> 1: 2025 2025 2025
Multi-year horizon:
planning_horizon <- newHorizon(
period = 2025:2050,
intervals = rep(5, 10)
)
planning_horizon
#> An object of class "horizon"
#> Slot "name":
#> character(0)
#>
#> Slot "desc":
#> character(0)
#>
#> Slot "period":
#> [1] 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039
#> [16] 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
#>
#> Slot "intervals":
#> start mid end
#> <num> <num> <num>
#> 1: 2025 2025 2025
#> 2: 2026 2027 2029
#> 3: 2030 2032 2034
#> 4: 2035 2037 2039
#> 5: 2040 2042 2044
#> 6: 2045 2047 2049
#> 7: 2050 2050 2050
The newModel
constructor-function combines all the
configuration and data objects to create a model object.
mod <- newModel(
name = "MODEL",
region = "WORLD",
data = world_repo,
calendar = hello_calendar,
horizon = planning_2025,
discount = 0.05
)
class(mod)
#> [1] "model"
#> attr(,"package")
#> [1] "energyRt"
Once the model object is created, it can be solved using the
solve
function. The solved (or interpolated) model object
becomes a scenario object.
# solve for one year
scen_2025 <- solve(mod, name = "HELLO_2025")
#> model: 'MODEL'
#> scenario: 'HELLO_2025'
#> path: scenarios/HELLO_2025_MODEL
#> inMemory: TRUE
#> Interpolating parameters
#> Repository 'world_repo'
#> Validating sets
#> Validating constraints
#> Repository 'world_repo'
#> Validating weather-sets
#> 1.36 sec elapsed
#> Solver directory: scenarios/HELLO_2025_MODEL/script/20241104020148UTC
#> Starting time: 2024-11-03 21:01:48
#> Solver directory: scenarios/HELLO_2025_MODEL/script/20241104020148UTC
#> Writing files: 0.17s
#> Starting GLPK
#> 0.02s
#> Reading solution: 0.03s
summary(scen_2025)
#> Scenario: HELLO_2025
#> desc:
#> Model: MODEL
#> Interpolated: TRUE
#> path: scenarios/HELLO_2025_MODEL
#> Solution status: optimal
#> vObjective: 1000
#> Size: 3.5 Mb
Solving for multiple years assumes the perferct foresight of the all
given data for the future. Since we haven’t defined any years for the
data, it is extrapolated by default for demand and supply objects.
# solve for multi-year horizon by adding the horizon object
scen_2050 <- solve(mod, name = "HELLO_WORLD",
calendar = planning_horizon)
#> model: 'MODEL'
#> scenario: 'HELLO_WORLD'
#> path: scenarios/HELLO_WORLD_MODEL
#> inMemory: TRUE
#> Interpolating parameters
#> Repository 'world_repo'
#> Validating sets
#> Validating constraints
#> Repository 'world_repo'
#> Validating weather-sets
#> 1.25 sec elapsed
#> Solver directory: scenarios/HELLO_WORLD_MODEL/script/20241104020149UTC
#> Starting time: 2024-11-03 21:01:49
#> Solver directory: scenarios/HELLO_WORLD_MODEL/script/20241104020149UTC
#> Writing files: 0.17s
#> Starting GLPK
#> 0.03s
#> Reading solution: 0.04s
summary(scen_2050)
#> Scenario: HELLO_WORLD
#> desc:
#> Model: MODEL
#> Interpolated: TRUE
#> path: scenarios/HELLO_WORLD_MODEL
#> Solution status: optimal
#> vObjective: 15093.94
#> Size: 3.6 Mb
tbc…