Public Documentation

Documentation for GenX public interface.

GenX.run_genx_case!Function
run_genx_case!(case::AbstractString, optimizer::Any=HiGHS.Optimizer)

Run a GenX case with the specified optimizer. The optimizer can be any solver supported by MathOptInterface.

Arguments

  • case::AbstractString: the path to the case folder
  • optimizer::Any: the optimizer instance to be used in the optimization model

Example

run_genx_case!("path/to/case", HiGHS.Optimizer)
run_genx_case!("path/to/case", Gurobi.Optimizer)
source
GenX.configure_settingsFunction
configure_settings(settings_path::String, output_settings_path::String)

Reads in the settings from the genx_settings.yml and output_settings.yml YAML files and merges them with the default settings. It then validates the settings and returns the settings dictionary.

Arguments

  • settings_path::String: The path to the settings YAML file.
  • output_settings_path::String: The path to the output settings YAML file.

Returns

  • settings::Dict: The settings dictionary.
source
GenX.configure_solverFunction
configure_solver(solver_settings_path::String, optimizer::Any)

This method returns a solver-specific MathOptInterface.OptimizerWithAttributes optimizer instance to be used in the GenX.generate\_model() method.

Arguments

  • solver_settings_path::String: specifies the path to the directory that contains the settings YAML file for the specified solver.
  • optimizer::Any: the optimizer instance to be configured.

Currently supported solvers include: "Gurobi", "CPLEX", "Clp", "Cbc", or "SCIP"

Returns

  • optimizer::MathOptInterface.OptimizerWithAttributes: the configured optimizer instance.
source
GenX.load_inputsFunction
load_inputs(setup::Dict,path::AbstractString)

Loads various data inputs from multiple input .csv files in path directory and stores variables in a Dict (dictionary) object for use in model() function

inputs: setup - dict object containing setup parameters path - string path to working directory

returns: Dict (dictionary) object containing all data inputs

source
GenX.load_dataframeFunction
load_dataframe(path::AbstractString)

Attempts to load a dataframe from a csv file with the given path. If it's not found immediately, it will look for files with a different case (lower/upper) in the file's basename.

source
load_dataframe(dir::AbstractString, base::AbstractString)

Attempts to load a dataframe from a csv file with the given directory and file name. If not found immediately, look for files with a different case (lower/upper) in the file's basename.

source
GenX.generate_modelFunction
generate_model(setup::Dict,inputs::Dict,OPTIMIZER::MOI.OptimizerWithAttributes,modeloutput = nothing)

This function sets up and solves a constrained optimization model of electricity system capacity expansion and operation problem and extracts solution variables for later processing.

In addition to calling a number of other modules to create constraints for specific resources, policies, and transmission assets, this function initializes two key expressions that are successively expanded in each of the resource-specific modules: (1) the objective function; and (2) the zonal power balance expression. These two expressions are the only expressions which link together individual modules (e.g. resources, transmission assets, policies), which otherwise are self-contained in defining relevant variables, expressions, and constraints.

Objective Function

The objective function of GenX minimizes total annual electricity system costs over the following six components shown in the equation below:

\[\begin{aligned} &\sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST}_{y,z} \times \overline{\Omega}^{size}_{y,z} \times \Omega_{y,z}) + (\pi^{FOM}_{y,z} \times \overline{\Omega}^{size}_{y,z} \times \Delta^{total}_{y,z})\right) + \notag \\ &\sum_{y \in \mathcal{O} } \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,energy}_{y,z} \times \Omega^{energy}_{y,z}) + (\pi^{FOM,energy}_{y,z} \times \Delta^{total,energy}_{y,z})\right) + \notag \\ &\sum_{y \in \mathcal{O}^{asym} } \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,charge}_{y,z} \times \Omega^{charge}_{y,z}) + (\pi^{FOM,charge}_{y,z} \times \Delta^{total,charge}_{y,z})\right) + \notag \\ & \sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times(\pi^{VOM}_{y,z} + \pi^{FUEL}_{y,z})\times \Theta_{y,z,t}\right) + \sum_{y \in \mathcal{O \cup DF} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,charge}_{y,z} \times \Pi_{y,z,t}\right) +\notag \\ &\sum_{s \in \mathcal{S} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}}\left(\omega_{t} \times n_{s}^{slope} \times \Lambda_{s,z,t}\right) + \sum_{t \in \mathcal{T} } \left(\omega_{t} \times \pi^{unmet}_{rsv} \times r^{unmet}_{t}\right) \notag \\ &\sum_{y \in \mathcal{H} } \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}}\left(\omega_{t} \times \pi^{START}_{y,z} \times \chi_{s,z,t}\right) + \notag \\ & \sum_{l \in \mathcal{L}}\left(\pi^{TCAP}_{l} \times \bigtriangleup\varphi^{max}_{l}\right) \end{aligned}\]

