8. REISE.jl

REISE.jl (Renewable Energy Integration Simulation Engine) is the simulation engine developed by BES to run power-flow studies in the U.S. electric grid. REISE.jl is an open-source package written in Julia that is available on GitHub. It can be interfaced with the BES software ecosystem (see PowerSimData) or used in a standalone mode. In both cases you will need an external optimization solver to solve the DCOPF problem.

You will find in this documentation all the information needed to install this package and use it. We also provide the formulation of the objective function along with its constraints.

8.1. System Requirements

Large simulations can require significant amounts of RAM. The amount of necessary RAM is proportional to both the size of the electric grid and the duration of the interval considered for the simulation.

As a general estimate, 1-2 GB of RAM is needed per hour in the interval in a simulation across the entire USA grid. For example, a 24-hour interval would require 24-48 GB of RAM; if only 16 GB of RAM is available, consider using a time interval of 8 hours or less as that would take 8-16 GB of RAM.

The memory necessary would also be proportional to the size of grid used. Since the Western interconnect is roughly 8 times smaller than the entire USA grid, a simulation ran in this interconnect with a 24-hour interval would require ~3-6 GB of RAM.

8.2. Installation

There are two options, either install all the dependencies yourself or setup the engine within a Docker image. Whatever option you choose, you will need an external solver to run optimizations. We recommend Gurobi, though any other solver compatible with JuMP can be used. Note that Gurobi is a commercial solver and hence a license file is required. This may be either a local license, a cloud license or a free license for academic use. Check their Software Downloads and License Center page for more details.

Start by cloning the repository locally:

git clone https://github.com/Breakthrough-Energy/REISE.jl

You will also need to download some input data in order to run simulations. Sample data are available on Zenodo. You will find there hourly time series for the hydro/solar/wind generators and a MAT-file enclosing all the information related to the electrical grid in accordance with the MATPOWER case file format.

8.2.1. Native Installation

Installation will depend on your operating system. Some examples are provided for Unix-like platforms.

8.2.1.1. Julia

Download Julia 1.5 and install it following the instructions located on their Platform Specific Instructions for Official Binaries page. This should be straightforward:

  • Choose a destination directory. For shared installation, /opt is recommended.

    cd /opt
    
  • Download and unzip the package in the chosen directory:

    wget -q https://julialang-s3.julialang.org/bin/linux/x64/1.5/julia-1.5.3-linux-x86_64.tar.gz
    tar -xf julia-1.5.3-linux-x86_64.tar.gz
    
  • Expand the PATH environment variable. For bash users edit the .bashrc file in your $HOME folder:

    export PATH="$PATH:/opt/julia-1.5.3/bin"
    

8.2.1.2. Gurobi

If you plan on using Gurobi as a solver, you will need to download and install it first so it can be accessed by Jump. Installation of Gurobi depends on both the operating system and the license type. Detailed instructions can be found in the Gurobi Installation Guide. For Unix-like platforms, this will look like:

  • Choose a destination directory. For shared installation, /opt is recommended.

    cd /opt
    
  • Download and unzip the package in the chosen directory:

    wget https://packages.gurobi.com/9.1/gurobi9.1.0_linux64.tar.gz
    tar -xvfz gurobi9.1.0_linux64.tar.gz
    

    This will create the /opt/gurobi910/linux64 subdirectory in which the complete distribution is located.

  • Set environments variables. For bash users edit the .bashrc file in your $HOME folder:

    export GUROBI_HOME="/opt/gurobi910/linux64"
    export PATH="${PATH}:${GUROBI_HOME}/bin"
    export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${GUROBI_HOME}/lib"
    
  • The Gurobi license needs to be download and installed. Download a copy of your Gurobi license from the account portal, and copy it into the parent directory of $GUROBI_HOME.

    cd gurobi.lic /opt/gurobi910/gurobi.lic
    

To verify that Gurobi is properly installed, run the gurobi.sh shell script:

.$GUROBI_HOME/bin/gurobi.sh

8.2.1.3. REISE.jl

The package will need to be added to each user’s default Julia environment. This can be done by launching Julia and typing ] to access the Pkg (the built-in package manager) REPL environment that easily allows operations such as installing, updating and removing packages.

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.3 (2020-11-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> ]

pkg>

From here, we recommend that you create an environment and install the dependencies in the same state specified in the manifest (Manifest.toml):