The first summation represents the fixed costs of generation/discharge over all zones and technologies, which refects the sum of the annualized capital cost, $\pi^{INVEST}_{y,z}$, times the total new capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM}_{y,z}$, times the net installed generation capacity, $\overline{\Omega}^{size}_{y,z} \times \Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).

The second summation corresponds to the fixed cost of installed energy storage capacity and is summed over only the storage resources. This term includes the sum of the annualized energy capital cost, $\pi^{INVEST,energy}_{y,z}$, times the total new energy capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, energy}_{y,z}$, times the net installed energy storage capacity, $\Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).

The third summation corresponds to the fixed cost of installed charging power capacity and is summed over only over storage resources with independent/asymmetric charge and discharge power components ($\mathcal{O}^{asym}$). This term includes the sum of the annualized charging power capital cost, $\pi^{INVEST,charge}_{y,z}$, times the total new charging power capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, energy}_{y,z}$, times the net installed charging power capacity, $\Delta^{total}_{y,z}$ (e.g., existing capacity less retirements plus additions).

The fourth and fifth summations corresponds to the operational cost across all zones, technologies, and time steps. The fourth summation represents the sum of fuel cost, $\pi^{FUEL}_{y,z}$ (if any), plus variable O&M cost, $\pi^{VOM}_{y,z}$ times the energy generation/discharge by generation or storage resources (or demand satisfied via flexible demand resources, $y\in\mathcal{DF}$) in time step $t$, $\Theta_{y,z,t}$, and the weight of each time step $t$, $\omega_t$, where $\omega_t$ is equal to 1 when modeling grid operations over the entire year (8760 hours), but otherwise is equal to the number of hours in the year represented by the representative time step, $t$ such that the sum of $\omega_t \forall t \in T = 8760$, approximating annual operating costs. The fifth summation represents the variable charging O&M cost, $\pi^{VOM,charge}_{y,z}$ times the energy withdrawn for charging by storage resources (or demand deferred by flexible demand resources) in time step $t$ , $\Pi_{y,z,t}$ and the annual weight of time step $t$,$\omega_t$.

The sixth summation represents the total cost of unserved demand across all segments $s$ of a segment-wise price-elastic demand curve, equal to the marginal value of consumption (or cost of non-served energy), $n_{s}^{slope}$, times the amount of non-served energy, $\Lambda_{y,z,t}$, for each segment on each zone during each time step (weighted by $\omega_t$).

The seventh summation represents the total cost of not meeting hourly operating reserve requirements, where $\pi^{unmet}_{rsv}$ is the cost penalty per unit of non-served reserve requirement, and $r^{unmet}_t$ is the amount of non-served reserve requirement in each time step (weighted by $\omega_t$).

The eighth summation corresponds to the startup costs incurred by technologies to which unit commitment decisions apply (e.g. $y \in \mathcal{UC}$), equal to the cost of start-up, $\pi^{START}_{y,z}$, times the number of startup events, $\chi_{y,z,t}$, for the cluster of units in each zone and time step (weighted by $\omega_t$).

The last term corresponds to the transmission reinforcement or construction costs, for each transmission line in the model. Transmission reinforcement costs are equal to the sum across all lines of the product between the transmission reinforcement/construction cost, $\pi^{TCAP}_{l}$, times the additional transmission capacity variable, $\bigtriangleup\varphi^{max}_{l}$. Note that fixed O\&M and replacement capital costs (depreciation) for existing transmission capacity is treated as a sunk cost and not included explicitly in the GenX objective function.

In summary, the objective function can be understood as the minimization of costs associated with five sets of different decisions: (1) where and how to invest on capacity, (2) how to dispatch or operate that capacity, (3) which consumer demand segments to serve or curtail, (4) how to cycle and commit thermal units subject to unit commitment decisions, (5) and where and how to invest in additional transmission network capacity to increase power transfer capacity between zones. Note however that each of these components are considered jointly and the optimization is performed over the whole problem at once as a monolithic co-optimization problem.

Power Balance