activate /PATH/TO/REISE.jl
instantiate

Note that the Julia packages for the user’s desired solvers need to be installed separately. For instance, if you want to use GLPK, the GNU Linear Programming Kit library, you will need to run:

import Pkg
Pkg.add("GLPK")

Then, create a JULIA_PROJECT environment variable that points to PATH/TO/REISE.jl.

To verify that the package has been successfully installed, open a new instance of Julia and verify that the REISE package can load without any errors with the following command:

using REISE

8.2.1.4. Python

We strongly recommend that you install Python in order to be able to use the command line interface we developed to run simulations but most importantly to extract the data generated by the simulation.

The scripts located in pyreisejl depend on several packages. Those are specified in the requirements.txt, file and can be installed using:

pip install -r requirements.txt

To verify that the Python scripts can successfully run, open a Python interpreter and run the following commands:

from julia.api import Julia
Julia(compiled_modules=False)
from julia import REISE

Note that the final import of REISE may take a couple of minutes to complete.

8.2.2. Docker

The easiest way to setup this engine is within a Docker image.

There is an included Dockerfile that can be used to build the Docker image. With the Docker daemon installed and running, do:

docker build . -t reisejl

To run the Docker image, you will need to mount two volumes; one containing the Gurobi license file and another containing the necessary input files for the engine.

docker run -it `
-v /path/to/gurobi.lic:/usr/share/gurobi_license `
-v /path/to/input/data:/path/to/input/data `
reisejl bash

You are ready to run simulation as demonstrated in the Usage section.

8.3. Usage

REISE.jl can be used within the Julia interpreter or through the Python scripts.

8.3.1. Julia

After the installation, the REISE package is registered and can be imported using import REISE to call REISE.run_scenario() or using REISE to call run_scenario().

Running a scenario requires the following inputs:

  • interval: the length of each simulation interval (hours).

  • n_interval: the number of simulation intervals.

  • start_index: the hour to start the simulation, representing the row of the time- series profiles in demand.csv, hydro.csv, solar.csv, and wind.csv. Note that unlike some other programming languages, Julia is 1-indexed, so the first index is 1.

  • inputfolder: the directory from which to load input files.

  • optimizer_factory: an argument that can be passed to JuMP.Model to create a new model instance with an attached solver. Be sure to pass the factory itself (e.g. GLPK.Optimizer) rather than an instance (e.g. GLPK.Optimizer()). See the JuMP.Model documentation for more information.

To illustrate, to run a scenario that starts at the 1st hour of the year, runs in 3 intervals of 24 hours each, using input data located in working directory (pwd()) and using the GLPK solver, call:

 import REISE
 import GLPK

 REISE.run_scenario(;
  interval=24, n_interval=3, start_index=1, inputfolder=pwd(),
  optimizer_factory=GLPK.Optimizer
)

Optional arguments include:

  • outputfolder: a directory in which to store results files. The default is a subdirectory “output” within the input directory (created if it does not already exist).

  • threads: the number of threads to be used by the solver. The default is to let the solver decide.

  • solver_kwargs: a dictionary of String => value pairs to be passed to the solver.

Default settings for running using Gurobi can be accessed if Gurobi.jl has already been imported using the REISE.run_scenario_gurobi function:

 using REISE
 using Gurobi

 REISE.run_scenario_gurobi(;
  interval=24, n_interval=3, start_index=1, inputfolder=pwd(),
)

Optional arguments for REISE.run_scenario can still be passed as desired.

8.3.2. Python

There are two main Python scripts included in pyreisejl:

  • pyreisejl/utility/call.py

  • pyreisejl/utility/extract_data.py

The first of these scripts transforms more descriptive input parameters into the ones necessary for the Julia engine while also performing some additional input validation. The latter, which can be set to automatically occur after the simulation has completed, extracts key metrics from the resulting .mat files to .pkl files.

8.3.2.1. Running a simulation

A simulation can be run as follows:

pyreisejl/utility/call.py -s '2016-01-01' -e '2016-01-07' -int 24 -i '/PATH/TO/INPUT/DATA'

It will solve the DCOPF problem in our grid model by interval of 24h using hourly data located in /PATH/TO/INPUT/DATA from January 1st to January 7th 2016. Note that the start and end dates need to match dates contained in the input profiles (demand, hydro, solar, wind). By default Gurobi will be used as the solver and the output data (.mat files) will be saved in an output folder created in the given input directory.

The full list of arguments can be accessed via pyreisejl/utility/call.py --help:

usage: call.py [-h] [-s START_DATE] [-e END_DATE] [-int INTERVAL] [-i INPUT_DIR] [-t THREADS] [-d] [-o OUTPUT_DIR] [-k]
               [--solver SOLVER] [-j JULIA_ENV]
               scenario_id

Run REISE.jl simulation.

positional arguments:
  scenario_id           Scenario ID only if using PowerSimData.

optional arguments:
  -h, --help            show this help message and exit
  -s START_DATE, --start-date START_DATE
                        The start date for the simulation in format 'YYYY-MM-DD', 'YYYY-MM-DD HH',
                        'YYYY-MM-DD HH:MM', or 'YYYY-MM-DD HH:MM:SS'.
  -e END_DATE, --end-date END_DATE
                        The end date for the simulation in format 'YYYY-MM-DD',
                        'YYYY-MM-DD HH', 'YYYY-MM-DD HH:MM', or 'YYYY-MM-DD HH:MM:SS'.
                        If only the date is specified (without any hours), the entire
                        end-date will be included in the simulation.
  -int INTERVAL, --interval INTERVAL
                        The length of each interval in hours.
  -i INPUT_DIR, --input-dir INPUT_DIR
                        The directory containing the input data files. Required files
                        are 'grid.pkl', 'demand.csv', 'hydro.csv', 'solar.csv', and
                        'wind.csv'.
  -t THREADS, --threads THREADS
                        The number of threads to run the simulation with. This is
                        optional and defaults to Auto.
  -d, --extract-data    If this flag is used, the data generated by the simulation
                        after the engine has finished running will be automatically
                        extracted into .pkl files, and the result.mat files will be
                        deleted. The extraction process can be memory intensive. This
                        is optional and defaults to False if the flag is omitted.
  -o OUTPUT_DIR, --output-dir OUTPUT_DIR
                        The directory to store the extracted data. This is optional
                        and defaults to a folder in the input directory. This flag is
                        only used if the extract-data flag is set.
  -k, --keep-matlab     The result.mat files found in the execute directory will be
                        kept instead of deleted after extraction. This flag is only
                        used if the extract-data flag is set.
  --solver SOLVER       Specify the solver to run the optimization. Will default to
                        gurobi. Current solvers available are clp,glpk,gurobi.
  -j JULIA_ENV, --julia-env JULIA_ENV
                        The path to the julia environment within which to run
                        REISE.jl. This is optional and defaults to the default julia
                        environment.

Different solvers can be used (--solver).

There is another optional flag that specifies the number of threads to use for the simulation run in Gurobi (--threads). If the number of threads specified is higher than the number of logical processor count available, a warning will be generated but the simulation will still run.

Finally, you can use --extract-data to automatically extract the data after a simulation run without having to manually initiate it. Note that the extraction process can be memory intensive

8.3.2.2. Extracting Simulation Results

After the simulation has completed and if the --extract-data is set in the call.py script, the extraction can be run using the same start and end dates as were used to run the simulation:

pyreisejl/utility/extract_data.py -s '2016-01-01' -e '2016-01-07' -i '/PATH/TO/INPUT/DATA'

The full list of arguments can be accessed via pyreisejl/utility/extract-data.py --help:

usage: extract_data.py [-h] [-s START_DATE] [-e END_DATE] [-i INPUT_DIR] [-o OUTPUT_DIR] [-f FREQUENCY] [-k] scenario_id

Extract data from the results of the REISE.jl simulation.

positional arguments:
  scenario_id           Scenario ID only if using PowerSimData.

optional arguments:
  -h, --help            show this help message and exit
  -s START_DATE, --start-date START_DATE
                        The start date as provided to run the simulation. Supported
                        formats are 'YYYY-MM-DD', 'YYYY-MM-DD HH', 'YYYY-MM-DD HH:MM',
                        or 'YYYY-MM-DD HH:MM:SS'.
  -e END_DATE, --end-date END_DATE
                        The end date as provided to run the simulation. Supported
                        formats are 'YYYY-MM-DD', 'YYYY-MM-DD HH', 'YYYY-MM-DD HH:MM',
                        or 'YYYY-MM-DD HH:MM:SS'.
  -i INPUT_DIR, --input-dir INPUT_DIR
                        The directory containing the input data files. Required files
                        are 'grid.pkl', 'demand.csv', 'hydro.csv', 'solar.csv', and
                        'wind.csv'.
  -o OUTPUT_DIR, --output-dir OUTPUT_DIR
                        The directory to store the results. This is optional and
                        defaults to a folder in the input directory.
  -f FREQUENCY, --frequency FREQUENCY
                        The frequency of data points in the original profile csvs as a
                        Pandas frequency string. This is optional and defaults to an
                        hour.
  -k, --keep-matlab     If this flag is used, the result.mat files found in the execute
                        directory will be kept instead of deleted.

When manually running the extract_data process, the script assumes the frequency of the input profiles are hourly and will construct the timestamps for the resulting data accordingly. If a different frequency was used for the input data, it must be specified via --frequency. Also, other parameters can be invoked to handle output data.

When the script has finished running, the following .pkl files will be available:

  • PF.pkl (power flow)

  • PG.pkl (power generated)

  • LMP.pkl (locational marginal price)

  • CONGU.pkl (congestion, upper flow limit)

  • CONGL.pkl (congestion, lower flow limit)

  • AVERAGED_CONG.pkl (time averaged congestion)

If the grid used in the simulation contains DC lines, energy storage devices, or flexible demand resources, the following files will also be extracted as necessary:

  • PF_DCLINE.pkl (power flow on DC lines)

  • STORAGE_PG.pkl (power generated by storage units)

  • STORAGE_E.pkl (energy state of charge)

  • LOAD_SHIFT_DN.pkl (demand that is curtailed)

  • LOAD_SHIFT_UP.pkl (demand that is added)

If one or more intervals of the simulation were found to be infeasible without shedding load, the following file will also be extracted:

  • LOAD_SHED.pkl (load shed profile for each load bus)

8.3.2.3. Compatibility with our Software Ecosystem

Both pyreisejl/utility/call.py and pyreisejl/utility/extract_data.py can be called using a positional argument that corresponds to a scenario id as generated by PowerSimData. Using this invocation assumes you have installed our software ecosystem. See Installation Guide ) if you are interested.

8.4. Formulation

8.4.1. Sets

  • \(B\): Set of buses indexed by \(b\).

  • \(I\): Set of generators indexed by \(i\).

  • \(L\): Set of transmission network branches indexed by \(l\).

  • \(S\): Set of generation cost curve segments indexed by \(s\).

  • \(T\): Set of time periods indexed by \(t\)

8.4.1.1. Subsets

  • \(I^{\rm H}\): Set of hydro generators.

  • \(I^{\rm S}\): Set of solar generators.

  • \(I^{\rm W}\): Set of wind generators.

8.4.2. Variables

  • \(E_{b,\,t}\): Energy available in energy storage devices at bus \(b\) at time \(t\).

  • \(f_{l,\,t}\): Power flowing on branch \(l\) at time \(t\).

  • \(g_{i,\,t}\): Power injected by each generator \(i\) at time \(t\).

  • \(g_{i,\,s,\,t}\): Power injected by each generator \(i\) from cost curve segment \(i\) at time \(t\).

  • \(J^{\rm chg}_{b,\,t}\): Charging power of energy storage devices at bus \(b\) at time \(t\).

  • \(J^{\rm dis}_{b,\,t}\): Discharging power of energy storage devices at bus \(b\) at time \(t\).

  • \(s_{b,\,t}\): Load shed at bus \(b\) at time \(t\).

  • \(v_{l,\,t}\): Branch limit violation for branch \(l\) at time \(t\).

  • \(\delta^{\rm down}_{b,\,t}\): Amount of flexible demand curtailed at bus \(b\) at time \(t\).

  • \(\delta^{\rm up}_{b,\,t}\): Amount of flexible demand added at bus \(b\) at time \(t\).

  • \(\theta_{b,\,t}\): Voltage angle of bus \(b\) at time \(t\).

8.4.3. Parameters

  • \(a^{\rm shed}\): Binary parameter, whether load shedding is enabled.

  • \(a^{\rm viol}\): Binary parameter, whether transmission limit violation is enabled.

  • \(c_{i,\,s}\): Cost coefficient for segment \(s\) of generator \(i\).

  • \(c^{\rm min}_{i}\): Cost of running generator \(i\) at its minimum power level.

  • \(d_{b,\,t}\): Power demand at bus \(b\) at time \(t\).

  • \(E_{b,\,0}\): Initial energy available in energy storage devices at bus \(b\).

  • \(E^{\rm max}_{b}\): Maximum energy stored in energy storage devices at bus \(b\).

  • \(f^{\rm max}_{l}\): Maximum flow over branch \(l\).

  • \(g^{\rm min}_{i}\): Minimum generation for generator \(i\).

  • \(g^{\rm max}_{i,\,s}\): Width of cost curve segment \(s\) of generator \(i\).

  • \(J^{max}_{b}\): Maximum charging/discharging power of energy storage devices at bus \(b\).

  • \(m^{\rm line}_{l,\,b}\): Mapping of branches to buses.
    • \(m^{\rm line}_{l,\,b} = 1\) if branch \(l\) starts at bus \(b\),

    • \(m^{\rm line}_{l,\,b} = -1\) if branch \(l\) ends at bus \(b\),

    • \(m^{\rm line}_{l,\,b} = 0\) otherwise.

  • \(m^{\rm unit}_{i,\,b}\): Mapping of generators to buses.
    • \(m^{\rm unit}_{i,\,b} = 1\) if generator \(i\) is located at bus \(b\),

    • \(m^{\rm unit}_{i,\,b} = 0\) otherwise.

  • \(M\): An arbitrarily-large constant, used in ‘big-M’ constraints to either constrain to \(0\), or relax constraint.

  • \(p^{\rm e}\): Value of stored energy at beginning/end of interval (so that optimization does not automatically drain the storage by end-of-interval).

  • \(p^{\rm s}\): Load shed penalty factor.

  • \(p^{\rm v}\): Transmission violation penalty factor.

  • \(r^{\rm up}_{i}\): Ramp-up limit for generator \(i\).

  • \(r^{\rm down}_{i}\): Ramp-down limit for generator \(i\).

  • \(w_{i,\,t}\): Power available at time \(t\) from time-varying generator (hydro, wind, solar) \(i\).

  • \(x_{l}\): Impedance of branch \(l\).

  • \(\underline{\delta}_{b,\,t}\): Demand flexibility curtailments available (in MW) at bus \(b\) at time \(t\).

  • \(\overline{\delta}_{b,\,t}\): Demand flexibility additions available (in MW) at bus \(b\) at time \(t\).

  • \(\Delta^{\rm balance}\): The length of the rolling load balance window (in hours), used to account for the duration that flexible demand is deviating from the base demand.

  • \(\eta^{\rm chg}_{b}\): Charging efficiency of storage device at bus \(b\).

  • \(\eta^{\rm dis}_{b}\): Discharging efficiency of storage device at bus \(b\).

8.4.4. Constraints

All equations apply over all entries in the indexed sets unless otherwise listed.

  • \(0 \le g_{i,\,s,\,t} \le g^{\rm max}_{i,\,s,\,t}\): Generator segment power is non-negative and less than the segment width.

  • \(0 \le s_{b,\,t} \le a^{\rm shed} \cdot \left ( d_{b,\,t} + \delta^{\rm up}_{b,\,t} - \delta^{\rm down}_{b,\,t} \right )\): Load shed is non-negative and less than the demand at that bus (including the impact of demand flexibility), if load shedding is enabled. If not, load shed is fixed to \(0\).

  • \(0 \le v_{b,\,t} \le a^{\rm viol} \cdot M\): Transmission violations are non- negative, if they are enabled (\(M\) is a sufficiently large constant that there is no effective upper limit when \(a^{\rm shed} = 1\)). If not, they are fixed to \(0\).

  • \(0 \le J_{b,\,t}^{\rm chg} \le J_{b}^{\rm max}\): Storage charging power is non-negative and limited by the maximum charging power at that bus.

  • \(0 \le J_{b,\,t}^{\rm dis} \le J_{b}^{\rm max}\): Storage discharging power is non-negative and limited by the maximum discharging power at that bus.

  • \(0 \le E_{b,\,t} \le E_{b}^{\rm max}\): Storage state-of-charge is non-negative and limited by the maximum state of charge at that bus.

  • \(g_{i,\,t} = w_{i,\,t} \quad \forall i \in I^{\rm H}\): Hydro generator power is fixed to the profiles.

  • \(0 \le g_{i,\,t} \le w_{i,\,t} \quad \forall i \in I^{\rm S} \cup I^{\rm W}\): Solar and wind generator power is non-negative and not greater than the availability profiles.

  • \(\sum_{i \in I} m_{i,\,b}^{\rm unit} g_{i,\,t} + \sum_{l \in L} m_{l,\,b}^{\rm line} f_{l,\,t} + J_{b,\,t}^{\rm dis} + s_{b,\, t} + \delta_{b,\, t}^{\rm down} = d_{b,\,t} + J_{b,\,t}^{\rm chg} + \delta_{b,\,t}^{\rm up}\): Power balance at each bus \(b\) at time \(t\).

  • \(g_{i,\,t} = g_{i}^{\rm min} + \sum_{s \in \rm S} g_{i,\,s,\,t}\): Total generator power is equal to the minimum power plus the power from each segment.

  • \(E_{b,\,t} = E_{b,\,t-1} + \eta_{b}^{\rm chg} J_{b,\, t}^{\rm chg} - \frac{1}{\eta_{b}^{\rm dis}} J_{b,\,t}^{\rm dis}\): Conservation of energy for energy storage state-of-charge.

  • \(g_{i,\,t} - g_{i,\,t-1} \le r_{i}^{\rm up}\): Ramp-up constraint.

  • \(g_{i,\,t} - g_{i,\,t-1} \ge r_{i}^{\rm down}\): Ramp-down constraint.

  • \(-\left ( f_{l}^{\rm max} + v_{l,\,t} \right ) \le f_{l,\,t} \le \left ( f_{l}^{\rm max} + v_{l,\,t} \right )\): Power flow over each branch is limited by the branch power limit, and can only exceed this value by using the ‘violation’ variable (if enabled), which is penalized in the objective function.

  • \(f_{l,\,t} = \frac{1}{x_{l}} \sum_{b \in B} m_{l,\,b}^{\rm line} \theta_{b,\,t}\): Power flow over each branch is proportional to the admittance and the angle difference.

  • \(0 \le \delta_{b,\,t}^{\rm down} \le \underline{\delta}_{b,\,t}\): Bound on the amount of demand that flexible demand resources can curtail.

  • \(0 \le \delta_{b,\,t}^{\rm up} \le \overline{\delta}_{b,\,t}\): Bound on the amount of demand that flexible demand resources can add.

  • \(\sum_{t = k}^{k + \Delta^{\rm balance}} \delta_{b,\,t}^{\rm up} - \delta_{b,\,t}^{\rm down} \ge 0, \quad \forall b \in B, \quad k = 1, ..., |T| - \Delta^{\rm balance}\): Rolling load balance for flexible demand resources; used to restrict the time that flexible demand resources can deviate from the base demand.

  • \(\sum_{t \in T} \delta_{b,\,t}^{\rm up} - \delta_{b,\,t}^{\rm down} \ge 0, \quad \forall b \in B\): Interval load balance for flexible demand resources.

8.4.5. Objective Function

\(\min \left [ \sum_{t \in T} \sum_{i \in I} \left [ C_{i}^{\rm min} + \sum_{s \in \rm S} c_{i,\,s} g_{i,\,s,\,t} \right ] + p^{\rm s} \sum_{t \in T} \sum_{b \in B} s_{b,\,t} + p^{\rm v} \sum_{t \in T} \sum_{l \in L} v_{l,\,t} + p^{\rm e} \sum_{b \in B} \left [ E_{b,\,0} - E_{b,\,|T|} \right ] \right ]\)

There are four main components to the objective function:

  • \(\sum_{t \in T} \sum_{i \in I} [ C_{i}^{\rm min} + \sum_{s \in \rm S} c_{i,\,s} g_{i,\,s,\,t} ]\): The cost of operating generators, fixed costs plus variable costs, which can consist of several cost curve segments for each generator.

  • \(p^{\rm s} \sum_{t \in T} \sum_{b \in B} s_{b,\,t}\): Penalty for load shedding (if load shedding is enabled).

  • \(p^{\rm v} \sum_{t \in T} \sum_{l \in L} v_{l,\,t}\): Penalty for transmission line limit violations (if transmission violations are enabled).

  • \(p^{\rm e} \sum_{b \in B} \left [ E_{b,\,0} - E_{b,\,|T|} \right ]\): Penalty for ending the interval with less stored energy than the start, or reward for ending with more.