The power balance constraint of the model ensures that electricity demand is met at every time step in each zone. As shown in the constraint, electricity demand, $D_{t,z}$, at each time step and for each zone must be strictly equal to the sum of generation, $\Theta_{y,z,t}$, from thermal technologies ($\mathcal{H}$), curtailable VRE ($\mathcal{VRE}$), must-run resources ($\mathcal{MR}$), and hydro resources ($\mathcal{W}$). At the same time, energy storage devices ($\mathcal{O}$) can discharge energy, $\Theta_{y,z,t}$ to help satisfy demand, while when these devices are charging, $\Pi_{y,z,t}$, they increase demand. For the case of flexible demand resources ($\mathcal{DF}$), delaying demand (equivalent to charging virtual storage), $\Pi_{y,z,t}$, decreases demand while satisfying delayed demand (equivalent to discharging virtual demand), $\Theta_{y,z,t}$, increases demand. Price-responsive demand curtailment, $\Lambda_{s,z,t}$, also reduces demand. Finally, power flows, $\Phi_{l,t}$, on each line $l$ into or out of a zone (defined by the network map $\varphi^{map}_{l,z}$), are considered in the demand balance equation for each zone. By definition, power flows leaving their reference zone are positive, thus the minus sign in the below constraint. At the same time losses due to power flows increase demand, and one-half of losses across a line linking two zones are attributed to each connected zone. The losses function $\beta_{l,t}(\cdot)$ will depend on the configuration used to model losses (see Transmission section).

\[\begin{aligned} & \sum_{y\in \mathcal{H}}{\Theta_{y,z,t}} +\sum_{y\in \mathcal{VRE}}{\Theta_{y,z,t}} +\sum_{y\in \mathcal{MR}}{\Theta_{y,z,t}} + \sum_{y\in \mathcal{O}}{(\Theta_{y,z,t}-\Pi_{y,z,t})} + \notag\\ & \sum_{y\in \mathcal{DF}}{(-\Theta_{y,z,t}+\Pi_{y,z,t})} +\sum_{y\in \mathcal{W}}{\Theta_{y,z,t}}+ \notag\\ &+ \sum_{s\in \mathcal{S}}{\Lambda_{s,z,t}} - \sum_{l\in \mathcal{L}}{(\varphi^{map}_{l,z} \times \Phi_{l,t})} -\frac{1}{2} \sum_{l\in \mathcal{L}}{(\varphi^{map}_{l,z} \times \beta_{l,t}(\cdot))} = D_{z,t} \forall z\in \mathcal{Z}, t \in \mathcal{T} \end{aligned}\]

Arguments

  • setup::Dict: Dictionary containing the settings for the model.
  • inputs::Dict: Dictionary containing the inputs for the model.
  • OPTIMIZER::MOI.OptimizerWithAttributes: The optimizer to use for solving the model.

Returns

  • Model: The model object containing the entire optimization problem model to be solved by solve_model.jl
source
GenX.solve_modelFunction
solve_model(EP::Model, setup::Dict)

Description: Solves and extracts solution variables for later processing

Arguments

  • EP::Model: a JuMP model representing the energy optimization problem
  • setup::Dict: a Dict containing GenX setup flags

Returns

  • EP::Model: the solved JuMP model
  • solver_time::Float64: time taken to solve the model
source
GenX.write_outputsFunction
write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)

Function for the entry-point for writing the different output files. From here, onward several other functions are called, each for writing specific output files, like costs, capacities, etc.

source
GenX.mgaFunction
mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)

We have implemented an updated Modeling to Generate Alternatives (MGA) Algorithm proposed by Berntsen and Trutnevyte (2017) to generate a set of feasible, near cost-optimal technology portfolios. This algorithm was developed by Brill Jr, E. D., 1979 and introduced to energy system planning by DeCarolia, J. F., 2011.

To create the MGA formulation, we replace the cost-minimizing objective function of GenX with a new objective function that creates multiple generation portfolios by zone. We further add a new budget constraint based on the optimal objective function value $f^*$ of the least-cost model and the user-specified value of slack $\delta$. After adding the slack constraint, the resulting MGA formulation is given as (MGAAnnualGeneration = 0 in the genx_settings.yml file, or not set):

\[\begin{aligned} \text{max/min} \quad &\sum_{z \in \mathcal{Z}}\sum_{r \in \mathcal{R}} \beta_{z,r}^{k}P_{z,r}\\ \text{s.t.} \quad &P_{z,r} = \sum_{y \in \mathcal{G}}C_{y,z,r} \\ & f \leq f^* + \delta \\ &Ax = b \end{aligned}\]

where, $\beta_{z,r}$ is a random objective function coefficient betwen $[0,1]$ for MGA iteration $k$. We aggregate capacity into a new variable $P_{z,r}$ that represents total capacity from technology type $r$ in a zone $z$.

If the users set MGAAnnualGeneration = 1 in the genx_settings.yml file, the MGA formulation is given as:

\[\begin{aligned} \text{max/min} \quad &\sum_{z \in \mathcal{Z}}\sum_{r \in \mathcal{R}} \beta_{z,r}^{k}P_{z,r}\\ \text{s.t.} \quad &P_{z,r} = \sum_{y \in \mathcal{G}}\sum_{t \in \mathcal{T}} \omega_{t} \Theta_{y,t,z,r} \\ & f \leq f^* + \delta \\ &Ax = b \end{aligned}\]

where, $\beta_{z,r}$ is a random objective function coefficient betwen $[0,1]$ for MGA iteration $k$. $\Theta_{y,t,z,r}$ is a generation of technology $y$ in zone $z$ in time period $t$ that belongs to a resource type $r$. We aggregate $\Theta_{y,t,z,r}$ into a new variable $P_{z,r}$ that represents total generation from technology type $r$ in a zone $z$.

In the second constraint in both the above formulations, $\delta$ denote the increase in budget from the least-cost solution and $f$ represents the expression for the total system cost. The constraint $Ax = b$ represents all other constraints in the power system model. We then solve the formulation with minimization and maximization objective function to explore near optimal solution space.

source

Multi-stage specific functions

GenX.configure_settings_multistageFunction
configure_settings_multistage(settings_path::String)

Reads in the settings from the multi_stage_settings.yml YAML file and merges them with the default multistage settings. It then returns the settings dictionary.

Arguments

  • settings_path::String: The path to the multistage settings YAML file.

Returns

  • settings::Dict: The multistage settings dictionary.
source
GenX.configure_multi_stage_inputsFunction
function configure_multi_stage_inputs(inputs_d::Dict, settings_d::Dict, NetworkExpansion::Int64)

This function overwrites input parameters read in via the load_inputs() method for proper configuration of multi-stage modeling:

  1. Overnight capital costs are computed via the compute_overnight_capital_cost() method and overwrite internal model representations of annualized investment costs.

  2. Annualized fixed O&M costs are scaled up to represent total fixed O&M incured over the length of each model stage (specified by "StageLength" field in multi_stage_settings.yml).

  3. Internal set representations of resources eligible for capacity retirements are overwritten to ensure compatability with multi-stage modeling.

  4. When NetworkExpansion is active and there are multiple model zones, parameters related to transmission and network expansion are updated. First, annualized transmission reinforcement costs are converted into overnight capital costs. Next, the maximum allowable transmission line reinforcement parameter is overwritten by the model stage-specific value specified in the "Line_Max_Flow_Possible_MW" fields in the network_multi_stage.csv file. Finally, internal representations of lines eligible or not eligible for transmission expansion are overwritten based on the updated maximum allowable transmission line reinforcement parameters.

inputs:

  • inputs_d - dict object containing model inputs dictionary generated by load_inputs().
  • settings_d - dict object containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
  • NetworkExpansion - integer flag (0/1) indicating whether network expansion is on, set via the "NetworkExpansion" field in genx_settings.yml.

returns: dictionary containing updated model inputs, to be used in the generate_model() method.

source
GenX.run_ddpFunction
run_ddp(models_d::Dict, setup::Dict, inputs_d::Dict)

This function run the dual dynamic programming (DDP) algorithm, as described in Pereira and Pinto (1991), and more recently, Lara et al. (2018). Note that if the algorithm does not converge within 10,000 (currently hardcoded) iterations, this function will return models with sub-optimal solutions. However, results will still be printed as if the model is finished solving. This sub-optimal termination is noted in the output with the 'Exiting Without Covergence!' message.

inputs:

  • models_d – Dictionary which contains a JuMP model for each model period.
  • setup - Dictionary object containing GenX settings and key parameters.
  • inputs_d – Dictionary of inputs for each model stage, generated by the load_inputs() method.

returns:

  • models_d – Dictionary which contains a JuMP model for each model stage, modified by this method.
  • stats_d – Dictionary which contains the run time, upper bound, and lower bound of each DDP iteration.
  • inputs_d – Dictionary of inputs for each model stage, generated by the load_inputs() method, modified by this method.
source
GenX.write_multi_stage_outputsFunction
write_multi_stage_outputs(stats_d::Dict, 
    outpath::String, 
    settings_d::Dict, 
    inputs_dict::Dict)

This function calls various methods which write multi-stage modeling outputs as .csv files.

Arguments:

  • stats_d: Dictionary which contains the run time, upper bound, and lower bound of each DDP iteration.
  • outpath: String which represents the path to the Results directory.
  • settings_d: Dictionary containing settings configured in the GenX settings genx_settings.yml file as well as the multi-stage settings file multi_stage_settings.yml.
  • inputs_dict: Dictionary containing the input data for the multi-stage model.
source