From 6f168ae49ed1ada44f068b9f824708ac7fd83784 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Mon, 25 Mar 2024 21:54:24 +0000 Subject: [PATCH] build based on 1e3fc34 --- previews/PR670/.documenter-siteinfo.json | 1 + .../commercial_solvers/index.html | 10 + .../examples_casestudies/index.html | 46 + .../model_introduction/index.html | 2 + .../model_notation/index.html | 2 + .../objective_function/index.html | 31 + .../power_balance/index.html | 7 + .../configure_multi_stage_inputs/index.html | 4 + .../dual_dynamic_programming/index.html | 12 + .../endogenous_retirement/index.html | 6 + .../multi_stage_overview/index.html | 2 + .../curtailable_variable_renewable/index.html | 12 + .../Resources/electrolyzers/index.html | 18 + .../Resources/flexible_demand/index.html | 13 + .../hydro_inter_period_linkage/index.html | 17 + .../Resources/hydro_res/index.html | 34 + .../Resources/investment_charge/index.html | 16 + .../Resources/investment_energy/index.html | 16 + .../long_duration_storage/index.html | 17 + .../Resources/maintenance/index.html | 66 ++ .../Resources/must_run/index.html | 5 + .../Resources/resource/index.html | 45 + .../Resources/retrofit/index.html | 4 + .../Resources/storage/index.html | 43 + .../Resources/storage_all/index.html | 2 + .../Resources/storage_asymmetric/index.html | 2 + .../Resources/storage_symmetric/index.html | 2 + .../Resources/thermal/index.html | 6 + .../Resources/thermal_commit/index.html | 46 + .../Resources/thermal_no_commit/index.html | 22 + .../Resources/vre_stor/index.html | 236 ++++ previews/PR670/Model_Reference/TDR/index.html | 3 + .../PR670/Model_Reference/core/index.html | 130 ++ .../Model_Reference/generate_model/index.html | 21 + .../Model_Reference/load_inputs/index.html | 8 + .../maintenance_overview/index.html | 2 + .../Model_Reference/methodofmorris/index.html | 2 + previews/PR670/Model_Reference/mga/index.html | 9 + .../PR670/Model_Reference/policies/index.html | 35 + .../Model_Reference/solve_model/index.html | 2 + .../solver_configuration_api/index.html | 321 +++++ .../utility_functions/index.html | 4 + .../Model_Reference/write_outputs/index.html | 6 + .../PR670/Public_API/public_api/index.html | 28 + .../index.html | 268 +++++ .../index.html | 3 + .../index.html | 244 ++++ .../Tutorial_4_model_generation/index.html | 327 +++++ .../Tutorial_5_solve_model/index.html | 169 +++ .../Tutorial_6_solver_settings/index.html | 330 ++++++ .../Tutorials/Tutorials_intro/index.html | 2 + .../PR670/Tutorials/files/LatexHierarchy.png | Bin 0 -> 93747 bytes .../PR670/Tutorials/files/OneZoneCase.png | Bin 0 -> 324610 bytes .../Tutorials/files/default_settings.png | Bin 0 -> 269573 bytes .../Tutorials/files/genx_settings_none.png | Bin 0 -> 55051 bytes .../PR670/Tutorials/files/genxsettings.png | Bin 0 -> 447480 bytes .../PR670/Tutorials/files/highs_defaults.png | Bin 0 -> 944058 bytes previews/PR670/Tutorials/files/jump_logo.png | Bin 0 -> 30453 bytes .../PR670/Tutorials/files/new_england.png | Bin 0 -> 318652 bytes .../PR670/Tutorials/files/output_14_0.svg | 1 + .../PR670/Tutorials/files/output_58_0.svg | 1 + .../PR670/Tutorials/files/output_65_0.svg | 1 + previews/PR670/Tutorials/files/runcase.png | Bin 0 -> 343629 bytes previews/PR670/Tutorials/files/statenames.csv | 2 + previews/PR670/Tutorials/files/states.csv | 50 + .../PR670/User_Guide/TDR_input/index.html | 2 + .../generate_alternatives/index.html | 2 + .../methodofmorris_input/index.html | 2 + .../User_Guide/model_configuration/index.html | 2 + .../PR670/User_Guide/model_input/index.html | 6 + .../PR670/User_Guide/model_output/index.html | 2 + .../User_Guide/multi_stage_input/index.html | 23 + .../PR670/User_Guide/running_TDR/index.html | 9 + .../PR670/User_Guide/running_model/index.html | 6 + .../slack_variables_overview/index.html | 2 + .../solver_configuration/index.html | 2 + previews/PR670/User_Guide/workflow/index.html | 8 + .../index.html | 2 + .../assets/Dimensions_graphic3_background.png | Bin 0 -> 87834 bytes .../assets/GenX_setup_tutorial_part_1.png | Bin 0 -> 52455 bytes .../assets/GenX_setup_tutorial_part_2.png | Bin 0 -> 95001 bytes previews/PR670/assets/LDES_approach.png | Bin 0 -> 121423 bytes previews/PR670/assets/documenter.js | 1050 +++++++++++++++++ previews/PR670/assets/genx_style.css | 4 + previews/PR670/assets/logo-dark.svg | 118 ++ previews/PR670/assets/logo.svg | 109 ++ previews/PR670/assets/logo_readme.svg | 357 ++++++ .../PR670/assets/themes/documenter-dark.css | 7 + .../PR670/assets/themes/documenter-light.css | 9 + previews/PR670/assets/themeswap.js | 84 ++ previews/PR670/assets/type_hierarchy.png | Bin 0 -> 116425 bytes previews/PR670/assets/vre_stor_module.png | Bin 0 -> 135358 bytes previews/PR670/assets/warner.js | 52 + previews/PR670/developer_guide/index.html | 136 +++ previews/PR670/index.html | 3 + previews/PR670/installation/index.html | 2 + previews/PR670/limitations_genx/index.html | 2 + previews/PR670/objects.inv | Bin 0 -> 12342 bytes previews/PR670/search_index.js | 3 + previews/PR670/siteinfo.js | 1 + previews/PR670/third_party_genx/index.html | 2 + 101 files changed, 4721 insertions(+) create mode 100644 previews/PR670/.documenter-siteinfo.json create mode 100644 previews/PR670/Getting_Started/commercial_solvers/index.html create mode 100644 previews/PR670/Getting_Started/examples_casestudies/index.html create mode 100644 previews/PR670/Model_Concept_Overview/model_introduction/index.html create mode 100644 previews/PR670/Model_Concept_Overview/model_notation/index.html create mode 100644 previews/PR670/Model_Concept_Overview/objective_function/index.html create mode 100644 previews/PR670/Model_Concept_Overview/power_balance/index.html create mode 100644 previews/PR670/Model_Reference/Multi_Stage/configure_multi_stage_inputs/index.html create mode 100644 previews/PR670/Model_Reference/Multi_Stage/dual_dynamic_programming/index.html create mode 100644 previews/PR670/Model_Reference/Multi_Stage/endogenous_retirement/index.html create mode 100644 previews/PR670/Model_Reference/Multi_Stage/multi_stage_overview/index.html create mode 100644 previews/PR670/Model_Reference/Resources/curtailable_variable_renewable/index.html create mode 100644 previews/PR670/Model_Reference/Resources/electrolyzers/index.html create mode 100644 previews/PR670/Model_Reference/Resources/flexible_demand/index.html create mode 100644 previews/PR670/Model_Reference/Resources/hydro_inter_period_linkage/index.html create mode 100644 previews/PR670/Model_Reference/Resources/hydro_res/index.html create mode 100644 previews/PR670/Model_Reference/Resources/investment_charge/index.html create mode 100644 previews/PR670/Model_Reference/Resources/investment_energy/index.html create mode 100644 previews/PR670/Model_Reference/Resources/long_duration_storage/index.html create mode 100644 previews/PR670/Model_Reference/Resources/maintenance/index.html create mode 100644 previews/PR670/Model_Reference/Resources/must_run/index.html create mode 100644 previews/PR670/Model_Reference/Resources/resource/index.html create mode 100644 previews/PR670/Model_Reference/Resources/retrofit/index.html create mode 100644 previews/PR670/Model_Reference/Resources/storage/index.html create mode 100644 previews/PR670/Model_Reference/Resources/storage_all/index.html create mode 100644 previews/PR670/Model_Reference/Resources/storage_asymmetric/index.html create mode 100644 previews/PR670/Model_Reference/Resources/storage_symmetric/index.html create mode 100644 previews/PR670/Model_Reference/Resources/thermal/index.html create mode 100644 previews/PR670/Model_Reference/Resources/thermal_commit/index.html create mode 100644 previews/PR670/Model_Reference/Resources/thermal_no_commit/index.html create mode 100644 previews/PR670/Model_Reference/Resources/vre_stor/index.html create mode 100644 previews/PR670/Model_Reference/TDR/index.html create mode 100644 previews/PR670/Model_Reference/core/index.html create mode 100644 previews/PR670/Model_Reference/generate_model/index.html create mode 100644 previews/PR670/Model_Reference/load_inputs/index.html create mode 100644 previews/PR670/Model_Reference/maintenance_overview/index.html create mode 100644 previews/PR670/Model_Reference/methodofmorris/index.html create mode 100644 previews/PR670/Model_Reference/mga/index.html create mode 100644 previews/PR670/Model_Reference/policies/index.html create mode 100644 previews/PR670/Model_Reference/solve_model/index.html create mode 100644 previews/PR670/Model_Reference/solver_configuration_api/index.html create mode 100644 previews/PR670/Model_Reference/utility_functions/index.html create mode 100644 previews/PR670/Model_Reference/write_outputs/index.html create mode 100644 previews/PR670/Public_API/public_api/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_1_configuring_settings/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_2_network_visualization/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_3_K-means_time_domain_reduction/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_4_model_generation/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_5_solve_model/index.html create mode 100644 previews/PR670/Tutorials/Tutorial_6_solver_settings/index.html create mode 100644 previews/PR670/Tutorials/Tutorials_intro/index.html create mode 100644 previews/PR670/Tutorials/files/LatexHierarchy.png create mode 100644 previews/PR670/Tutorials/files/OneZoneCase.png create mode 100644 previews/PR670/Tutorials/files/default_settings.png create mode 100644 previews/PR670/Tutorials/files/genx_settings_none.png create mode 100644 previews/PR670/Tutorials/files/genxsettings.png create mode 100644 previews/PR670/Tutorials/files/highs_defaults.png create mode 100644 previews/PR670/Tutorials/files/jump_logo.png create mode 100644 previews/PR670/Tutorials/files/new_england.png create mode 100644 previews/PR670/Tutorials/files/output_14_0.svg create mode 100644 previews/PR670/Tutorials/files/output_58_0.svg create mode 100644 previews/PR670/Tutorials/files/output_65_0.svg create mode 100644 previews/PR670/Tutorials/files/runcase.png create mode 100644 previews/PR670/Tutorials/files/statenames.csv create mode 100644 previews/PR670/Tutorials/files/states.csv create mode 100644 previews/PR670/User_Guide/TDR_input/index.html create mode 100644 previews/PR670/User_Guide/generate_alternatives/index.html create mode 100644 previews/PR670/User_Guide/methodofmorris_input/index.html create mode 100644 previews/PR670/User_Guide/model_configuration/index.html create mode 100644 previews/PR670/User_Guide/model_input/index.html create mode 100644 previews/PR670/User_Guide/model_output/index.html create mode 100644 previews/PR670/User_Guide/multi_stage_input/index.html create mode 100644 previews/PR670/User_Guide/running_TDR/index.html create mode 100644 previews/PR670/User_Guide/running_model/index.html create mode 100644 previews/PR670/User_Guide/slack_variables_overview/index.html create mode 100644 previews/PR670/User_Guide/solver_configuration/index.html create mode 100644 previews/PR670/User_Guide/workflow/index.html create mode 100644 previews/PR670/additional_third_party_extensions/index.html create mode 100644 previews/PR670/assets/Dimensions_graphic3_background.png create mode 100644 previews/PR670/assets/GenX_setup_tutorial_part_1.png create mode 100644 previews/PR670/assets/GenX_setup_tutorial_part_2.png create mode 100644 previews/PR670/assets/LDES_approach.png create mode 100644 previews/PR670/assets/documenter.js create mode 100644 previews/PR670/assets/genx_style.css create mode 100644 previews/PR670/assets/logo-dark.svg create mode 100644 previews/PR670/assets/logo.svg create mode 100644 previews/PR670/assets/logo_readme.svg create mode 100644 previews/PR670/assets/themes/documenter-dark.css create mode 100644 previews/PR670/assets/themes/documenter-light.css create mode 100644 previews/PR670/assets/themeswap.js create mode 100644 previews/PR670/assets/type_hierarchy.png create mode 100644 previews/PR670/assets/vre_stor_module.png create mode 100644 previews/PR670/assets/warner.js create mode 100644 previews/PR670/developer_guide/index.html create mode 100644 previews/PR670/index.html create mode 100644 previews/PR670/installation/index.html create mode 100644 previews/PR670/limitations_genx/index.html create mode 100644 previews/PR670/objects.inv create mode 100644 previews/PR670/search_index.js create mode 100644 previews/PR670/siteinfo.js create mode 100644 previews/PR670/third_party_genx/index.html diff --git a/previews/PR670/.documenter-siteinfo.json b/previews/PR670/.documenter-siteinfo.json new file mode 100644 index 0000000000..c2a46738d8 --- /dev/null +++ b/previews/PR670/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.10.2","generation_timestamp":"2024-03-25T21:54:14","documenter_version":"1.3.0"}} \ No newline at end of file diff --git a/previews/PR670/Getting_Started/commercial_solvers/index.html b/previews/PR670/Getting_Started/commercial_solvers/index.html new file mode 100644 index 0000000000..eeaa4570ab --- /dev/null +++ b/previews/PR670/Getting_Started/commercial_solvers/index.html @@ -0,0 +1,10 @@ + +Commertial solvers · GenX.jl

Using commercial solvers: Gurobi or CPLEX

If you want to use the commercial solvers Gurobi or CPLEX:

  • Make sure you have a valid license and the actual solvers for either of Gurobi or CPLEX installed on your machine
  • Add Gurobi or CPLEX to the Julia Project.
$ julia --project=/home/youruser/GenX
+
+julia> <press close-bracket ] to access the package manager>
+(GenX) pkg> add Gurobi
+-or-
+(GenX) pkg> add CPLEX
  • Edit the Run.jl file to use the commercial solver. For example, to use Gurobi, you can add the following lines to the Run.jl file:
using Gurobi
+using GenX
+
+run_genx_case!(dirname(@__FILE__), Gurobi.Optimizer)
Warning

Note that if you have not already installed the required Julia packages or you do not have a valid Gurobi license on your host machine, you will receive an error message and Run.jl will not run to completion.

diff --git a/previews/PR670/Getting_Started/examples_casestudies/index.html b/previews/PR670/Getting_Started/examples_casestudies/index.html new file mode 100644 index 0000000000..cb1aba6aab --- /dev/null +++ b/previews/PR670/Getting_Started/examples_casestudies/index.html @@ -0,0 +1,46 @@ + +Running GenX · GenX.jl

Running GenX

This section describes how to run GenX with the examples provided in the repository and with user-defined cases. To have a deeper understanding of how to structure the input files and the settings, please refer to the User Guide.

Example cases

GenX repository contains several examples to get you started with GenX. These examples are located in the example_systems folder of the repository and are designed to be easy to run and to demonstrate the main features of GenX.

The available examples are:

Note

The following instructions assume that you have already installed GenX and its dependencies. If you haven't, please follow the instructions in the Installation Guide.

To run an example, follow these steps:

  1. Open a terminal and run Julia with an environment containing GenX;
  2. Run the Run.jl file located in the example folder.

For example, to run the 1_three_zones example, you can use the following commands:

$ julia
+julia> <press close-bracket ] to access the package manager>
+(@v1.9) pkg> activate /path/to/env
+julia> using GenX
+julia> include("/path/to/GenX/example_systems/1_three_zones/Run.jl")

where /path/to/env is the path to the environment containing GenX and /path/to/GenX is the path to the GenX repository containing the examples.

The Run.jl file will read the .csv input files which define the system, the resources, and the policies, will solve the model, and finally will write the results in a results folder located in the same directory as Run.jl.

Tip

You could also run the example from the terminal using the following command:

$ julia --project=/path/to/env /path/to/GenX/example_systems/1_three_zones/Run.jl`

This is equivalent to open a Julia REPL and call the Run.jl file using the include function. The first option is recommended if you want to run GenX multiple times with different settings because it avoids the overhead of recompiling the code every time you run it.

Note

The default solver for GenX is HiGHS.

For more information on what happens when you run a GenX case, see the Running GenX section.

Note

The first seven examples are based on a one-year example with hourly resolution, containing zones representing Massachusetts, Connecticut, and Maine. The ten represented resources include natural gas, solar PV, wind, and lithium-ion battery storage.

Running GenX with user-defined cases

To run GenX with a user-defined case, you need to create a folder MyCase with the following structure:

MyCase
+├── settings/
+├── system/
+├── policies/
+├── resources/
+├── README.md
+└── Run.jl

where the settings folder contains the configuration files for the model and the solver, the system folder contains the .csv input files related to the system under study, the resource folder contains the .csv input files with the list of generators to include in the model, and the policies folder contains the .csv input files which define the policies to be included in the model. For instance, one case could have the following structure:

MyCase
+│ 
+├── settings
+│   ├── genx_settings.yml           # GenX settings
+│   ├── [solver_name]_settings.yml  # Solver settings
+│   ├── multi_stage_settings.yml    # Multi-stage settings
+│   └── time_domain_reduction.yml   # Time-domain clustering settings
+│ 
+├── system
+│   ├── Demand_data.csv
+│   ├── Fuel_data.csv
+│   ├── Generators_variability.csv
+│   └── Network.csv
+│ 
+├── policies
+│   ├── CO2_cap.csv
+│   ├── Minimum_capacity_requirement.csv
+│   └── Energy_share_requirement.csv
+│ 
+├── resources
+│   ├── Thermal.csv
+│   ├── Storage.csv
+│   ├── Vre.csv
+│   ├── Hydro.csv
+│   └── policy_assignments
+|       ├── Resource_minimum_capacity_requirement.csv
+│       └── Resource_energy_share_requirement.csv
+│
+└── Run.jl

In this example, MyCase will define a case with Themal, Storage, Vre, and Hydro resources, the system folder will provide the data for the demand, fuel, generators' variability, and network, the policies folder will include a CO2 cap, a minimum capacity requirement, and an energy share requirement, and the settings folder will contain the configuration files for the model.

The Run.jl file should contain the following code:

using GenX
+
+run_genx_case!(dirname(@__FILE__))

which will run the case using the default solver. To use a different solver, you can pass the Optimizer object as an argument to run_genx_case! function. For example, to use Gurobi as the solver, you can use the following code:

using GenX
+using Gurobi
+
+run_genx_case!(dirname(@__FILE__), Gurobi.Optimizer)

To run the case, open a terminal and run the following command:

$ julia --project="/path/to/env"
+julia> include("/path/to/MyCase/Run.jl")

where /path/to/env is the path to the environment with GenX installed, and /path/to/MyCase is the path to the folder of the MyCase case. Alternatively, you can run the case directly from the terminal using the following command:

$ julia --project="/path/to/env" /path/to/MyCase/Run.jl

What happens when you run a case

Added in 0.3.4

The entry point for running a GenX case is the run_genx_case!("path/to/case") function, where path/to/case is the path to the case directory that contains the .csv files with the inputs for GenX and the settings folder with the configuration files.

The following are the main steps performed in this function:

  1. Establish path to environment setup files and GenX source files.
  2. Read in model settings genx_settings.yml from the example directory.
  3. Configure solver settings.
  4. Load the model inputs from the example directory and perform time-domain clustering if required.
  5. Generate a GenX model instance.
  6. Solve the model.
  7. Write the output files to a specified directory.

After the script runs to completion, results will be written to a folder called results, located in the current working directory.

diff --git a/previews/PR670/Model_Concept_Overview/model_introduction/index.html b/previews/PR670/Model_Concept_Overview/model_introduction/index.html new file mode 100644 index 0000000000..d2313a1295 --- /dev/null +++ b/previews/PR670/Model_Concept_Overview/model_introduction/index.html @@ -0,0 +1,2 @@ + +Model Introduction · GenX.jl

GenX Model Introduction

Introduction

GenX allows for the simultaneous co-optimization of several interlinked power system decision layers, described below:

  1. Capacity expansion planning (e.g., investment and retirement decisions for a full range of centralized and distributed generation, storage, and demand-side resources)
  2. Hourly dispatch of generation, storage, and demand-side resources,
  3. Unit commitment decisions and operational constraints for thermal generators,
  4. Commitment of generation, storage, and demand-side capacity to meet system operating reserves requirements,
  5. Commitment of generation, storage, and demand-side capacity to meet capacity reserve requirements,
  6. Transmission network power flows (including losses) and network expansion decisions, and
  7. Several optional policy constraints

Depending on the dimensionality of the problem, it may not be possible to model all decision layers at the highest possible resolution of detail, so the GenX model is designed to be highly configurable, allowing the user to specify the level of detail or abstraction along each of these layers or to omit one or more layers from consideration entirely.

For example, while investment and dispatch decisions (Layers 1 and 2) are a consistent feature of the model under all configurations, the user has several options with regards to representing the operational constraints on various thermal power plants (e.g., coal, gas, nuclear, and biomass generators). Unit commitment (e.g., start-up and shut-down) decisions Morales-España et al., 2013 (Layer 3) can be modeled at the individual power plant level (as per De Sisternes Jimenez, 2014); by using an efficient clustering of similar or identical units (as per Palmintier, 2011, Palmintier, 2013, Palmintier, 2014); by using a linear relaxation (or convex hull) of the integer unit commitment constraints set; or ignoring unit commitment decisions entirely and treating generator output as fully continuous. Furthermore, different levels of resolution can be selected for each individual resource type, as desired (e.g., larger thermal units can be represented with integer unit commitment decisions while smaller units can be treated as fully continuous). In such a manner, the model can be configured to represent operating constraints on thermal generators at a level of resolution that achieves a desired balance between abstraction error and computational tractability and provides sufficient accuracy to generate insights for the problem at hand.

The model can also be configured to consider commitment of capacity to supply frequency regulation (symmetric up and down) and operating reserves (up) needed by system operators to robustly resolve short-term uncertainty in demand and renewable energy forecasts and power plant or transmission network failures (Layer 4). Alternatively, reserve commitments can be ignored if desired.

Additionally, the model can approximate resource adequacy requirements through capacity reserve margin requirements at the zonal and/or system level (Layer 5). In this way, the model can approximate varying structure of capacity markets seen in deregulated electricity markets in the U.S. and other regions.

The model also allows for transmission networks to be represented at several levels of detail (Layer 6) including at a zonal level with transport constraints on power flows between zones (as per Mai et al., 2013, Johnston et al., 2013, Hirth, 2017); or as a single zone problem where transmission constraints and flows are ignored. (A DC optimal power flow formulation is in development.) In cases where a nodal or zonal transmission model is employed, network capacity expansion decisions can be modeled or ignored, and transmission losses can be represented either as a linear function of power flows or a piecewise linear approximation of a quadratic function of power flows between nodes or zones (as per Zhang et al., 2013, Fitiwi et al., 2016), with the number of segments in the piecewise approximation specified by the user as desired. In a multi-zonal or nodal configuration, GenX can therefore consider siting generators in different locations, including balancing tradeoffs between access to different renewable resource quality, siting restrictions, and impacts on network congestions, power flows and losses.

GenX also allows the user to specify several optional public policy constraints, such as CO2 emissions limits, minimum energy share requirements (such as renewable portfolio standard or clean energy standard policies), and minimum technology capacity requirements (e.g. technology deployment mandates).

Finally, the model is usually configured to consider a full year of operating decisions at an hourly resolution, but as this is often not tractable when considering large-scale problems with high resolution in other dimensions, GenX is also designed to model a number of subperiods – typically multiday periods of chronologically sequential hourly operating decisions – that can be selected via appropriate statistical clustering methods to represent a full year of operations (De Sisternes Jimenez and Webster, 2013, De Sisternes Jimenez, 2014, Poncelet et al., 2016, Nahmmacher et al., 2016, Blanford et al., 2016, Merrick, 2016, Mallapragada et al., 2018). GenX ships with a built-in time-domain reduction package that uses k-means or k-medoids to cluster raw time series data for demand (load) profiles and resource capacity factor profiles into representative periods during the input processing stage of the model. This method can also consider extreme points in the time series to capture noteworthy periods or periods with notably poor fits.

With appropriate configuration of the model, GenX thus allows the user to tractably consider several interlinking decision layers in a single, monolithic optimization problem that would otherwise have been necessary to solve in different separated stages or models. The following figure reflects the range of configurations currently possible along the three key dimensions of chronological detail, operational detail, and network detail.

Range of configurations currently implemented in GenX along three key dimensions of model resolution Figure. Range of configurations currently implemented in GenX along three key dimensions of model resolution

The model can be configured to consider a single future planning year or multiple planning stages (or investment periods) in sequence.

  • In single-stage planning mode, the model formulation is static, in the sense that its objective is not to determine when investments should take place over time, but rather to produce a snapshot of the minimum-cost generation capacity mix to meet demand at least cost under some pre-specified future conditions.
  • The user can formulate and solve a deterministic multi-stage planning problem with perfect foresight i.e. demand, cost, and policy assumptions about all stages are known and exploited to determine the least-cost investment trajectory for the entire period. The solution of this multi-stage problem relies on exploiting the decomposable nature of the multi-stage problem via the implementation of the dual dynamic programming algorithm, described in Lara et al. 2018 here.
  • The user can formulate a sequential, myopic multi-stage planning problem, where the model solves a sequence of single-stage investment planning problems wherein investment decisions in each stage are individually optimized to meet demand given assumptions for the current planning stage and with investment decisions from previous stages treated as inputs for the current stage. We refer to this as "myopic" (or shortsighted) mode since the solution does not account for information about future stages in determining investments for a given stage. This version is generally more computationally efficient than the deterministic multi-stage expansion with perfect foresight mode.

More information on the two sequential, multi-stage planning modes can be found in the section on Multi-stage under the Model function reference tab.

Uses

From a centralized planning perspective, the GenX model can help to determine the investments needed to supply future electricity demand at minimum cost, as is common in least-cost utility planning or integrated resource planning processes. In the context of liberalized markets, the model can be used by regulators and policy makers for indicative energy planning or policy analysis in order to establish a long-term vision of efficient market and policy outcomes. The model can also be used for techno-economic assessment of emerging electricity generation, storage, and demand-side resources and to enumerate the effect of parametric uncertainty (e.g., technology costs, fuel costs, demand, policy decisions) on the system-wide value or role of different resources.

diff --git a/previews/PR670/Model_Concept_Overview/model_notation/index.html b/previews/PR670/Model_Concept_Overview/model_notation/index.html new file mode 100644 index 0000000000..c32bedbdff --- /dev/null +++ b/previews/PR670/Model_Concept_Overview/model_notation/index.html @@ -0,0 +1,2 @@ + +Notation · GenX.jl

Model Notation

Model Indices and Sets


NotationDescription
$t \in \mathcal{T}$where $t$ denotes an time step and $\mathcal{T}$ is the set of time steps over which grid operations are modeled
$\mathcal{T}^{interior} \subseteq \mathcal{T}^{}$where $\mathcal{T}^{interior}$ is the set of interior timesteps in the data series
$\mathcal{T}^{start} \subseteq \mathcal{T}$where $\mathcal{T}^{start}$ is the set of initial timesteps in the data series. $\mathcal{T}^{start}={1}$ when representing entire year as a single contiguous period; $\mathcal{T}^{start}=\{\left(m-1\right) \times \tau^{period}+1 | m \in \mathcal{M}\}$, which corresponds to the first time step of each representative period $m \in \mathcal{M}$
$n \in \mathcal{N}$where $n$ corresponds to a contiguous time period and $\mathcal{N}$ corresponds to the set of contiguous periods of length $\tau^{period}$ that make up the input time series (e.g. demand, variable renewable energy availability) to the model
$\mathcal{N}^{rep} \subseteq \mathcal{N}$where $\mathcal{N}^{rep}$ corresponds to the set of representative time periods that are selected from the set of contiguous periods, $\mathcal{M}$
$m \in \mathcal{M}$where $m$ corresponds to a representative time period and $\mathcal{M}$ corresponds to the set of representative time periods indexed as per their chronological ocurrence in the set of contiguous periods spanning the input time series data, i.e. $\mathcal{N}$
$z \in \mathcal{Z}$where $z$ denotes a zone and $\mathcal{Z}$ is the set of zones in the network
$l \in \mathcal{L}$where $l$ denotes a line and $\mathcal{L}$ is the set of transmission lines in the network
$y \in \mathcal{G}$where $y$ denotes a technology and $\mathcal{G}$ is the set of available technologies
$\mathcal{H} \subseteq \mathcal{G}$where $\mathcal{H}$ is the subset of thermal resources
$\mathcal{VRE} \subseteq \mathcal{G}$where $\mathcal{VRE}$ is the subset of curtailable Variable Renewable Energy (VRE) resources
$\overline{\mathcal{VRE}}^{y,z}$set of VRE resource bins for VRE technology type $y \in \mathcal{VRE}$ in zone $z$
$\mathcal{CE} \subseteq \mathcal{G}$where $\mathcal{CE}$ is the subset of resources qualifying for the clean energy standard policy constraint
$\mathcal{UC} \subseteq \mathcal{H}$where $\mathcal{UC}$ is the subset of thermal resources subject to unit commitment constraints
$s \in \mathcal{S}$where $s$ denotes a segment and $\mathcal{S}$ is the set of consumers segments for price-responsive demand curtailment
$\mathcal{O} \subseteq \mathcal{G}$where $\mathcal{O}$ is the subset of storage resources excluding heat storage and hydro storage
$o \in \mathcal{O}$where $o$ denotes a storage technology in a set $\mathcal{O}$
$\mathcal{O}^{sym} \subseteq \mathcal{O}$where $\mathcal{O}^{sym}$ corresponds to the set of energy storage technologies with equal (or symmetric) charge and discharge power capacities
$\mathcal{O}^{asym} \subseteq \mathcal{O}$where $\mathcal{O}^{asym}$ corresponds to the set of energy storage technologies with independently sized (or asymmetric) charge and discharge power capacities
$\mathcal{O}^{LDES} \subseteq \mathcal{O}$where $\mathcal{O}^{LDES}$ corresponds to the set of long-duration energy storage technologies for which inter-period energy exchange is permitted when using representative periods to model annual grid operations
$\mathcal{VS} \subseteq \mathcal{G}$where $\mathcal{VS}$ is the subset of co-located VRE and storage resources
$\mathcal{VS}^{pv} \subseteq \mathcal{VS}$where $\mathcal{VS}^{pv}$ corresponds to the set of co-located VRE and storage resources with a solar PV component
$\mathcal{VS}^{wind} \subseteq \mathcal{VS}$where $\mathcal{VS}^{wind}$ corresponds to the set of co-located VRE and storage resources with a wind component
$\mathcal{VS}^{inv} \subseteq \mathcal{VS}$where $\mathcal{VS}^{inv}$ corresponds to the set of co-located VRE and storage resources with an inverter component
$\mathcal{VS}^{stor} \subseteq \mathcal{VS}$where $\mathcal{VS}^{stor}$ corresponds to the set of co-located VRE and storage resources with a storage component
$\mathcal{VS}^{sym, dc} \subseteq \mathcal{VS}$where $\mathcal{VS}^{sym, dc}$ corresponds to the set of co-located VRE and storage resources with a storage DC component with equal (or symmetric) charge and discharge power capacities
$\mathcal{VS}^{sym, ac} \subseteq \mathcal{VS}$where $\mathcal{VS}^{sym, ac}$ corresponds to the set of co-located VRE and storage resources with a storage AC component with equal (or symmetric) charge and discharge power capacities
$\mathcal{VS}^{asym, dc, dis} \subseteq \mathcal{VS}$where $\mathcal{VS}^{asym, dc, dis}$ corresponds to the set of co-located VRE and storage resources with a storage DC component with independently sized (or asymmetric) discharge power capabilities
$\mathcal{VS}^{asym, dc, cha} \subseteq \mathcal{VS}$where $\mathcal{VS}^{asym, dc, cha}$ corresponds to the set of co-located VRE and storage resources with a storage DC component with independently sized (or asymmetric) charge power capabilities
$\mathcal{VS}^{asym, ac, dis} \subseteq \mathcal{VS}$where $\mathcal{VS}^{asym, ac, dis}$ corresponds to the set of co-located VRE and storage with a storage AC component with independently sized (or asymmetric) discharge power capabilities
$\mathcal{VS}^{asym, ac, cha} \subseteq \mathcal{VS}$where $\mathcal{VS}^{asym, ac, cha}$ corresponds to the set of co-located VRE and storage resources with a storage AC component with independently sized (or asymmetric) charge power capabilities
$\mathcal{VS}^{LDES} \subseteq \mathcal{VS}$where $\mathcal{VS}^{LDES}$ corresponds to the set of co-located VRE and storage resources with a long-duration energy storage component for which inter-period energy exchange is permitted when using representative periods to model annual grid operations
$\mathcal{W} \subseteq \mathcal{G}$where $\mathcal{W}$ set of hydroelectric generators with water storage reservoirs
$\mathcal{W}^{nocap} \subseteq \mathcal{W}$where $\mathcal{W}^{nocap}$ is a subset of set of $ \mathcal{W}$ and represents resources with unknown reservoir capacity
$\mathcal{W}^{cap} \subseteq \mathcal{W}$where $\mathcal{W}^{cap}$ is a subset of set of $ \mathcal{W}$ and represents resources with known reservoir capacity
$\mathcal{MR} \subseteq \mathcal{G}$where $\mathcal{MR}$ set of must-run resources
$\mathcal{DF} \subseteq \mathcal{G}$where $\mathcal{DF}$ set of flexible demand resources
$\mathcal{ELECTROLYZER} \subseteq \mathcal{G}$where $\mathcal{ELECTROLYZER}$ set of electrolyzer resources (optional set)
$\mathcal{G}_p^{ESR} \subseteq \mathcal{G}$where $\mathcal{G}_p^{ESR}$ is a subset of $\mathcal{G}$ that is eligible for Energy Share Requirement (ESR) policy constraint $p$
$p \in \mathcal{P}$where $p$ denotes a instance in the policy set $\mathcal{P}$
$\mathcal{P}^{ESR} \subseteq \mathcal{P}$Energy Share Requirement type policies
$\mathcal{P}^{CO_2} \subseteq \mathcal{P}$CO$_2$ emission cap policies
$\mathcal{P}^{CO_2}_{mass} \subseteq \mathcal{P}^{CO_2}$CO$_2$ emissions limit policy constraints, mass-based
$\mathcal{P}^{CO_2}_{demand} \subseteq \mathcal{P}^{CO_2}$CO$_2$ emissions limit policy constraints, demand and emission-rate based
$\mathcal{P}^{CO_2}_{gen} \subseteq \mathcal{P}^{CO_2}$CO$_2$ emissions limit policy constraints, generation emission-rate based
$\mathcal{P}^{CRM} \subseteq \mathcal{P}$Capacity reserve margin (CRM) type policy constraints
$\mathcal{P}^{MinTech} \subseteq \mathcal{P}$Minimum Capacity Carve-out type policy constraint
$\mathcal{Z}^{ESR}_{p} \subseteq \mathcal{Z}$set of zones eligible for ESR policy constraint $p \in \mathcal{P}^{ESR}$
$\mathcal{Z}^{CRM}_{p} \subseteq \mathcal{Z}$set of zones that form the locational deliverable area for capacity reserve margin policy constraint $p \in \mathcal{P}^{CRM}$
$\mathcal{Z}^{CO_2}_{p,mass} \subseteq \mathcal{Z}$set of zones are under the emission cap mass-based cap-and-trade policy constraint $p \in \mathcal{P}^{CO_2}_{mass}$
$\mathcal{Z}^{CO_2}_{p,demand} \subseteq \mathcal{Z}$set of zones are under the emission cap demand-and-emission-rate based cap-and-trade policy constraint $p \in \mathcal{P}^{CO_2}_{demand}$
$\mathcal{Z}^{CO_2}_{p,gen} \subseteq \mathcal{Z}$set of zones are under the emission cap generation emission-rate based cap-and-trade policy constraint $p \in \mathcal{P}^{CO2,gen}$
$\mathcal{L}_p^{in} \subseteq \mathcal{L}$The subset of transmission lines entering Locational Deliverability Area of capacity reserve margin policy $p \in \mathcal{P}^{CRM}$
$\mathcal{L}_p^{out} \subseteq \mathcal{L}$The subset of transmission lines leaving Locational Deliverability Area of capacity reserve margin policy $p \in \mathcal{P}^{CRM}$
$\mathcal{Qualified} \subseteq \mathcal{G}$where $\mathcal{Qualified}$ is the subset of generation and storage resources eligible to supply electrolyzers within the same zone (optional set)

Decision Variables


NotationDescription
$\Omega_{y,z} \in \mathbb{R}_+$Installed capacity in terms of the number of units (each unit, being of size $\overline{\Omega}_{y,z}^{size}$) of resource $y$ in zone $z$ [Dimensionless] (Note that for co-located VRE and storage resources, this value represents the installed capacity of the grid connection in [MW AC])
$\Omega^{energy}_{y,z} \in \mathbb{R}_+$Installed energy capacity of resource $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that for co-located VRE and storage resources, this value represents the installed capacity of the storage component in MWh)
$\Omega^{charge}_{y,z} \in \mathbb{R}_+$Installed charging power capacity of resource $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}^{asym}$ [MW]
$\Omega^{pv}_{y,z} \in \mathbb{R}_+$Installed solar PV capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\Omega^{wind}_{y,z} \in \mathbb{R}_+$Installed wind capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\Omega^{inv}_{y,z} \in \mathbb{R}_+$Installed inverter capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\Omega^{dc,dis}_{y,z} \in \mathbb{R}_+$Installed storage DC discharge capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\Omega^{dc,cha}_{y,z} \in \mathbb{R}_+$Installed storage DC charge capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\Omega^{ac,dis}_{y,z} \in \mathbb{R}_+$Installed storage AC discharge capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\Omega^{ac,cha}_{y,z} \in \mathbb{R}_+$Installed storage AC charge capacity of resource $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [MW AC]
$\Delta_{y,z} \in \mathbb{R}_+$Retired capacity of technology $y$ from existing capacity in zone $z$ [MW] (Note that for co-located VRE and storage resources, this value represents the retired capacity of the grid connection in MW AC)
$\Delta^{energy}_{y,z} \in \mathbb{R}_+$Retired energy capacity of technology $y$ from existing capacity in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that for co-located VRE and storage resources, this value represents the retired capacity of the storage component in MWh)
$\Delta^{charge}_{y,z} \in \mathbb{R}_+$Retired charging capacity of technology $y$ from existing capacity in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}^{asym}$[MW]
$\Delta^{pv}_{y,z} \in \mathbb{R}_+$Retired solar PV capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\Delta^{wind}_{y,z} \in \mathbb{R}_+$Retired wind capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\Delta^{inv}_{y,z} \in \mathbb{R}_+$Retired inverter capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\Delta^{dc,dis}_{y,z} \in \mathbb{R}_+$Retired storage DC discharge capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\Delta^{dc,cha}_{y,z} \in \mathbb{R}_+$Retired storage DC charge capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\Delta^{ac,dis}_{y,z} \in \mathbb{R}_+$Retired storage AC discharge capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\Delta^{ac,cha}_{y,z} \in \mathbb{R}_+$Retired storage AC charge capacity of technology $y$ from existing capacity in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [MW AC]
$\Delta_{y,z}^{total} \in \mathbb{R}_+$Total installed capacity of technology $y$ in zone $z$ [MW] (Note that co-located VRE and storage resources, this value represents the total capacity of the grid connection in MW AC)
$\Delta_{y,z}^{total,energy} \in \mathbb{R}_+$Total installed energy capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that co-located VRE and storage resources, this value represents the total installed energy capacity of the storage component in MWh)
$\Delta_{y,z}^{total,charge} \in \mathbb{R}_+$Total installed charging power capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}^{asym}$ [MW]
$\Delta_{y,z}^{total,pv} \in \mathbb{R}_+$Total installed solar PV capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\Delta_{y,z}^{total,wind} \in \mathbb{R}_+$Total installed wind capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\Delta_{y,z}^{total,inv} \in \mathbb{R}_+$Total installed inverter capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\Delta_{y,z}^{total,dc,dis} \in \mathbb{R}_+$Total installed storage DC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\Delta_{y,z}^{total,dc,cha} \in \mathbb{R}_+$Total installed storage DC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\Delta_{y,z}^{total,ac,dis} \in \mathbb{R}_+$Total installed storage AC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\Delta_{y,z}^{total,ac,cha} \in \mathbb{R}_+$Total installed storage AC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [MW AC]
$\bigtriangleup\varphi^{max}_{l}$Additional transmission capacity added to line $l$ [MW]
$\Theta_{y,z,t} \in \mathbb{R}_+$Energy injected into the grid by technology $y$ at time step $t$ in zone $z$ [MWh]
$\Theta^{pv}_{y,z,t} \in \mathbb{R}_+$Energy generated by the solar PV component into the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MWh]
$\Theta^{wind}_{y,z,t} \in \mathbb{R}_+$Energy generated by the wind component into the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MWh]
$\Theta^{dc}_{y,z,t} \in \mathbb{R}_+$Energy discharged by the storage DC component into the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a discharge DC component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,dis}$ [MWh]
$\Theta^{ac}_{y,z,t} \in \mathbb{R}_+$Energy discharged by the storage AC component into the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a discharge AC component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,dis}$ [MWh]
$\Pi_{y,z,t} \in \mathbb{R}_+$Energy withdrawn from grid by technology $y$ at time step $t$ in zone $z$ [MWh]
$\Pi^{dc}_{y,z,t} \in \mathbb{R}_+$Energy withdrawn from the VRE and grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a charge DC component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,cha}$ [MWh]
$\Pi^{ac}_{y,z,t} \in \mathbb{R}_+$Energy withdrawn from the VRE and grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with a charge AC component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,cha}$ [MWh]
$\Gamma_{y,z,t} \in \mathbb{R}_+$Stored energy level of technology $y$ at end of time step $t$ in zone $z$ [MWh]
$\Lambda_{s,z,t} \in \mathbb{R}_+$Non-served energy/curtailed demand from the price-responsive demand segment $s$ in zone $z$ at time step $t$ [MWh]
$l_{l,t} \in \mathbb{R}_+$Losses in line $l$ at time step $t$ [MWh]
$\varrho_{y,z,t}\in \mathbb{R}_+$Spillage from a reservoir technology $y$ at end of time step $t$ in zone $z$ [MWh]
$f_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves from technology $y$ in zone $z$ at time $t$\footnote{Regulation reserve contribution are modeled to be symmetric, consistent with current practice in electricity markets}
$r_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] from technology $y$ in zone $z$ at time t (we are not modeling down spinning reserves since these are usually never binding for high variable renewable energy systems)
$f^{charge}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves from charging storage technology $y$ in zone $z$ at time $t$
$f^{discharge}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves from discharging storage technology $y$ in zone $z$ at time $t$
$r^{charge}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] from charging storage technology $y$ in zone $z$ at time $t$
$r^{discharge}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] from discharging storage technology $y$ in zone $z$ at time $t$
$r^{unmet}_t \in \mathbb{R}_+$Shortfall in provision of upward operating spinning reserves during each time period $t \in T$
$f^{pv}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the solar PV component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$
$r^{pv}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the solar PV component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$
$f^{wind}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the wind component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$
$r^{wind}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the wind component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$
$f^{dc,dis}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the storage DC discharge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage DC discharge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,dis}$
$r^{dc,dis}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the storage DC discharge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage DC discharge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,dis}$
$f^{dc,cha}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the storage DC charge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage DC charge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,cha}$
$r^{dc,cha}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the storage DC charge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage DC charge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,cha}$
$f^{ac,dis}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the storage AC discharge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage AC discharge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,dis}$
$r^{ac,dis}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the storage AC discharge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage AC discharge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,dis}$
$f^{ac,cha}_{y,z,t}\in \mathbb{R}_+$Frequency regulation contribution [MW] for up and down reserves for the storage AC charge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage AC charge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,cha}$
$r^{ac,cha}_{y,z,t} \in \mathbb{R}_+$Upward spinning reserves contribution [MW] for the storage AC charge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage AC charge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,cha}$
$\alpha^{Contingency,Aux}_{y,z} \in \{0,1\}$Binary variable that is set to be 1 if the total installed capacity $\Delta^{\text{total}}_{y,z} > 0$ for any generator $y \in \mathcal{UC}$ and zone $z$, and can be 0 otherwise
$\Phi_{l,t} \in \mathbb{R}_+$Power flow in line $l$ at time step $t$ [MWh]
$\theta_{z,t} \in \mathbb{R}$Volta phase angle in zone $z$ at time step $t$ [radian]
$v_{y,z,t}$Commitment state of the generation cluster $y$ in zone $z$ at time $t$
$\mathcal{X}_{y,z,t}$Number of startup decisions, of the generation cluster $y$ in zone $z$ at time $t$
$\zeta_{y,z,t}$Number of shutdown decisions, of the generation cluster $y$ in zone $z$ at time $t$
$\mathcal{Q}_{o,n} \in \mathbb{R}_+$Inventory of storage of type $o$ at the beginning of input period $n$ [MWh]
$\Delta\mathcal{Q}_{o,m} \in \mathbb{R}$Excess storage inventory built up during representative period $m$ [MWh]
$ON^{+}_{l,t} \in {0,1}$Binary variable to activate positive flows on line $l$ in time $t$
$TransON^{+}_{l,t} \in \mathbb{R}_+$Variable defining maximum positive flow in line $l$ in time $t$ [MW]
$\Theta^{CRM}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy discharged by a storage resource that contributes to the capacity reserve margin for technology $y$ at time step $t$ in zone $z$ - only applicable for storage resources with activated capacity reserve margin policies, $y \in \mathcal{O}$ [MWh]
$\Pi^{CRM}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy withdrawn by a storage resource from the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for storage resources with activated capacity reserve margin policies, $y \in \mathcal{O}$ [MWh]
$\Theta^{CRM,dc}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy discharged by a storage DC component that contributes to the capacity reserve margin for technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with activated capacity reserve margin policies, $y \in \mathcal{VS}^{stor}$ [MWh]
$\Pi^{CRM,dc}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy withdrawn by a storage DC component from the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with activated capacity reserve margin policies, $y \in \mathcal{VS}^{stor}$ [MWh]
$\Theta^{CRM,ac}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy discharged by a storage AC component that contributes to the capacity reserve margin for technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with activated capacity reserve margin policies, $y \in \mathcal{VS}^{stor}$ [MWh]
$\Pi^{CRM,ac}_{y,z,t} \in \mathbb{R}_+$"Virtual" energy withdrawn by a storage AC component from the grid by technology $y$ at time step $t$ in zone $z$ - only applicable for co-located VRE and storage resources with activated capacity reserve margin policies, $y \in \mathcal{VS}^{stor}$ [MWh]
$\Gamma^{CRM}_{y,z,t} \in \mathbb{R}_+$Total "virtual" state of charge being held in reserves for technology $y$ at time step $t$ in zone $z$ - only applicable for standalone storage and co-located VRE and storage resources with activated capacity reserve margin policies, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh]

Parameters


NotationDescription
$D_{z,t}$Electricity demand in zone $z$ and at time step $t$ [MWh]
$\tau^{period}$number of time steps in each representative period $w \in \mathcal{W}^{rep}$ and each input period $w \in \mathcal{W}^{input}$
$\omega_{t}$weight of each model time step $\omega_t =1 \forall t \in T$ when modeling each time step of the year at an hourly resolution [1/year]
$n_s^{slope}$Cost of non-served energy/demand curtailment for price-responsive demand segment $s$ [$/MWh]
$n_s^{size}$Size of price-responsive demand segment $s$ as a fraction of the hourly zonal demand [%]
$\overline{\Omega}_{y,z}$Maximum capacity of technology $y$ in zone $z$ [MW] (Note that for co-located VRE and storage resources, this value represents the maximum grid connection capacity in MW AC)
$\underline{\Omega}_{y,z}$Minimum capacity of technology $y$ in zone $z$ [MW] (Note that for co-located VRE and storage resources, this value represents the minimum grid connection capacity in MW AC)
$\overline{\Omega}^{energy}_{y,z}$Maximum energy capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that for co-located VRE and storage resources, this value represents the maximum storage component in MWh)
$\underline{\Omega}^{energy}_{y,z}$Minimum energy capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that for co-located VRE and storage resources, this value represents the minimum storage component in MWh)
$\overline{\Omega}^{charge}_{y,z}$Maximum charging power capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}^{asym}$ [MW]
$\underline{\Omega}^{charge}_{y,z}$Minimum charging capacity of technology $y$ in zone $z$- only applicable for storage resources, $y \in \mathcal{O}^{asym}$ [MW]
$\overline{\Omega}^{pv}_{y,z}$Maximum solar PV capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\underline{\Omega}^{pv}_{y,z}$Minimum solar PV capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\overline{\Omega}^{wind}_{y,z}$Maximum wind capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\underline{\Omega}^{wind}_{y,z}$Minimum wind capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\overline{\Omega}^{inv}_{y,z}$Maximum inverter capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\underline{\Omega}^{inv}_{y,z}$Minimum inverter capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\overline{\Omega}^{dc,dis}_{y,z}$Maximum storage DC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\underline{\Omega}^{dc,dis}_{y,z}$Minimum storage DC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\overline{\Omega}^{dc,cha}_{y,z}$Maximum storage DC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\underline{\Omega}^{dc,cha}_{y,z}$Minimum storage DC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\overline{\Omega}^{ac,dis}_{y,z}$Maximum storage AC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\underline{\Omega}^{ac,dis}_{y,z}$Minimum storage AC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\overline{\Omega}^{ac,cha}_{y,z}$Maximum storage AC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [MW AC]
$\underline{\Omega}^{ac,cha}_{y,z}$Minimum storage AC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [MW AC]
$\overline{\Delta}_{y,z}$Existing installed capacity of technology $y$ in zone $z$ [MW] (Note that for co-located VRE and storage resources, this value represents the existing installed capacity of the grid connection in [MW AC])
$\overline{\Delta^{energy}_{y,z}}$Existing installed energy capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [MWh] (Note that for co-located VRE and storage resources, this value represents the existing installed energy capacity of the storage component in MWh)
$\overline{\Delta^{charge}_{y,z}}$Existing installed charging capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}$ [MW]
$\overline{\Delta^{pv}_{y,z}}$Existing installed solar PV capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [MW DC]
$\overline{\Delta^{wind}_{y,z}}$Existing installed wind capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [MW AC]
$\overline{\Delta^{inv}_{y,z}}$Existing installed inverter capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [MW AC]
$\overline{\Delta^{dc,dis}_{y,z}}$Existing installed storage DC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [MW DC]
$\overline{\Delta^{dc,cha}_{y,z}}$Existing installed storage DC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW DC]
$\overline{\Delta^{ac,dis}_{y,z}}$Existing installed storage AC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [MW AC]
$\overline{\Delta^{dc,cha}_{y,z}}$Existing installed storage AC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [MW AC]
$\overline{\Omega}_{y,z}^{size}$Unit size of technology $y$ in zone $z$ [MW]
$\pi_{y,z}^{INVEST}$Investment cost (annual amortization of total construction cost) for power capacity of technology $y$ in zone $z$ [$/MW-yr] (Note that for co-located VRE and storage resources, this value represents the investment cost of the grid connection capacity in $/MW AC-yr)
$\pi_{y,z}^{INVEST,energy}$Investment cost (annual amortization of total construction cost) for energy capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{pv}$ [$/MWh-yr] (Note that for co-located VRE and storage resources, this value represents the investment cost of the energy capacity of the storage component in $/MWh-yr)
$\pi_{y,z}^{INVEST,charge}$Investment cost (annual amortization of total construction cost) for charging power capacity of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}$ [$/MW-yr]
$\pi_{y,z}^{INVEST,pv}$Investment cost (annual amortization of total construction cost) for solar PV capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [$/MW DC-yr]
$\pi_{y,z}^{INVEST,wind}$Investment cost (annual amortization of total construction cost) for wind capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [$/MW AC-yr]
$\pi_{y,z}^{INVEST,inv}$Investment cost (annual amortization of total construction cost) for inverter capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [$/MW AC-yr]
$\pi_{y,z}^{INVEST,dc,dis}$Investment cost (annual amortization of total construction cost) for storage DC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [$/MW DC-yr]
$\pi_{y,z}^{INVEST,dc,cha}$Investment cost (annual amortization of total construction cost) for storage DC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [$/MW DC-yr]
$\pi_{y,z}^{INVEST,ac,dis}$Investment cost (annual amortization of total construction cost) for storage AC discharge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [$/MW AC-yr]
$\pi_{y,z}^{INVEST,ac,cha}$Investment cost (annual amortization of total construction cost) for storage AC charge capacity of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [$/MW AC-yr]
$\pi_{y,z}^{FOM}$Fixed O&M cost of technology $y$ in zone $z$ [$/MW-yr] (Note that for co-located VRE and storage resources, this value represents the fixed O&M cost of the grid connection capacity in $/MW AC-yr)
$\pi_{y,z}^{FOM,energy}$Fixed O&M cost of energy component of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O} \cup y \in \mathcal{VS}^{stor}$ [$/MWh-yr] (Note that for co-located VRE and storage resources, this value represents the fixed O&M cost of the storage energy capacity in $/MWh-yr)
$\pi_{y,z}^{FOM,charge}$Fixed O&M cost of charging power component of technology $y$ in zone $z$ - only applicable for storage resources, $y \in \mathcal{O}$ [$/MW-yr]
$\pi_{y,z}^{FOM,pv}$Fixed O&M cost of the solar PV component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [$/MW DC-yr]
$\pi_{y,z}^{FOM,wind}$Fixed O&M cost of the wind component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [$/MW AC-yr]
$\pi_{y,z}^{FOM,inv}$Fixed O&M cost of the inverter component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an inverter component, $y \in \mathcal{VS}^{inv}$ [$/MW AC-yr]
$\pi_{y,z}^{FOM,dc,dis}$Fixed O&M cost of the storage DC discharge component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC discharge component, $y \in \mathcal{VS}^{asym,dc,dis}$ [$/MW DC-yr]
$\pi_{y,z}^{FOM,dc,cha}$Fixed O&M cost of the storage DC charge component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage DC charge component, $y \in \mathcal{VS}^{asym,dc,cha}$ [$/MW DC-yr]
$\pi_{y,z}^{FOM,ac,dis}$Fixed O&M cost of the storage AC discharge component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC discharge component, $y \in \mathcal{VS}^{asym,ac,dis}$ [$/MW AC-yr]
$\pi_{y,z}^{FOM,ac,cha}$Fixed O&M cost of the storage AC charge component of technology $y$ in zone $z$ - only applicable for co-located VRE and storage resources with an asymmetric storage AC charge component, $y \in \mathcal{VS}^{asym,ac,cha}$ [$/MW AC-yr]
$\pi_{y,z}^{VOM}$Variable O&M cost of technology $y$ in zone $z$ [$/MWh]
$\pi_{y,z}^{VOM,charge}$Variable O&M cost of charging technology $y$ in zone $z$ - only applicable for storage and demand flexibility resources, $y \in \mathcal{O} \cup \mathcal{DF}$ [$/MWh]
$\pi_{y,z}^{VOM,pv}$Variable O&M cost of the solar PV component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a solar PV component, $y \in \mathcal{VS}^{pv}$ [$/MWh]
$\pi_{y,z}^{VOM,wind}$Variable O&M cost of the wind component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a wind component, $y \in \mathcal{VS}^{wind}$ [$/MWh]
$\pi_{y,z}^{VOM,dc,dis}$Variable O&M cost of the storage DC discharge component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a storage DC discharge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,dis}$ [$/MWh]
$\pi_{y,z}^{VOM,dc,cha}$Variable O&M cost of the storage DC charge component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a storage DC charge component, $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{asym,dc,cha}$ [$/MWh]
$\pi_{y,z}^{VOM,ac,dis}$Variable O&M cost of the storage AC discharge component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a storage AC discharge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,dis}$ [$/MWh]
$\pi_{y,z}^{VOM,ac,cha}$Variable O&M cost of the storage AC charge component of technology $y$ in zone $z$ - only applicable to co-located VRE and storage resources with a storage AC charge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,cha}$ [$/MWh]
$\pi_{y,z}^{FUEL}$Fuel cost of technology $y$ in zone $z$ [$/MWh]
$\pi_{y,z}^{START}$Startup cost of technology $y$ in zone $z$ [$/startup]
$\upsilon^{reg}_{y,z}$Maximum fraction of capacity that a resource $y$ in zone $z$ can contribute to frequency regulation reserve requirements
$\upsilon^{rsv}_{y,z}$Maximum fraction of capacity that a resource $y$ in zone $z$ can contribute to upward operating (spinning) reserve requirements
$\pi^{Unmet}_{rsv}$Cost of unmet spinning reserves in [$/MW]
$\epsilon^{demand}_{reg}$Frequency regulation reserve requirement as a fraction of forecasted demand in each time step
$\epsilon^{vre}_{reg}$Frequency regulation reserve requirement as a fraction of variable renewable energy generation in each time step
$\epsilon^{demand}_{rsv}$Operating (spinning) reserve requirement as a fraction of forecasted demand in each time step
$\epsilon^{vre}_{rsv}$Operating (spinning) reserve requirement as a fraction of forecasted variable renewable energy generation in each time step
$\epsilon_{y,z}^{CO_2}$CO$_2$ emissions per unit energy produced by technology $y$ in zone $z$ [metric tons/MWh]
$\epsilon_{y,z,p}^{MinTech}$Equals to 1 if a generator of technology $y$ in zone $z$ is eligible for minimum capacity carveout policy $p \in \mathcal{P}^{MinTech}$, otherwise 0
$REQ_p^{MinTech}$The minimum capacity requirement of minimum capacity carveout policy $p \in \mathcal{P}^{MinTech}$ [MW]
$REQ_p^{MaxTech}$The maximum capacity requirement of minimum capacity carveout policy $p \in \mathcal{P}^{MinTech}$ [MW]
$\epsilon_{y,z,p}^{CRM}$Capacity derating factor of technology $y$ in zone $z$ for capacity reserve margin policy $p \in \mathcal{P}^{CRM}$ [fraction]
$RM_{z,p}^{CRM}$Reserve margin of zone $z$ of capacity reserve margin policy $p \in \mathcal{P}^{CRM}$ [fraction]
$\epsilon_{z,p,mass}^{CO_2}$Emission budget of zone $z$ under the emission cap $p \in \mathcal{P}^{CO_2}_{mass}$ [ million of metric tonnes]
$\epsilon_{z,p,demand}^{CO_2}$Maximum carbon intensity of the demand of zone $z$ under the emission cap $p \in \mathcal{P}^{CO_2}_{demand}$ [metric tonnes/MWh]
$\epsilon_{z,p,gen}^{CO_2}$Maximum emission rate of the generation of zone $z$ under the emission cap $p \in \mathcal{P}^{CO_2}_{gen}$ [metric tonnes/MWh]
$\rho_{y,z}^{min}$Minimum stable power output per unit of installed capacity for technology $y$ in zone $z$ [%]
$\rho_{y,z,t}^{max}$Maximum available generation per unit of installed capacity during time step t for technology y in zone z [%]
$\rho_{y,z,t}^{max,pv}$Maximum available generation per unit of installed capacity for the solar PV component of a co-located VRE and storage resource during time step t for technology y in zone z [%]
$\rho_{y,z,t}^{max,wind}$Maximum available generation per unit of installed capacity for the wind component of a co-located VRE and storage resource during time step t for technology y in zone z [%]
$VREIndex_{y,z}$Resource bin index for VRE technology $y$ in zone $z$. $VREIndex_{y,z}=1$ for the first bin, and $VREIndex_{y,z}=0$ for remaining bins. Only defined for $y\in \mathcal{VRE}$
$\varphi^{map}_{l,z}$Topology of the network, for line l: $\varphi^{map}_{l,z}=1$ for start zone $z$, - 1 for end zone $z$, 0 otherwise.
$\mathcal{B}_{l}$DC-OPF coefficient for line $l$ [MWh]
$\Delta \theta^{\max}_{l}$Maximum voltage phase angle difference for line $l$ [radian]
$\eta_{y,z}^{loss}$Self discharge rate per time step per unit of installed capacity for storage technology $y$ in zone $z$ [%]
$\eta_{y,z}^{charge}$Single-trip efficiency of storage charging/demand deferral for technology $y$ in zone $z$ [%]
$\eta_{y,z}^{discharge}$Single-trip efficiency of storage (and hydro reservoir) discharging/demand satisfaction for technology $y$ in zone $z$ [%]
$\eta_{y,z}^{charge,dc}$Single-trip efficiency of storage DC charging/demand deferral for technology $y$ in zone $z$ for co-located VRE and storage resources [%]
$\eta_{y,z}^{discharge,dc}$Single-trip efficiency of storage DC discharging/demand satisfaction for technology $y$ in zone $z$ for co-located VRE and storage resources [%]
$\eta_{y,z}^{charge,ac}$Single-trip efficiency of storage AC charging/demand deferral for technology $y$ in zone $z$ for co-located VRE and storage resources [%]
$\eta_{y,z}^{discharge,ac}$Single-trip efficiency of storage AC discharging/demand satisfaction for technology $y$ in zone $z$ for co-located VRE and storage resources [%]
$\eta_{y,z}^{inverter}$Inverter efficiency representing losses from converting DC to AC power and vice versa for technology $y$ in zone $z$ for co-located VRE and storage resources [%]
$\eta_{y,z}^{ILR,pv}$Inverter loading ratio (the solar PV capacity sizing to the inverter capacity built) of technology $y$ in zone $z$ for co-located VRE and storage resources with a solar PV component [%]
$\eta_{y,z}^{ILR,wind}$Inverter loading ratio (the wind PV capacity sizing to the grid connection capacity built) of technology $y$ in zone $z$ for co-located VRE and storage resources with a wind component [%]
$\mu_{y,z}^{stor}$Ratio of energy capacity to discharge power capacity for storage technology (and hydro reservoir) $y$ in zone $z$ [MWh/MW]
$\mu_{y,z}^{dc,stor}$Ratio of discharge power capacity to energy capacity for the storage DC component of co-located VRE and storage technology $y$ in zone $z$ [MW/MWh]
$\mu_{y,z}^{ac,stor}$Ratio of discharge power capacity to energy capacity for the storage AC component of co-located VRE and storage technology $y$ in zone $z$ [MW/MWh]
$\mu_{y,z}^{\mathcal{DF}}$Maximum percentage of hourly demand that can be shifted by technology $y$ in zone $z$ [%]
$\kappa_{y,z}^{up}$Maximum ramp-up rate per time step as percentage of installed capacity of technology y in zone z [%/hr]
$\kappa_{y,z}^{down}$Maximum ramp-down rate per time step as percentage of installed capacity of technology y in zone z [%/hr]
$\tau_{y,z}^{up}$Minimum uptime for thermal generator type y in zone z before new shutdown [hours].
$\tau_{y,z}^{down}$Minimum downtime or thermal generator type y in zone z before new restart [hours].
$\tau_{y,z}^{advance}$maximum time by which flexible demand resource can be advanced [hours]
$\tau_{y,z}^{delay}$maximum time by which flexible demand resource can be delayed [hours]
$\eta_{y,z}^{dflex}$energy losses associated with shifting the flexible demand [%]
$\mu_{p,z}^{\mathcal{ESR}}$share of total demand in each model zone $z \in \mathcal{ESR}^{p}$ that must be served by qualifying renewable energy resources $y \in \mathcal{G}^{ESR}_{p}$
$f(n)$Mapping each modeled period $n \in \mathcal{N}$ to corresponding representative period $w \in \mathcal{W}$
$\eta_{y}^{electrolyzer}$Efficiency of the electrolyzer $y$ in megawatt-hours (MWh) of electricity per metric tonne of hydrogen produced [MWh/t] (optional parameter)
$ $^{hydrogen}_y$Price of hydrogen per metric tonne for electrolyzer $y$ [$/t] (optional parameter)
$\mathcal{Min kt}_y$Minimum annual quantity of hydrogen that must be produced by electrolyzer $y$ in kilotonnes [kt] (optional parameter)

diff --git a/previews/PR670/Model_Concept_Overview/objective_function/index.html b/previews/PR670/Model_Concept_Overview/objective_function/index.html new file mode 100644 index 0000000000..88addd98fd --- /dev/null +++ b/previews/PR670/Model_Concept_Overview/objective_function/index.html @@ -0,0 +1,31 @@ + +Objective Function · GenX.jl

Objective Function

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

\[\begin{aligned} + \text{min} \quad + &\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) + \\ + &\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) + \\ + &\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) + \\ + & \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) + \\ + &\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) \\ + &\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) + \\ + & \sum_{l \in \mathcal{L}}\left(\pi^{TCAP}_{l} \times \bigtriangleup\varphi^{max}_{l}\right) + \\ + &\sum_{y \in \mathcal{VS}^{inv}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST, inv}_{y,z} \times \Omega^{inv}_{y,z}) + + (\pi^{FOM, inv}_{y,z} \times \Delta^{total,inv}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{pv}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST, pv}_{y,z} \times \Omega^{pv}_{y,z}) + + (\pi^{FOM, pv}_{y,z} \times \Delta^{total,pv}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{wind}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST, wind}_{y,z} \times \Omega^{pv}_{y,z}) + + (\pi^{FOM, wind}_{y,z} \times \Delta^{total,wind}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{asym,dc,dis}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,dc,dis}_{y,z} \times \Omega^{dc,dis}_{y,z}) + + (\pi^{FOM,dc,dis}_{y,z} \times \Delta^{total,dc,dis}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{asym,dc,cha}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,dc,cha}_{y,z} \times \Omega^{dc,cha}_{y,z}) + + (\pi^{FOM,dc,cha}_{y,z} \times \Delta^{total,dc,cha}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{asym,ac,dis}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,ac,dis}_{y,z} \times \Omega^{ac,dis}_{y,z}) + + (\pi^{FOM,ac,dis}_{y,z} \times \Delta^{total,ac,dis}_{y,z})\right) + \\ + &\sum_{y \in \mathcal{VS}^{asym,ac,cha}} \sum_{z \in \mathcal{Z}} \left( (\pi^{INVEST,ac,cha}_{y,z} \times \Omega^{ac,cha}_{y,z}) + + (\pi^{FOM,ac,cha}_{y,z} \times \Delta^{total,ac,cha}_{y,z})\right) + \\ + & \sum_{y \in \mathcal{VS}^{pv}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,pv}_{y,z}\times \eta^{inverter}_{y,z} \times \Theta^{pv}_{y,z,t}\right) + \\ + & \sum_{y \in \mathcal{VS}^{wind}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,wind}_{y,z} \times \Theta^{wind}_{y,z,t}\right) + \\ + & \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,dc,dis}_{y,z} \times\eta^{inverter}_{y,z} \times \Theta^{dc}_{y,z,t}\right) + \\ + & \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,dc,cha}_{y,z} \times \frac{\Pi^{dc}_{y,z,t}}{\eta^{inverter}_{y,z}}\right) + \\ + & \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,ac,dis}_{y,z} \times \Theta^{ac}_{y,z,t}\right) + \\ + & \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,cha}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} \left( \omega_{t}\times\pi^{VOM,ac,cha}_{y,z} \times \Pi^{ac}_{y,z,t}\right) +\end{aligned}\]

The first summation represents the fixed costs of generation/discharge over all zones and technologies, which reflects 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 ($y \in \mathcal{O} \cup \mathcal{VS}^{stor}$). 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 ($y \in \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 correspond 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$. 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 weight of each time step, $\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 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 (if modeled), 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 ninth term corresponds to the transmission reinforcement or construction costs, for each transmission line (if modeled). 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.

The tenth term onwards specifically relates to the breakdown investment, fixed O&M, and variable O&M costs associated with each configurable component of a co-located VRE and storage resource. The tenth term represents to the fixed cost of installed inverter capacity and is summed over only the co-located resources with an inverter component ($y \in \mathcal{VS}^{inv}$). This term includes the sum of the annualized inverter capital cost, $\pi^{INVEST,inv}_{y,z}$, times the total new inverter capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, inv}_{y,z}$, times the net installed inverter capacity, $\Delta^{total,inv}_{y,z}$ (e.g., existing capacity less retirements plus additions). The eleventh term represents the fixed cost of installed solar PV capacity and is summed over only the co-located resources with a solar PV component ($y \in \mathcal{VS}^{pv}$). This term includes the sum of the annualized solar PV capital cost, $\pi^{INVEST,pv}_{y,z}$, times the total new solar PV capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, pv}_{y,z}$, times the net installed solar PV capacity, $\Delta^{total,pv}_{y,z}$ (e.g., existing capacity less retirements plus additions). The twelveth term represents the fixed cost of installed wind capacity and is summed over only the co-located resources with a wind component ($y \in \mathcal{VS}^{wind}$). This term includes the sum of the annualized wind capital cost, $\pi^{INVEST,wind}_{y,z}$, times the total new wind capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, wind}_{y,z}$, times the net installed wind capacity, $\Delta^{total,wind}_{y,z}$ (e.g., existing capacity less retirements plus additions). The thirteenth term represents the fixed cost of installed storage DC discharge capacity and is summed over only the co-located resources with an asymmetric storage DC discharge component ($y \in \mathcal{VS}^{asym,dc,dis}$). This term includes the sum of the annualized storage DC discharge capital cost, $\pi^{INVEST,dc,dis}_{y,z}$, times the total new storage DC discharge capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, dc, dis}_{y,z}$, times the net installed storage DC discharge capacity, $\Delta^{total,dc,dis}_{y,z}$ (e.g., existing capacity less retirements plus additions). The fourteenth term represents the fixed cost of installed storage DC charge capacity and is summed over only the co-located resources with an asymmetric storage DC charge component ($y \in \mathcal{VS}^{asym,dc,cha}$). This term includes the sum of the annualized storage DC charge capital cost, $\pi^{INVEST,dc,cha}_{y,z}$, times the total new storage DC charge capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, dc, cha}_{y,z}$, times the net installed storage DC charge capacity, $\Delta^{total,dc,cha}_{y,z}$ (e.g., existing capacity less retirements plus additions). The fifteenth term represents the fixed cost of installed storage AC discharge capacity and is summed over only the co-located resources with an asymmetric storage AC discharge component ($y \in \mathcal{VS}^{asym,ac,dis}$). This term includes the sum of the annualized storage AC discharge capital cost, $\pi^{INVEST,ac,dis}_{y,z}$, times the total new storage AC discharge capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, ac, dis}_{y,z}$, times the net installed storage AC discharge capacity, $\Delta^{total,ac,dis}_{y,z}$ (e.g., existing capacity less retirements plus additions). The sixteenth term represents to the fixed cost of installed storage AC charge capacity and is summed over only the co-located resources with an asymmetric storage AC charge component ($y \in \mathcal{VS}^{asym,ac,cha}$). This term includes the sum of the annualized storage AC charge capital cost, $\pi^{INVEST,ac,cha}_{y,z}$, times the total new storage AC charge capacity added (if any), plus the Fixed O&M cost, $\pi^{FOM, ac, cha}_{y,z}$, times the net installed storage AC charge capacity, $\Delta^{total,ac,cha}_{y,z}$ (e.g., existing capacity less retirements plus additions).

The seventeeth term onwards corresponds to the operational cost across all zones, technologies, and time steps for co-located VRE and storage resources. The seventeenth summation represents the variable O&M cost, $\pi^{VOM,pv}_{y,z}$, times the energy generation by solar PV resources ($y\in\mathcal{VS}^{pv}$) in time step $t$, $\Theta^{pv}_{y,z,t}$, the inverter efficiency, $\eta^{inverter}_{y,z}$, and the weight of each time step $t$, $\omega_t$. The eighteenth summation represents the variable O&M cost, $\pi^{VOM,wind}_{y,z}$, times the energy generation by wind resources ($y\in\mathcal{VS}^{wind}$) in time step $t$, $\Theta^{wind}_{y,z,t}$, and the weight of each time step $t$, $\omega_t$. The nineteenth summation represents the variable O&M cost, $\pi^{VOM,dc,dis}_{y,z}$, times the energy discharge by storage DC components ($y\in\mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}$) in time step $t$, $\Theta^{dc}_{y,z,t}$, the inverter efficiency, $\eta^{inverter}_{y,z}$, and the weight of each time step $t$, $\omega_t$. The twentieth summation represents the variable O&M cost, $\pi^{VOM,dc,cha}_{y,z}$, times the energy withdrawn by storage DC components ($y\in\mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}$) in time step $t$, $\Pi^{dc}_{y,z,t}$, and the weight of each time step $t$, $\omega_t$, and divided by the inverter efficiency, $\eta^{inverter}_{y,z}$. The twenty-first summation represents the variable O&M cost, $\pi^{VOM,ac,dis}_{y,z}$, times the energy discharge by storage AC components ($y\in\mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}$) in time step $t$, $\Theta^{ac}_{y,z,t}$, and the weight of each time step $t$, $\omega_t$. The twenty-second summation represents the variable O&M cost, $\pi^{VOM,ac,cha}_{y,z}$, times the energy withdrawn by storage AC components ($y\in\mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,cha}$) in time step $t$, $\Pi^{ac}_{y,z,t}$, and the weight of each time step $t$, $\omega_t$.

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.

While the objective function is formulated as a cost minimization problem, it is also equivalent to a social welfare maximization problem, with the bulk of demand treated as inelastic and always served, and the utility of consumption for price-elastic consumers represented as a segment-wise approximation, as per the cost of unserved demand summation above.

diff --git a/previews/PR670/Model_Concept_Overview/power_balance/index.html b/previews/PR670/Model_Concept_Overview/power_balance/index.html new file mode 100644 index 0000000000..b5d85743db --- /dev/null +++ b/previews/PR670/Model_Concept_Overview/power_balance/index.html @@ -0,0 +1,7 @@ + +Power Balance · GenX.jl

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 variable renewable energy resources ($\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. Similarly, co-located variable renewable energy and storage resources ($\mathcal{VS}$ and $\mathcal{VS}^{stor}$ to represent co-located resources with a storage component) can generate electricity from the solar PV and/or wind component and discharge electricity from the storage resource, $\Theta_{y,z,t}$ to help satisfy demand, and charge from the grid, $\Pi_{y,z,t}$, if the storage component of the resource exists. (For the case of flexible demand resources ($\mathcal{DF}$), delaying demand, $\Pi_{y,z,t}$, decreases demand while satisfying delayed 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).

\[\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})} + \\ + & \sum_{y\in \mathcal{DF}}{(-\Theta_{y,z,t}+\Pi_{y,z,t})} +\sum_{y\in \mathcal{W}}{\Theta_{y,z,t}}+ \sum_{y\in \mathcal{VS}}{\Theta_{y,z,t}} - \sum_{y\in \mathcal{VS}^{stor}}{\Pi_{y,z,t}} + \\ + & \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} + \quad \quad \forall z\in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

diff --git a/previews/PR670/Model_Reference/Multi_Stage/configure_multi_stage_inputs/index.html b/previews/PR670/Model_Reference/Multi_Stage/configure_multi_stage_inputs/index.html new file mode 100644 index 0000000000..aef84073ee --- /dev/null +++ b/previews/PR670/Model_Reference/Multi_Stage/configure_multi_stage_inputs/index.html @@ -0,0 +1,4 @@ + +Configure multi-stage inputs · GenX.jl

Configure multi stage inputs

GenX.compute_overnight_capital_costMethod
function compute_overnight_capital_cost(settings_d::Dict,inv_costs_yr::Array,crp::Array,tech_wacc::Array)

This function computes overnight capital costs incured within the model horizon, assuming that annualized costs to be paid after the model horizon are fully recoverable, and so are not included in the cost computation.

For each resource $y \in \mathcal{G}$ with annualized investment cost $AIC_{y}$ and capital recovery period $CRP_{y}$, overnight capital costs $OCC_{y}$ are computed as follows:

\[\begin{aligned} + & OCC_{y} = \sum^{min(CRP_{y},H)}_{i=1}\frac{AIC_{y}}{(1+WACC_{y})^{i}} +\end{aligned}\]

where $WACC_y$ is the technology-specific weighted average cost of capital (set by the "WACC" field in the Generators_data.csv or Network.csv files), $H$ is the number of years remaining between the start of the current model stage and the model horizon (the end of the final model stage) and $CRP_y$ is the capital recovery period for technology $y$ (specified in Generators_data.csv).

inputs:

  • settings_d - dict object containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
  • inv_costs_yr - array object containing annualized investment costs.
  • crp - array object of capital recovery period values.
  • tech_wacc - array object containing technology-specific weighted costs of capital.

NOTE: The inv_costs_yr and crp arrays must be the same length; values with the same index in each array correspond to the same resource $y \in \mathcal{G}$.

returns: array object containing overnight capital costs, the discounted sum of annual investment costs incured within the model horizon.

source
GenX.configure_multi_stage_inputsMethod
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
diff --git a/previews/PR670/Model_Reference/Multi_Stage/dual_dynamic_programming/index.html b/previews/PR670/Model_Reference/Multi_Stage/dual_dynamic_programming/index.html new file mode 100644 index 0000000000..412ceed83e --- /dev/null +++ b/previews/PR670/Model_Reference/Multi_Stage/dual_dynamic_programming/index.html @@ -0,0 +1,12 @@ + +Model multi stage: Dual Dynamic Programming Algorithm · GenX.jl

Dual Dynamic Programming Algorithm

GenX.add_cutMethod
add_cut(EP_cur::Model, EP_next::Model, start_cap_d::Dict, cap_track_d::Dict)

inputs:

  • EP_cur - JuMP model from the current model stage $p$.
  • EP_next - JuMP model from the next model stage $p+1$..
  • start_cap_d – Dictionary which contains key-value pairs of available capacity investment expression names (as Symbols) as keys and their corresponding linking constraint names (as Symbols) as values.
  • cap_track_d – Dictionary which contains key-value pairs of capacity addition and retirement tracking variable names (as Symbols) as keys and their corresponding linking constraint names (as Symbols) as values.

returns: JuMP expression representing a sum of Benders cuts for linking capacity investment variables to be added to the cost-to-go function.

source
GenX.configure_ddp_dictsMethod
configure_ddp_dicts(setup::Dict, inputs::Dict)

This function instantiates Dictionary objects containing the names of linking expressions, constraints, and variables used in multi-stage modeling.

inputs:

  • setup - Dictionary object containing GenX settings and key parameters.
  • inputs – Dictionary of inputs for each model period, generated by the load_inputs() method.

returns:

  • start_cap_d – Dictionary which contains linking expression names as keys and linking constraint names as values, used for setting the end capacity in stage $p$ to the starting capacity in stage $p+1$.
  • cap_track_d – Dictionary which contains linking variable names as keys and linking constraint names as values, used for enforcing endogenous retirements.
source
GenX.fix_capacity_trackingMethod
fix_capacity_tracking(EP_prev::Model, EP_cur::Model, cap_track_d::Dict, cur_stage::Int)

This function sets the right hand side values of the new and retired capacity tracking linking constraints in the current stage $p$ to the realized values of the new and retired capacity tracking linking variables from the previous stage $p-1$ as part of the forward pass. where tracking linking variables are defined variables for tracking, linking and passing realized expansion and retirement of capacities of each stage to the next stage. Tracking linking variables are each defined in endogenous_retirement_discharge, endogenous_retirement_energy, and endogenous_retirement_charge functions. Three examples are "vCAPTRACK", "vCAPTRACKCHARGE", and ""vCAPTRACKENERGY"

inputs:

  • EP_prev - JuMP model from the previous model stage $p-1$.
  • EP_cur - JuMP model from the current model stage $p$.
  • cap_track_d – Dictionary which contains key-value pairs of capacity addition and retirement tracking variable names (as Symbols) as keys and their corresponding linking constraint names (as Symbols) as values.
  • cur_period – Int representing the current model stage $p$.

returns: JuMP model with updated linking constraints.

source
GenX.fix_initial_investmentsMethod
fix_initial_investments(EP_prev::Model, EP_cur::Model, start_cap_d::Dict)

This function sets the right hand side values of the existing capacity linking constraints in the current stage $p$ to the realized values of the total available end capacity linking variable expressions from the previous stage $p-1$ as part of the forward pass.

inputs:

  • EP_prev - JuMP model from the previous model stage $p-1$.
  • EP_cur - JuMP model from the current model stage $p$.
  • start_cap_d – Dictionary which contains key-value pairs of available capacity investment expression names (as Symbols) as keys and their corresponding linking constraint names (as Symbols) as values.

returns: JuMP model with updated linking constraints.

source
GenX.generate_cut_component_invMethod
generate_cut_component_inv(EP_cur::Model, EP_next::Model, expr_name::Symbol, constr_name::Symbol)

This function generates Bender's cut expressions for linking capacity investment variable expression in the form:

\[\begin{aligned} + \mu_{next}^{\top}(\hat{x}_{cur} - x_{cur}) +\end{aligned}\]

where $\mu_{next}$ is a vector of dual values of the linking constraints defined by constr_name in EP_next, $\hat{x}_{cur}$ is a vector of realized values from the forward pass of the linking capacity investment variable expressions expr_name from EP_cur, and $x_{cur}$ is a vector of unrealized linking capacity investment variable expressions from EP_cur. inputs:

inputs:

  • EP_cur - JuMP model from the current model stage $p$, solved in the forward pass.
  • EP_next - JuMP model from the next model stage $p+1$, solved in the forward pass.
  • expr_name – Symbol representing the name of a JuMP expression array which contains linking capacity investment variables.
  • constr_name – Symbol representing the name of the array of linking JuMP constraints which contain the linking capacity investment variables.

returns: JuMP expression representing a sum of Benders cuts for linking capacity investment variables to be added to the cost-to-go function.

source
GenX.generate_cut_component_trackMethod
generate_cut_component_inv(EP_cur::Model, EP_next::Model, expr_name::Symbol, constr_name::Symbol)

This function generates Bender's cut expressions for total new or retired capacity tracking linking variables in the form:

\[\begin{aligned} + \mu_{next}^{\top}(\hat{x}_{cur} - x_{cur}) +\end{aligned}\]

where $\mu_{next}$ is a vector of dual values of the linking constraints defined by constr_name in EP_next, $\hat{x}_{cur}$ is a vector of realized values from the forward pass of the new or retired capacity tracking linking variables var_name from EP_cur, and $x_{cur}$ is a vector of unrealized new or retired capacity tracking linking variables from EP_cur.

inputs:

  • EP_cur - JuMP model from the current model stage $p$.
  • EP_next - JuMP model from the next model stage $p+1$.
  • var_name – Symbol representing the name of a JuMP variable array which contains total new or retired capacity tracking linking variables.
  • constr_name – Symbol representing the name of the array of linking JuMP constraints which contain total new or retired capacity tracking linking variables.

returns: JuMP expression representing a sum of Benders cuts for linking capacity investment variables to be added to the cost-to-go function.

source
GenX.initialize_cost_to_goMethod
initialize_cost_to_go(settings_d::Dict, EP::Model)

This function scales the model objective function so that costs are consistent with multi-stage modeling and introduces a cost-to-go function variable to the objective function.

The updated objective function $OBJ^{*}$ returned by this method takes the form:

\[\begin{aligned} + OBJ^{*} = DF * OPEXMULT * OBJ + \alpha +\end{aligned}\]

where $OBJ$ is the original objective function. $OBJ$ is scaled by two terms. The first is a discount factor (applied only in the non-myopic case), which discounts costs associated with the model stage $p$ to year-0 dollars:

\[\begin{aligned} + DF = \frac{1}{(1+WACC)^{L*(p-1)}} +\end{aligned}\]

where $WACC$ is the weighted average cost of capital, and $L$ is the length of each stage in years (both set in multi_stage_settings.yml)

The second term is a discounted sum of annual operational expenses incurred each year of a multi-year model stage:

\[\begin{aligned} + & OPEXMULT = \sum^{L}_{l=1}\frac{1}{(1+WACC)^{l-1}} +\end{aligned}\]

Note that although the objective function contains investment costs, which occur only once and thus do not need to be scaled by OPEXMULT, these costs are multiplied by a factor of $\frac{1}{WACC}$ before being added to the objective function in investment_discharge_multi_stage(), investment_charge_multi_stage(), investment_energy_multi_stage(), and transmission_multi_stage(). Thus, this step scales these costs back to their correct value.

The cost-to-go function $\alpha$ represents an approximation of future costs given the investment and retirement decisions in the current stage. It is constructed through the addition of cuts to the cost-to-go function $\alpha$ during the backwards pass.

inputs:

  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
  • EP – JuMP model.

returns: JuMP model with updated objective function.

source
GenX.run_ddpMethod
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_outputsMethod
write_multi_stage_outputs(stats_d::Dict, outpath::String, settings_d::Dict)

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

inputs:

  • 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.
source
diff --git a/previews/PR670/Model_Reference/Multi_Stage/endogenous_retirement/index.html b/previews/PR670/Model_Reference/Multi_Stage/endogenous_retirement/index.html new file mode 100644 index 0000000000..853c77c719 --- /dev/null +++ b/previews/PR670/Model_Reference/Multi_Stage/endogenous_retirement/index.html @@ -0,0 +1,6 @@ + +Endogenous Retirement · GenX.jl

Endogenous Retirement

GenX.endogenous_retirement_discharge!Method
endogenous_retirement_discharge!(EP::Model, inputs::Dict, num_stages::Int, cur_stage::Int, stage_lens::Array{Int, 1})

This function models the following constraint

\[\begin{aligned} +& RETCAP_{y,p} \geq \sum^p_{t=1} MINRET_{y,t} + \sum^r_{t=1}CAP_{y,t} - \sum^{(p-1)}_{t=1}RETCAP_{y,t} +\end{aligned}\]

where $r \in \{1, ..., (p-1)\}$ is defined as the last stage such that if we built $y$ at the end of stage $r$, it would reach its end of life before the end of stage $p$. In other words, it is the largest index $r \in \{1, ..., (p-1)\}$ such that:

\[\begin{aligned} +\sum^p_{t=r+1}StageLength_{t} \leq LifeTime_{y} +\end{aligned}\]

source
GenX.get_retirement_stageMethod
get_retirement_stage(cur_stage::Int, stage_len::Int, lifetime::Int, stage_lens::Array{Int, 1})

This function determines the model stage before which all newly built capacity must be retired. Used to enforce endogenous lifetime retirements in multi-stage modeling.

inputs:

  • cur_stage – An Int representing the current model stage $p$.
  • lifetime – An Int representing the lifetime of a particular resource.
  • stage_lens – An Int array representing the length $L$ of each model stage.

returns: An Int representing the model stage in before which the resource must retire due to endogenous lifetime retirements.

source
diff --git a/previews/PR670/Model_Reference/Multi_Stage/multi_stage_overview/index.html b/previews/PR670/Model_Reference/Multi_Stage/multi_stage_overview/index.html new file mode 100644 index 0000000000..a265d7bf49 --- /dev/null +++ b/previews/PR670/Model_Reference/Multi_Stage/multi_stage_overview/index.html @@ -0,0 +1,2 @@ + +Multi-Stage Modeling Introduction · GenX.jl

Multi-stage investment planning

Added in 0.3

GenX can be used to study the long-term evolution of the power system across multiple investment stages, in the following two ways:

  • The user can formulate and solve a deterministic multi-stage planning problem with perfect foresight i.e. demand, cost, and policy assumptions about all stages are known and exploited to determine the least-cost investment trajectory for the entire period. The solution of this multi-stage problem relies on exploiting the decomposable nature of the multi-stage problem via the implementation of the dual dynamic programming algorithm, described in Lara et al. 2018 here. This algorithm splits up a multi-stage investment planning problem into multiple, single-period sub-problems. Each period is solved iteratively as a separate linear program sub-problem (“forward pass”), and information from future periods is shared with past periods (“backwards pass”) so that investment decisions made in subsequent iterations reflect the contributions of present-day investments to future costs. The decomposition algorithm adapts previous nested Benders methods by handling integer and continuous state variables, although at the expense of losing its finite convergence property due to potential duality gap.
  • The user can formulate a sequential, myopic multi-stage planning problem, where the model solves a sequence of single-stage investment planning problems wherein investment decisions in each stage are individually optimized to meet demand given assumptions for the current planning stage and with investment decisions from previous stages treated as inputs for the current stage. We refer to this as "myopic" (or shortsighted) mode since the solution does not account for information about future stages in determining investments for a given stage. This version is generally more computationally efficient than the deterministic multi-stage expansion with perfect foresight mode.

More information on this feature can be found in the section Multi-stage setup.

diff --git a/previews/PR670/Model_Reference/Resources/curtailable_variable_renewable/index.html b/previews/PR670/Model_Reference/Resources/curtailable_variable_renewable/index.html new file mode 100644 index 0000000000..a864b365a8 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/curtailable_variable_renewable/index.html @@ -0,0 +1,12 @@ + +Curtailable Variable Renewable · GenX.jl

Curtailable Variable Renewables

GenX.curtailable_variable_renewable!Method
curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict)

This function defines the constraints for operation of variable renewable energy (VRE) resources whose output can be curtailed ($y \in \mathcal{VRE}$), such as utility-scale solar PV or wind power resources or run-of-river hydro resources that can spill water. The operational constraints for VRE resources are a function of each technology's time-dependent hourly capacity factor (or availability factor, $\rho^{max}_{y,z,t}$), in per unit terms, and the total available capacity ($\Delta^{total}_{y,z}$).

Power output in each time step For each VRE technology type $y$ and model zone $z$, the model allows for incorporating multiple bins with different parameters for resource quality ($\rho^{max}_{y,z,t}$), maximum availability ($\overline{\Omega_{y,z}}$) and investment cost ($\Pi^{INVEST}_{y,z}$, for example, due to interconnection cost differences). We define variables related to installed capacity ($\Delta_{y,z}$) and retired capacity ($\Delta_{y,z}$) for all resource bins for a particular VRE resource type $y$ and zone $z$ ($\overline{\mathcal{VRE}}^{y,z}$). However, the variable corresponding to power output in each timestep is only defined for the first bin. Parameter $VREIndex_{y,z}$, is used to keep track of the first bin, where $VREIndex_{y,z}=1$ for the first bin and $VREIndex_{y,z}=0$ for the remaining bins. This approach allows for modeling many different bins per VRE technology type and zone while significantly reducing the number of operational variable (related to power output for each time step from each bin) added to the model with every additional bin. Thus, the maximum power output for each VRE resource type in each zone is given by the following equation:

\[\begin{aligned} + \Theta_{y,z,t} \leq \sum_{(x,z)\in \overline{\mathcal{VRE}}^{x,z}}{\rho^{max}_{x,z,t} \times \Delta^{total}_{x,z}} \hspace{2 cm} \forall y,z \in \{(y,z)|VREIndex_{y,z}=1, z \in \mathcal{Z}\},t \in \mathcal{T} +\end{aligned}\]

The above constraint is defined as an inequality instead of an equality to allow for VRE power output to be curtailed if desired. This adds the possibility of introducing VRE curtailment as an extra degree of freedom to guarantee that generation exactly meets demand in each time step. Note that if OperationalReserves=1 indicating that frequency regulation and operating reserves are modeled, then this function calls curtailable_variable_renewable_operational_reserves!(), which replaces the above constraints with a formulation inclusive of reserve provision.

source
GenX.curtailable_variable_renewable_operational_reserves!Method
curtailable_variable_renewable_operational_reserves!(EP::Model, inputs::Dict)

When modeling operating reserves, this function is called by curtailable_variable_renewable(), which modifies the constraint for maximum power output in each time step from VRE resources to account for procuring some of the available capacity for frequency regulation ($f_{y,z,t}$) and upward operating (spinning) reserves ($r_{y,z,t}$).

\[\begin{aligned} + \Theta_{y,z,t} + f_{y,z,t} + r_{y,z,t} \leq \sum_{(x,z)\in \overline{\mathcal{VRE}}^{x,z}}{\rho^{max}_{x,z,t}\times \Delta^{total}_{x,z}} \hspace{0.1 cm} \forall y,z \in \{(y,z)|VREIndex_{y,z}=1, z \in \mathcal{Z}\},t \in \mathcal{T} +\end{aligned}\]

The amount of downward frequency regulation reserves also cannot exceed the current power output.

\[\begin{aligned} + f_{y,z,t} \leq \Theta_{y,z,t} + \forall y,z \in \{(y,z)|VREIndex_{y,z}=1, z \in \mathcal{Z}\},t \in \mathcal{T} +\end{aligned}\]

The amount of frequency regulation and operating reserves procured in each time step is bounded by the user-specified fraction ($\upsilon^{reg}_{y,z}$,$\upsilon^{rsv}_{y,z}$) of available capacity in each period for each reserve type, reflecting the maximum ramp rate for the VRE resource in whatever time interval defines the requisite response time for the regulation or reserve products (e.g., 5 mins or 15 mins or 30 mins). These response times differ by system operator and reserve product, and so the user should define these parameters in a self-consistent way for whatever system context they are modeling.

\[\begin{aligned} + r_{y,z,t} \leq \upsilon^{rsv}_{y,z} \sum_{(x,z)\in \overline{\mathcal{VRE}}^{x,z}}{\rho^{max}_{x,z,t}\times \Delta^{total}_{x,z}} \hspace{1 cm} \forall y,z \in \{(y,z)|VREIndex_{y,z}=1, z \in \mathcal{Z}\},t \in \mathcal{T} \\ + f_{y,z,t} \leq \upsilon^{reg}_{y,z} \sum_{(x,z)\in \overline{\mathcal{VRE}}^{x,z}}{\rho^{max}_{x,z,t}\times \Delta^{total}_{x,z}} \hspace{1 cm} \forall y,z \in \{(y,z)|VREIndex_{y,z}=1, z \in \mathcal{Z}\},t \in \mathcal{T} +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/electrolyzers/index.html b/previews/PR670/Model_Reference/Resources/electrolyzers/index.html new file mode 100644 index 0000000000..8c35b798f6 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/electrolyzers/index.html @@ -0,0 +1,18 @@ + +Hydrogen Electrolyzers · GenX.jl

Hydrogen Electrolyzers

GenX.electrolyzer!Method
electrolyzer!(EP::Model, inputs::Dict, setup::Dict)

This function defines the expressions and constraints for operation of hydrogen electrolyzers ($y \in \mathcal{EL} \subseteq \mathcal{G}$). This is a basic implementation of hydrogen electrolyzers that allows the specification of an hourly clean supply constraint. For a richer formulation, please see the DOLPHYN code at https://github.com/macroenergy/DOLPHYN.

Expressions

Consumption of electricity by electrolyzer $y$ in time $t$, denoted by $\Pi_{y,z}$, is subtracted from power balance expression ePowerBalance (as per other demands or battery charging) and added to Energy Share Requirement policy balance (if applicable), eESR.

Revenue from hydrogen production by each electrolyzer $y$, equal to $\omega_t \times \Pi_{y,t} / \eta^{electrolyzer}_y \times \$^{hydrogen}y$, is subtracted from the objective function, where \eta^{electrolyzer}y$ is the efficiency of the electrolyzer $y$ in megawatt-hours (MWh) of electricity per metric tonne of hydrogen produced and $\$^{hydrogen}_y$ is the price of hydrogen per metric tonne for electrolyzer $y$.

Ramping limits

Electrolyzers adhere to the following ramping limits on hourly changes in power output:

\[\begin{aligned} + \Pi_{y,t-1} - \Pi_{y,t} \leq \kappa_{y}^{down} \Delta^{\text{total}}_{y}, \hspace{1cm} \forall y \in \mathcal{EL}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Pi_{y,t} - \Pi_{y,t-1} \leq \kappa_{y}^{up} \Delta^{\text{total}}_{y} \hspace{1cm} \forall y \in \mathcal{EL}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 1-2 in the code)

This set of time-coupling constraints wrap around to ensure the power output in the first time step of each year (or each representative period), $t \in \mathcal{T}^{start}$, is within the eligible ramp of the power output in the final time step of the year (or each representative period), $t+\tau^{period}-1$.

Minimum and maximum power output

Electrolyzers are bound by the following limits on maximum and minimum power output:

\[\begin{aligned} + \Pi_{y,t} \geq \rho^{min}_{y} \times \Delta^{total}_{y} + \hspace{1cm} \forall y \in \mathcal{EL}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,t} \leq \rho^{max}_{y,t} \times \Pi_^{total}_{y} + \hspace{1cm} \forall y \in \mathcal{EL}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 3-4 in the code)

Minimum annual hydrogen production

The sum of annual hydrogen production by each electrolyzer $y \in \mathcal{EL}$ must exceed a minimum quantity specified in inputs:

\[\begin{aligned} + \sum_{t \in T} (\omega_{t} \times \Pi_{y,t} / \eta^{electrolyzer}_y) \geq \mathcal{Min kt}_y \times 10^3 + \hspace{1cm} \forall y \in \mathcal{EL} +\end{aligned}\]

where $\eta^{electrolyzer}_y$ is the efficiency of the electrolyzer $y$ in megawatt-hours (MWh) of electricity per metric tonne of hydrogen produced and $\mathcal{Min kt}_y$ is the minimum annual quantity of hydrogen that must be produced by electrolyzer $y$ in kilotonnes. (See constraint 5 in the code)

Hourly clean supply matching constraint

This optional constraint (enabled by setting HydrogenHourlyMatching==1 in genx_settings.yml) requires generation from qualified resources ($y \in \mathcal{Qualified}$, indicated by Qualified_Hydrogen_Supply==1 in the resource .csv files) from within the same zone $z$ as the electrolyzers are located to be >= hourly consumption from electrolyzers in the zone (and any charging by qualified storage within the zone used to help increase electrolyzer utilization):

\[\begin{aligned} + \sum_{y \in \{z \cap \mathcal{Qualified}\}} \Theta_{y,t} \geq \sum_{y \in \{z \cap \mathcal{EL}\}} \Pi_{y,t} + \sum_{y \in \{z \cap \mathcal{Qualified} \cap \mathcal{STOR}\}} \Pi_{y,t} + \hspace{1cm} \forall z \in \mathcal{Z}, \forall t \in \mathcal{T}, +\end{aligned}\]

(See constraint 6 in the code)

This constraint permits modeling of the 'three pillars' requirements for clean hydrogen supply of (1) new clean supply (if only new clean resources are designated as eligible), (2) that is deliverable to the electrolyzer (assuming co-location within the same modeled zone = deliverability), and (3) produced within the same hour as the electrolyzer consumes power (otherwise known as 'additionality/new supply', 'deliverability', and 'temporal matching requirements') See Ricks, Xu & Jenkins (2023), ''Minimizing emissions from grid-based hydrogen production in the United States'' Environ. Res. Lett. 18 014025 doi:10.1088/1748-9326/acacb5 for more.

source
diff --git a/previews/PR670/Model_Reference/Resources/flexible_demand/index.html b/previews/PR670/Model_Reference/Resources/flexible_demand/index.html new file mode 100644 index 0000000000..98f35d7dc0 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/flexible_demand/index.html @@ -0,0 +1,13 @@ + +Flexible Demand · GenX.jl

Flexible Demand

GenX.flexible_demand!Method
flexible_demand!(EP::Model, inputs::Dict, setup::Dict)

This function defines the operating constraints for flexible demand resources. As implemented, flexible demand resources ($y \in \mathcal{DF}$) are characterized by: a) maximum deferrable demand as a fraction of available capacity in a particular time step $t$, $\rho^{max}_{y,z,t}$, b) the maximum time this demand can be advanced and delayed, defined by parameters, $\tau^{advance}_{y,z}$ and $\tau^{delay}_{y,z}$, respectively and c) the energy losses associated with shifting demand, $\eta_{y,z}^{dflex}$.

Tracking total deferred demand The operational constraints governing flexible demand resources are as follows. The first two constraints model keep track of inventory of deferred demand in each time step. Specifically, the amount of deferred demand remaining to be served ($\Gamma_{y,z,t}$) depends on the amount in the previous time step minus the served demand during time step $t$ ($\Theta_{y,z,t}$) while accounting for energy losses associated with demand flexibility, plus the demand that has been deferred during the current time step ($\Pi_{y,z,t}$). Note that variable $\Gamma_{y,z,t} \in \mathbb{R}$, $\forall y \in \mathcal{DF}, t \in \mathcal{T}$. Similar to hydro inventory or storage state of charge constraints, for the first time step of the year (or each representative period), we define the deferred demand level based on level of deferred demand in the last time step of the year (or each representative period).

\[\begin{aligned} +\Gamma_{y,z,t} = \Gamma_{y,z,t-1} -\eta_{y,z}^{dflex}\Theta_{y,z,t} +\Pi_{y,z,t} \hspace{4 cm} \forall y \in \mathcal{DF}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior} \\ +\Gamma_{y,z,t} = \Gamma_{y,z,t +\tau^{period}-1} -\eta_{y,z}^{dflex}\Theta_{y,z,t} +\Pi_{y,z,t} \hspace{4 cm} \forall y \in \mathcal{DF}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

Bounds on available demand flexibility At any given time step, the amount of demand that can be shifted or deferred cannot exceed the maximum deferrable demand, defined by product of the availability factor ($\rho^{max}_{y,t}$) times the available capacity($\Delta^{total}_{y,z}$).

\[\begin{aligned} +\Pi_{y,t} \leq \rho^{max}_{y,z,t}\Delta_{y,z} \hspace{4 cm} \forall y \in \mathcal{DF}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Maximum time delay and advancements Delayed demand must then be served within a fixed number of time steps. This is done by enforcing the sum of demand satisfied ($\Theta_{y,z,t}$) in the following $\tau^{delay}_{y,z}$ time steps (e.g., t + 1 to t + $\tau^{delay}_{y,z}$) to be greater than or equal to the level of energy deferred during time step $t$.

\[\begin{aligned} +\sum_{e=t+1}^{t+\tau^{delay}_{y,z}}{\Theta_{y,z,e}} \geq \Gamma_{y,z,t} + \hspace{4 cm} \forall y \in \mathcal{DF},z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

A similar constraints maximum time steps of demand advancement. This is done by enforcing the sum of demand deferred ($\Pi_{y,t}$) in the following $\tau^{advance}_{y}$ time steps (e.g., t + 1 to t + $\tau^{advance}_{y}$) to be greater than or equal to the total level of energy deferred during time $t$ (-$\Gamma_{y,t}$). The negative sign is included to account for the established sign convention that treat demand deferred in advance of the actual demand is defined to be negative.

\[\begin{aligned} +\sum_{e=t+1}^{t+\tau^{advance}_{y,z}}{\Pi_{y,z,e}} \geq -\Gamma_{y,z,t} + \hspace{4 cm} \forall y \in \mathcal{DF}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

If $t$ is first time step of the year (or the first time step of the representative period), then the above two constraints are implemented to look back over the last n time steps, starting with the last time step of the year (or the last time step of the representative period). This time-wrapping implementation is similar to the time-wrapping implementations used for defining the storage balance constraints for hydropower reservoir resources and energy storage resources.

source
diff --git a/previews/PR670/Model_Reference/Resources/hydro_inter_period_linkage/index.html b/previews/PR670/Model_Reference/Resources/hydro_inter_period_linkage/index.html new file mode 100644 index 0000000000..6dc9f428e6 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/hydro_inter_period_linkage/index.html @@ -0,0 +1,17 @@ + +Long Duration Hydro · GenX.jl

Reservoir Hydro

GenX.hydro_inter_period_linkage!Method
hydro_inter_period_linkage!(EP::Model, inputs::Dict)

This function creates variables and constraints enabling modeling of long duration storage resources when modeling representative time periods.

Storage inventory balance at beginning of each representative period The constraints in this section are used to approximate the behavior of long-duration energy storage technologies when approximating annual grid operations by modeling operations over representative periods. Previously, the state of charge balance for storage (as defined in storage_all()) assumed that state of charge at the beginning and end of each representative period has to be the same. In other words, the amount of energy built up or consumed by storage technology $o$ in zone $z$ over the representative period $m$, $\Delta Q_{o,z,m} = 0$. This assumption implicitly excludes the possibility of transferring energy from one representative period to the other which could be cost-optimal when the capital cost of energy storage capacity is relatively small. To model long-duration energy storage using representative periods, we replace the state of charge equation, such that the first term on the right hand side accounts for change in storage inventory associated with representative period $m$ ($\Delta Q_{o,z,m}$), which could be positive (net accumulation) or negative (net reduction).

\[\begin{aligned} +& \Gamma_{o,z,(m-1)\times \tau^{period}+1 } =\left(1-\eta_{o,z}^{loss}\right)\times \left(\Gamma_{o,z,m\times \tau^{period}} -\Delta Q_{o,z,m}\right) - \\ +& \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} +\end{aligned}\]

By definition $\mathcal{T}^{start}=\{\left(m-1\right) \times \tau^{period}+1 | m \in \mathcal{M}\}$, which implies that this constraint is defined for all values of $t \in T^{start}$.

Storage inventory change input periods We need additional variables and constraints to approximate energy exchange between representative periods, while accounting for their chronological occurence in the original input time series data and the possibility that two representative periods may not be adjacent to each other (see Figure below). To implement this, we introduce a new variable $Q_{o,z, n}$ that models inventory of storage technology $o \in O$ in zone $z$ in each input period $n \in \mathcal{N}$. Additionally we define a function mapping, $f: n \rightarrow m$, that uniquely maps each input period $n$ to its corresponding representative period $m$. This mapping is available as an output of the process used to identify representative periods (E.g. k-means clustering Mallapragada et al., 2018). Modeling inter-period energy exchange via long-duration storage when using representative period temporal resolution to approximate annual grid operations Figure. Modeling inter-period energy exchange via long-duration storage when using representative period temporal resolution to approximate annual grid operations The following two equations define the storage inventory at the beginning of each input period $n+1$ as the sum of storage inventory at begining of previous input period $n$ plus change in storage inventory for that period. The latter is approximated by the change in storage inventory in the corresponding representative period, identified per the mapping $f(n)$. The second constraint relates the storage level of the last input period, $|N|$, with the storage level at the beginning of the first input period. Finally, if the input period is also a representative period, then a third constraint enforces that initial storage level estimated by the intra-period storage balance constraint should equal the initial storage level estimated from the inter-period storage balance constraints. Note that $|N|$ refers to the last modeled period.

\[\begin{aligned} +& Q_{o,z,n+1} = Q_{o,z,n} + \Delta Q_{o,z,f(n)} +\quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N}\setminus\{|N|\} +\end{aligned}\]

\[\begin{aligned} +& Q_{o,z,1} = Q_{o,z,|N|} + \Delta Q_{o,z,f(|N|)} +\quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n = |N| +\end{aligned}\]

\[\begin{aligned} +& Q_{o,z,n} =\Gamma_{o,z,f(n)\times \tau^{period}} - \Delta Q_{o,z,m} +\quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N}^{rep}, +\end{aligned}\]

Finally, the next constraint enforces that the initial storage level for each input period $n$ must be less than the installed energy capacity limit. This constraint ensures that installed energy storage capacity is consistent with the state of charge during both the operational time periods $t$ during each sample period $m$ as well as at the start of each chronologically ordered input period $n$ in the full annual time series.

\[\begin{aligned} + Q_{o,z,n} \leq \Delta^{total, energy}_{o,z} +\quad \forall n \in \mathcal{N}, o \in \mathcal{O}^{LDES} +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/hydro_res/index.html b/previews/PR670/Model_Reference/Resources/hydro_res/index.html new file mode 100644 index 0000000000..63fe481dc3 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/hydro_res/index.html @@ -0,0 +1,34 @@ + +Hydro Reservoir · GenX.jl

Hydro Resources

GenX.hydro_res!Method
hydro_res!(EP::Model, inputs::Dict, setup::Dict)

This module defines the operational constraints for reservoir hydropower plants. Hydroelectric generators with water storage reservoirs ($y \in \mathcal{W}$) are effectively modeled as energy storage devices that cannot charge from the grid and instead receive exogenous inflows to their storage reservoirs, reflecting stream flow inputs. For resources with unknown reservoir capacity ($y \in \mathcal{W}^{nocap}$), their operation is parametrized by their generation efficiency, $\eta_{y,z}^{down}$, and energy inflows to the reservoir at every time-step, represented as a fraction of the total power capacity,($\rho^{max}_{y,z,t}$). In case reservoir capacity is known ($y \in \mathcal{W}^{cap}$), an additional parameter, $\mu^{stor}_{y,z}$, referring to the ratio of energy capacity to discharge power capacity, is used to define the available reservoir storage capacity.

Storage inventory balance Reservoir hydro systems are governed by the storage inventory balance constraint given below. This constraint enforces that energy level of the reservoir resource $y$ and zone $z$ in time step $t$ ($\Gamma_{y,z,t}$) is defined as the sum of the reservoir level in the previous time step, less the amount of electricity generated, $\Theta_{y,z,t}$ (accounting for the generation efficiency, $\eta_{y,z}^{down}$), minus any spillage $\varrho_{y,z,t}$, plus the hourly inflows into the reservoir (equal to the installed reservoir discharged capacity times the normalized hourly inflow parameter $\rho^{max}_{y,z, t}$).

\[\begin{aligned} +&\Gamma_{y,z,t} = \Gamma_{y,z,t-1} -\frac{1}{\eta_{y,z}^{down}}\Theta_{y,z,t} - \varrho_{y,z,t} + \rho^{max}_{y,z,t} \times \Delta^{total}_{y,z} \hspace{.1 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior} \\ +&\Gamma_{y,z,t} = \Gamma_{y,z,t+\tau^{period}-1} -\frac{1}{\eta_{y,z}^{down}}\Theta_{y,z,t} - \varrho_{y,z,t} + \rho^{max}_{y,z,t} \times \Delta^{total}_{y,z} \hspace{.1 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

We implement time-wrapping to endogenize the definition of the intial state prior to the first period with the following assumption. If time step $t$ is the first time step of the year then storage inventory at $t$ is defined based on last time step of the year. Alternatively, if time step $t$ is the first time step of a representative period, then storage inventory at $t$ is defined based on the last time step of the representative period. Thus, when using representative periods, the storage balance constraint for hydro resources does not allow for energy exchange between representative periods. Note: in future updates, an option to model hydro resources with large reservoirs that can transfer energy across sample periods will be implemented, similar to the functions for modeling long duration energy storage in long_duration_storage.jl.

Ramping Limits The following constraints enforce hourly changes in power output (ramps down and ramps up) to be less than the maximum ramp rates ($\kappa^{down}_{y,z}$ and $\kappa^{up}_{y,z}$ ) in per unit terms times the total installed capacity of technology y ($\Delta^{total}_{y,z}$).

\[\begin{aligned} +&\Theta_{y,z,t} + f_{y,z,t} + r_{y,z,t} - \Theta_{y,z,t-1} - f_{y,z,t-1} \leq \kappa^{up}_{y,z} \times \Delta^{total}_{y,z} +\hspace{2 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} +&\Theta_{y,z,t-1} + f_{y,z,t-1} + r_{y,z,t-1} - \Theta_{y,z,t} - f_{y,z,t}\leq \kappa^{down}_{y,z} \Delta^{total}_{y,z} +\hspace{2 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Ramping constraints are enforced for all time steps except the first time step of the year or first time of each representative period when using representative periods to model grid operations.

Power generation and stream flow bounds Electricity production plus total spilled power from hydro resources is constrained to always be above a minimum output parameter, $\rho^{min}_{y,z}$, to represent operational constraints related to minimum stream flows or other demands for water from hydro reservoirs. Electricity production is constrained by either the the net installed capacity or by the energy level in the reservoir in the prior time step, whichever is more binding. For the latter constraint, the constraint for the first time step of the year (or the first time step of each representative period) is implemented based on energy storage level in last time step of the year (or last time step of each representative period).

\[\begin{aligned} +&\Theta_{y,z,t} + \varrho_{y,z,t} \geq \rho^{min}_{y,z} \times \Delta^{total}_{y,z} +\hspace{2 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} +\Theta_{y,t} \leq \times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t\in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} +\Theta_{y,z,t} \leq \Gamma_{y,t-1} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t\in \mathcal{T} +\end{aligned}\]

Reservoir energy capacity constraint In case the reservoir capacity is known ($y \in W^{cap}$), then an additional constraint enforces the total stored energy in each time step to be less than or equal to the available reservoir capacity. Here, the reservoir capacity is defined multiplying the parameter, $\mu^{stor}_{y,z}$ with the available power capacity.

\[\begin{aligned} +\Gamma_{y,z, t} \leq \mu^{stor}_{y,z}\times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{W}^{cap}, z \in \mathcal{Z}, t\in \mathcal{T} +\end{aligned}\]

source
GenX.hydro_res_operational_reserves!Method
hydro_res_operational_reserves!(EP::Model, inputs::Dict)

This module defines the modified constraints and additional constraints needed when modeling operating reserves

Modifications when operating reserves are modeled When modeling operating reserves, the constraints regarding maximum power flow limits are modified to account for procuring some of the available capacity for frequency regulation ($f_{y,z,t}$) and "updward" operating (or spinning) reserves ($r_{y,z,t}$).

\[\begin{aligned} + \Theta_{y,z,t} + f_{y,z,t} +r_{y,z,t} \leq \times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t\in \mathcal{T} +\end{aligned}\]

The amount of downward frequency regulation reserves cannot exceed the current power output.

\[\begin{aligned} + f_{y,z,t} \leq \Theta_{y,z,t} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

The amount of frequency regulation and operating reserves procured in each time step is bounded by the user-specified fraction ($\upsilon^{reg}_{y,z}$,$\upsilon^{rsv}_{y,z}$) of nameplate capacity for each reserve type, reflecting the maximum ramp rate for the hydro resource in whatever time interval defines the requisite response time for the regulation or reserve products (e.g., 5 mins or 15 mins or 30 mins). These response times differ by system operator and reserve product, and so the user should define these parameters in a self-consistent way for whatever system context they are modeling.

\[\begin{aligned} +f_{y,z,t} \leq \upsilon^{reg}_{y,z} \times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +r_{y,z, t} \leq \upsilon^{rsv}_{y,z}\times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/investment_charge/index.html b/previews/PR670/Model_Reference/Resources/investment_charge/index.html new file mode 100644 index 0000000000..92bb1616af --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/investment_charge/index.html @@ -0,0 +1,16 @@ + +Investment Charge · GenX.jl

Investment Charge

GenX.investment_charge!Method
investment_charge!(EP::Model, inputs::Dict)

This function defines the expressions and constraints keeping track of total available storage charge capacity across all resources as well as constraints on capacity retirements. The function also adds investment and fixed O\&M related costs related to charge capacity to the objective function.

The total capacity of each resource is defined as the sum of the existing capacity plus the newly invested capacity minus any retired capacity.

\[\begin{aligned} +& \Delta^{total,charge}_{y,z} =(\overline{\Delta^{charge}_{y,z}}+\Omega^{charge}_{y,z}-\Delta^{charge}_{y,z}) \forall y \in \mathcal{O}^{asym}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more capacity than existing capacity.

\[\begin{aligned} +&\Delta^{charge}_{y,z} \leq \overline{\Delta^{charge}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{O}^{asym}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega_{y,z}^{charge}}$ and $\underline{\Omega_{y,z}^{charge}}$ is defined, then we impose constraints on minimum and maximum power capacity.

\[\begin{aligned} +& \Delta^{total,charge}_{y,z} \leq \overline{\Omega}^{charge}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{O}^{asym}, z \in \mathcal{Z} \\ +& \Delta^{total,charge}_{y,z} \geq \underline{\Omega}^{charge}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{O}^{asym}, z \in \mathcal{Z} +\end{aligned}\]

In addition, this function adds investment and fixed O&M related costs related to charge capacity to the objective function:

\[\begin{aligned} +& \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) +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/investment_energy/index.html b/previews/PR670/Model_Reference/Resources/investment_energy/index.html new file mode 100644 index 0000000000..d29514386c --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/investment_energy/index.html @@ -0,0 +1,16 @@ + +Investment Energy · GenX.jl

Investment Energy

GenX.investment_energy!Method
investment_energy!(EP::Model, inputs::Dict)

This function defines the expressions and constraints keeping track of total available storage charge capacity across all resources as well as constraints on capacity retirements. The function also adds investment and fixed O\&M related costs related to charge capacity to the objective function.

The total capacity of each resource is defined as the sum of the existing capacity plus the newly invested capacity minus any retired capacity.

\[\begin{aligned} +& \Delta^{total,energy}_{y,z} =(\overline{\Delta^{energy}_{y,z}}+\Omega^{energy}_{y,z}-\Delta^{energy}_{y,z}) \forall y \in \mathcal{O}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more capacity than existing capacity.

\[\begin{aligned} +&\Delta^{energy}_{y,z} \leq \overline{\Delta^{energy}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{O}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega_{y,z}^{energy}}$ and $\underline{\Omega_{y,z}^{energy}}$ is defined, then we impose constraints on minimum and maximum power capacity.

\[\begin{aligned} +& \Delta^{total,energy}_{y,z} \leq \overline{\Omega}^{energy}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{O}, z \in \mathcal{Z} \\ +& \Delta^{total,energy}_{y,z} \geq \underline{\Omega}^{energy}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{O}, z \in \mathcal{Z} +\end{aligned}\]

In addition, this function adds investment and fixed O\&M related costs related to charge capacity to the objective function:

\[\begin{aligned} +& \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) +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/long_duration_storage/index.html b/previews/PR670/Model_Reference/Resources/long_duration_storage/index.html new file mode 100644 index 0000000000..57da375a7b --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/long_duration_storage/index.html @@ -0,0 +1,17 @@ + +Long Duration Storage · GenX.jl

Long Duration Storage

GenX.long_duration_storage!Method
long_duration_storage!(EP::Model, inputs::Dict, setup::Dict)

This function creates variables and constraints enabling modeling of long duration storage resources when modeling representative time periods.
Storage inventory balance at beginning of each representative period The constraints in this section are used to approximate the behavior of long-duration energy storage technologies when approximating annual grid operations by modeling operations over representative periods. Previously, the state of charge balance for storage (as defined in storage_all()) assumed that state of charge at the beginning and end of each representative period has to be the same. In other words, the amount of energy built up or consumed by storage technology $o$ in zone $z$ over the representative period $m$, $\Delta Q_{o,z,m} = 0$. This assumption implicitly excludes the possibility of transferring energy from one representative period to the other which could be cost-optimal when the capital cost of energy storage capacity is relatively small. To model long-duration energy storage using representative periods, we replace the state of charge equation, such that the first term on the right hand side accounts for change in storage inventory associated with representative period $m$ ($\Delta Q_{o,z,m}$), which could be positive (net accumulation) or negative (net reduction).

\[\begin{aligned} +& \Gamma_{o,z,(m-1)\times \tau^{period}+1 } =\left(1-\eta_{o,z}^{loss}\right)\times \left(\Gamma_{o,z,m\times \tau^{period}} -\Delta Q_{o,z,m}\right) - \\ +& \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} +\end{aligned}\]

By definition $\mathcal{T}^{start}=\{\left(m-1\right) \times \tau^{period}+1 | m \in \mathcal{M}\}$, which implies that this constraint is defined for all values of $t \in T^{start}$.
Storage inventory change input periods We need additional variables and constraints to approximate energy exchange between representative periods, while accounting for their chronological occurence in the original input time series data and the possibility that two representative periods may not be adjacent to each other (see Figure below). To implement this, we introduce a new variable $Q_{o,z, n}$ that models inventory of storage technology $o \in O$ in zone $z$ in each input period $n \in \mathcal{N}$. Additionally we define a function mapping, $f: n \rightarrow m$, that uniquely maps each input period $n$ to its corresponding representative period $m$. This mapping is available as an output of the process used to identify representative periods (E.g. k-means clustering Mallapragada et al., 2018).

Modeling inter-period energy exchange via long-duration storage when using representative period temporal resolution to approximate annual grid operations Figure. Modeling inter-period energy exchange via long-duration storage when using representative period temporal resolution to approximate annual grid operations

The following two equations define the storage inventory at the beginning of each input period $n+1$ as the sum of storage inventory at begining of previous input period $n$ plus change in storage inventory for that period. The latter is approximated by the change in storage inventory in the corresponding representative period, identified per the mapping $f(n)$. If the input period is also a representative period, then a second constraint enforces that initial storage level estimated by the intra-period storage balance constraint should equal the initial storage level estimated from the inter-period storage balance constraints.

\[\begin{aligned} +& Q_{o,z,n+1} = Q_{o,z,n} + \Delta Q_{o,z,f(n)} +\quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N} +\end{aligned}\]

\[\begin{aligned} +& Q_{o,z,n} =\Gamma_{o,z,f(n)\times \tau^{period}} - \Delta Q_{o,z,m} +\quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N}^{rep}, +\end{aligned}\]

Finally, the next constraint enforces that the initial storage level for each input period $n$ must be less than the installed energy capacity limit. This constraint ensures that installed energy storage capacity is consistent with the state of charge during both the operational time periods $t$ during each sample period $m$ as well as at the start of each chronologically ordered input period $n$ in the full annual time series.

\[\begin{aligned} + Q_{o,z,n} \leq \Delta^{total, energy}_{o,z} +\quad \forall n \in \mathcal{N}, o \in \mathcal{O}^{LDES} +\end{aligned}\]

If the capacity reserve margin constraint is enabled, a similar set of constraints is used to track the evolution of the energy held in reserve across representative periods. The main linking constraint is as follows:

\[\begin{aligned} +& \Gamma^{CRM}_{o,z,(m-1)\times \tau^{period}+1 } =\left(1-\eta_{o,z}^{loss}\right)\times \left(\Gamma^{CRM}_{o,z,m\times \tau^{period}} -\Delta Q_{o,z,m}\right) + \\ +& \frac{1}{\eta_{o,z}^{discharge}}\Theta^{CRM}_{o,z,(m-1)\times \tau^{period}+1} - \eta_{o,z}^{charge}\Pi^{CRM}_{o,z,(m-1)\times \tau^{period}+1} \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} +\end{aligned}\]

All other constraints are identical to those used to track the actual state of charge, except with the new variables $Q^{CRM}_{o,z,n}$ and $\Delta Q^{CRM}_{o,z,n}$ used in place of $Q_{o,z,n}$ and $\Delta Q_{o,z,n}$, respectively.

source
diff --git a/previews/PR670/Model_Reference/Resources/maintenance/index.html b/previews/PR670/Model_Reference/Resources/maintenance/index.html new file mode 100644 index 0000000000..250b9aef8d --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/maintenance/index.html @@ -0,0 +1,66 @@ + +Scheduled maintenance for various resources · GenX.jl

Optimized Scheduled Maintenance

Added in v0.4

In the real world, some types of resources (notably, fission) require regular scheduled maintenance, which often takes several weeks. During this time, the plant produces no power. This module allows GenX to find the best time of year for plants to undergo maintenance.

Scheduled maintenance is implemented only for thermal plants with unit commitment (THERM=1).

Description of the maintenance model

A plant requires a single contiguous period of $h \ge 1$ hours of maintenance, every $y \ge 1$ years. For each plant, the best time to start the maintenance period is determined by the optimizer.

During maintenance, the plant cannot be "commited", and therefore

  • uses no fuel,
  • produces no power,
  • and does not contribute to reserves.

Additionally,

  • the plant does not contribute to any Capacity Reserve Margin.

Treatment of plants that require maintenance only every few years

GenX models a long-term equilibrium, and each problem generally represents a single full year. If a plant requires maintenance every $y$ years, we take the simplification that at least $1/y$ of the plants must undergo maintenance in the modeled year.

See also "Interaction with integer unit commitment" below.

Reduction of number of possible start dates

This module creates constraints which work across long periods, and consequently can be very expensive to solve. In order to reduce the expense, the set of possible maintenance start dates can be limited. Rather than have maintenance potentially start every hour, one can have possible start dates which are once per day, once per week, etc. (In reality, maintenance is likely scheduled months in advance, so optimizing down to the hour may not be realistic anyway.)

How to use

There are four columns which need to be added to the plant data, i.e. in the resource .csv files:

  1. MAINT should be 1 for plants that require maintenance and 0 otherwise.
  2. Maintenance_Duration is the number of hours the maintenance period lasts.
  3. Maintenance_Cycle_Length_Years. If 1, maintenance every year, if 3 maintenance every 3 years, etc.
  4. Maintenance_Begin_Cadence. Spacing between hours in which maintenance can start.

The last three fields must be integers which are greater than 0. They are ignored for any plants which do not require maintenance.

Maintenance_Duration must be less than the total number of hours in the year.

If Maintenance_Begin_Cadence is 1 then the maintenance can begin in any hour. If it is 168 then it can begin in hours 1, 169, 337, etc.

Restrictions on use

The maintenance module has these restrictions:

  • More than a single maintenance period per year (i.e. every three months) is not possible in the current formulation.
  • Only full-year cases can be run; there must be only one "representative period".

It would not make sense to model a month-long maintenance period when the year is modeled as a series of representative weeks, for example.

Interaction with integer unit commitment

If integer unit commitment is on (UCommit=1) this module may not produce correct results; there may be more maintenance than the user wants. This is because the formulation specifies that the number of plants that go down for maintenance in the simulated year must be at least (the number of plants in the zone)/(the maintenance cycle length in years). As a reminder, the number of plants is eTotalCap / Cap_Size.

If there were three 500 MW plants (total 1500 MW) in a zone, and they require maintenance every three years (Maintenance_Cycle_Length_Years=3), the formulation will work properly: one of the three plants will go under maintenance.

But if there was only one 500 MW plant, and it requires maintenance every 3 years, the constraint will still make it do maintenance every year, because ceil(1/3) is 1. The whole 500 MW plant will do maintenance. This is the unexpected behavior.

However, if integer unit commitment was relaxed to "linearized" unit commitment (UCommit=2), the model will have only 500 MW / 3 = 166.6 MW worth of this plant do maintenance.

Hint: pre-scheduling maintenance

If you want to pre-schedule when maintenance occurs, you might not need this module. Instead, you could set the maximum power output of the plant to zero for a certain period, or make its fuel extremely expensive during that time. However, the plant would still be able to contribute to the Capacity Reserve Margin.

Outputs produced

If at least one plant has MAINT=1, a file maint_down.csv will be written listing how many plants are down for maintenance in each timestep.

Notes on mathematical formulation

The formulation of the maintenance state is very similar to the formulation of unit commitment.

There is a variable called something like vMSHUT which is analogous to vSTART and controls the start of the maintenance period. There is another variable called something like vMDOWN analogous to vCOMMIT which controls the maintenance status in any hour.

A constraint ensures that the value of vMDOWN in any hour is always more than the number of vMSHUTs in the previous Maintenance_Duration hours.

Another constraint ensures that the number of plants committed (vCOMMIT) at any one time plus the number of plants under maintenance (vMDOWN) is less than the total number of plants.

Developer note: adding maintenance to a resource

The maintenance formulation is applied on a per-resource basis, by calling the function maintenance_formulation!.

GenX.maintenance_formulation!Function
maintenance_formulation!(EP::Model,
+    inputs::Dict,
+    resource_component::AbstractString,
+    r_id::Int,
+    maint_begin_cadence::Int,
+    maint_dur::Int,
+    maint_freq_years::Int,
+    cap::Float64,
+    vcommit::Symbol,
+    ecap::Symbol,
+    integer_operational_unit_commitment::Bool)
+
+EP: the JuMP model
+inputs: main data storage
+resource_component: unique resource name with optional component name
+   If the plant has more than one component, this could identify a specific part which
+   is undergoing maintenance.
+r_id: Resource ID (unique resource integer)
+maint_begin_cadence:
+    It may be too expensive (from an optimization perspective) to allow maintenance
+    to begin at any time step during the simulation. Instead this integer describes
+    the cadence of timesteps in which maintenance can begin. Must be at least 1.
+maint_dur: Number of timesteps that maintenance takes. Must be at least 1.
+maint_freq_years: 1 is maintenannce every year,
+    2 is maintenance every other year, etc. Must be at least 1.
+cap: Plant electrical capacity.
+vcommit: Symbol of vCOMMIT-like variable.
+ecap: Symbol of eTotalCap-like variable.
+integer_operational_unit_commitment: whether this plant has integer unit
+    commitment for operational variables.
+
+Creates maintenance-tracking variables and adds their Symbols to two Sets in `inputs`.
+Adds constraints which act on the vCOMMIT-like variable.
source
GenX.resources_with_maintenanceFunction
resources_with_maintenance(df::DataFrame)::Vector{Int}
+
+Get a vector of the R_ID's of all resources listed in a dataframe
+that have maintenance requirements. If there are none, return an empty vector.
+
+This method takes a specific dataframe because compound resources may have their
+data in multiple dataframes.
source
GenX.maintenance_down_nameFunction
maintenance_down_name(resource_component::AbstractString)::String
+
+JuMP variable name to control whether a resource-component is down for maintenance.
+Here resource-component could be a whole resource or a component (for complex resources).
source
GenX.maintenance_shut_nameFunction
maintenance_shut_name(resource_component::AbstractString)::String
+
+JuMP variable name to control when a resource-components begins maintenance.
+Here resource-component could be a whole resource or a component (for complex resources).
source
GenX.controlling_maintenance_start_hoursFunction
controlling_maintenance_start_hours(p::Int, t::Int, maintenance_duration::Int, maintenance_begin_hours::UnitRange{Int64})
+
+p: hours_per_subperiod
+t: the current hour
+maintenance_duration: length of a maintenance period
+maintenance_begin_hours: collection of hours in which maintenance is allowed to start
source
GenX.ensure_maintenance_variable_records!Function
ensure_maintenance_variable_records!(dict::Dict)
+
+dict: a dictionary of model data
+
+This should be called by each method that adds maintenance formulations,
+to ensure that certain entries in the model data dict exist.
source
GenX.has_maintenanceFunction
has_maintenance(dict::Dict)
+
+dict: a dictionary of model data
+
+Checks whether the dictionary contains listings of maintenance-related variable names.
+This is true only after `maintenance_formulation!` has been called.
source
GenX.maintenance_down_variablesFunction
maintenance_down_variables(dict::Dict)
+
+dict: a dictionary of model data
+
+get listings of maintenance-related variable names.
+This is available only after `maintenance_formulation!` has been called.
source
diff --git a/previews/PR670/Model_Reference/Resources/must_run/index.html b/previews/PR670/Model_Reference/Resources/must_run/index.html new file mode 100644 index 0000000000..c47d8f23d5 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/must_run/index.html @@ -0,0 +1,5 @@ + +Must Run · GenX.jl

Must Run

GenX.must_run!Method
must_run!(EP::Model, inputs::Dict, setup::Dict)

This function defines the constraints for operation of `must-run' or non-dispatchable resources, such as rooftop solar systems that do not receive dispatch signals, run-of-river hydroelectric facilities without the ability to spill water, or cogeneration systems that must produce a fixed quantity of heat in each time step. This resource type can also be used to model baseloaded or self-committed thermal generators that do not respond to economic dispatch.

For must-run resources ($y\in \mathcal{MR}$) output in each time period $t$ must exactly equal the available capacity factor times the installed capacity, not allowing for curtailment. These resources are also not eligible for contributing to frequency regulation or operating reserve requirements.

\[\begin{aligned} +\Theta_{y,z,t} = \rho^{max}_{y,z,t}\times \Delta^{total}_{y,z} +\hspace{4 cm} \forall y \in \mathcal{MR}, z \in \mathcal{Z},t \in \mathcal{T} +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/resource/index.html b/previews/PR670/Model_Reference/Resources/resource/index.html new file mode 100644 index 0000000000..35787ab502 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/resource/index.html @@ -0,0 +1,45 @@ + +Resource types · GenX.jl

Resource types and function interfaces

GenX.resource_typesConstant
resource_types

Name of the type of resources available in the model.

Possible values:

  • :Thermal
  • :Vre
  • :Hydro
  • :Storage
  • :MustRun
  • :FlexDemand
  • :VreStorage
  • :Electrolyzer
source
Base.findallMethod
findall(f::Function, rs::Vector{<:AbstractResource})

Find all resources in the vector rs that satisfy the condition given by the function f. Return the resource id instead of the vector index.

Arguments

  • f::Function: The condition function.
  • rs::Vector{<:AbstractResource}: The vector of resources.

Returns

  • Vector: The vector of resource ids.

Examples

julia> findall(r -> max_cap_mwh(r) != 0, gen.Storage)
+3-element Vector{Int64}:
+ 48
+ 49
+ 50
source
Base.getMethod
Base.get(r::AbstractResource, sym::Symbol, default)

Retrieves the value of a specific attribute from an AbstractResource object. If the attribute exists, its value is returned; otherwise, the default value is returned.

Arguments:

  • r::AbstractResource: The resource object.
  • sym::Symbol: The symbol representing the attribute name.
  • default: The default value to return if the attribute does not exist.

Returns:

  • The value of the attribute if it exists in the parent object, default otherwise.
source
Base.getpropertyMethod
Base.getproperty(r::AbstractResource, sym::Symbol)

Allows to access the attributes of an AbstractResource object using dot syntax. It checks if the attribute exists in the object and returns its value, otherwise it throws an ErrorException indicating that the attribute does not exist.

Arguments:

  • r::AbstractResource: The resource object.
  • sym::Symbol: The symbol representing the attribute name.

Returns:

  • The value of the attribute if it exists in the parent object.

Throws:

  • ErrorException: If the attribute does not exist in the resource.
source
Base.getpropertyMethod
Base.getproperty(rs::Vector{<:AbstractResource}, sym::Symbol)

Allows to access attributes of a vector of AbstractResource objects using dot syntax. If the sym is an element of the resource_types constant, it returns all resources of that type. Otherwise, it returns the value of the attribute for each resource in the vector.

Arguments:

  • rs::Vector{<:AbstractResource}: The vector of AbstractResource objects.
  • sym::Symbol: The symbol representing the attribute name or a type from resource_types.

Returns:

  • If sym is an element of the resource_types constant, it returns a vector containing all resources of that type.
  • If sym is an attribute name, it returns a vector containing the value of the attribute for each resource.

Examples

julia> vre_gen = gen.Vre;  # gen vector of resources
+julia> typeof(vre_gen)
+Vector{Vre} (alias for Array{Vre, 1})
+julia> vre_gen.zone
source
Base.haskeyMethod
haskey(r::AbstractResource, sym::Symbol)

Check if an AbstractResource object has a specific attribute. It returns a boolean value indicating whether the attribute exists in the parent object.

Arguments:

  • r::AbstractResource: The resource object.
  • sym::Symbol: The symbol representing the attribute name.

Returns:

  • true if the attribute exists in the parent object, false otherwise.
source
Base.pairsMethod
pairs(r::AbstractResource)

Return an iterator of key-value pairs with the attributes of a given resource.

Arguments

  • r::AbstractResource: The resource.

Returns

  • Pairs: An iterator of key-value pairs over the attributes.
source
Base.setindex!Method
Base.setindex!(rs::Vector{<:AbstractResource}, value::Vector, sym::Symbol)

Define dot syntax for setting the attributes specified by sym to the corresponding values in value for a vector of resources.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • value::Vector: The vector of values to set for the attribute.
  • sym::Symbol: The symbol representing the attribute to set.

Returns

  • rs::Vector{<:AbstractResource}: The updated vector of resources.
source
Base.setproperty!Method
setproperty!(r::AbstractResource, sym::Symbol, value)

Allows to set the attribute sym of an AbstractResource object using dot syntax.

Arguments:

  • r::AbstractResource: The resource object.
  • sym::Symbol: The symbol representing the attribute name.
  • value: The value to set for the attribute.
source
Base.setproperty!Method
Base.setproperty!(rs::Vector{<:AbstractResource}, sym::Symbol, value::Vector)

Set the attributes specified by sym to the corresponding values in value for a vector of resources.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • sym::Symbol: The symbol representing the attribute to set.
  • value::Vector: The vector of values to set for the attribute.

Returns

  • rs::Vector{<:AbstractResource}: The updated vector of resources.
source
Base.showMethod
show(io::IO, r::AbstractResource)

Print the attributes of the given resource.

Arguments

  • io::IO: The IO stream to print to.
  • r::AbstractResource: The resource.
source
GenX.attributesMethod
attributes(r::AbstractResource)

Returns a tuple of the attribute names of the given resource.

Arguments

  • r::AbstractResource: The resource.

Returns

  • Tuple: A tuple with symbols representing the attribute names.
source
GenX.electrolyzerMethod
electrolyzer(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all electrolyzer resources in the vector rs.

source
GenX.flex_demandMethod
flex_demand(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all flexible demand resources in the vector rs.

source
GenX.has_all_options_contributingMethod
has_all_options_contributing(retrofit_res::AbstractResource, rs::Vector{T}) where T <: AbstractResource

Check if all retrofit options in the retrofit cluster of the retrofit resource retrofit_res contribute to min retirement.

Arguments

  • retrofit_res::AbstractResource: The retrofit resource.
  • rs::Vector{T}: The vector of resources.

Returns

  • Bool: True if all retrofit options contribute to min retirement, otherwise false.
source
GenX.has_all_options_not_contributingMethod
has_all_options_not_contributing(retrofit_res::AbstractResource, rs::Vector{T}) where T <: AbstractResource

Check if all retrofit options in the retrofit cluster of the retrofit resource retrofit_res do not contribute to min retirement.

Arguments

  • retrofit_res::AbstractResource: The retrofit resource.
  • rs::Vector{T}: The vector of resources.

Returns

  • Bool: True if all retrofit options do not contribute to min retirement, otherwise false.
source
GenX.hydroMethod
hydro(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all hydro resources in the vector rs.

source
GenX.ids_withMethod
ids_with(rs::Vector{T}, f::Function, default=default_zero) where T <: AbstractResource

Function for finding resources in a vector rs where the attribute specified by f is not equal to default.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • f::Function: The getter of the attribute.
  • default: The default value of the attribute.

Returns

  • ids (Vector{Int64}): The vector of resource ids with attribute not equal to default.

Examples

julia> ids_with(gen.Thermal, existing_cap_mw)
+4-element Vector{Int64}:
+ 21
+ 22
+ 23
+ 24
+julia> existing_cap_mw(gen[21])
+7.0773
source
GenX.ids_withMethod
ids_with(rs::Vector{T}, name::Symbol, default=default_zero) where T <: AbstractResource

Function for finding resources in a vector rs where the attribute specified by name is not equal to the default value of the attribute.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • name::Symbol: The name of the attribute.
  • default: The default value of the attribute.

Returns

  • ids (Vector{Int64}): The vector of resource ids with attribute not equal to default.

Examples

julia> ids_with(gen.Thermal, :existing_cap_mw)
+4-element Vector{Int64}:
+ 21
+ 22
+ 23
+ 24
+julia> existing_cap_mw(gen[21])
+7.0773
source
GenX.ids_with_all_options_contributingMethod
ids_with_all_options_contributing(rs::Vector{T}) where T <: AbstractResource

Find the resource ids of the retrofit units in the vector rs where all retrofit options contribute to min retirement.

Arguments

  • rs::Vector{T}: The vector of resources.

Returns

  • Vector{Int64}: The vector of resource ids.
source
GenX.ids_with_all_options_not_contributingMethod
ids_with_all_options_not_contributing(rs::Vector{T}) where T <: AbstractResource

Find the resource ids of the retrofit units in the vector rs where all retrofit options do not contribute to min retirement.

Arguments

  • rs::Vector{T}: The vector of resources.

Returns

  • Vector{Int64}: The vector of resource ids.
source
GenX.ids_with_nonnegMethod
ids_with_nonneg(rs::Vector{T}, f::Function) where T <: AbstractResource

Function for finding resources in a vector rs where the attribute specified by f is non-negative.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • f::Function: The getter of the attribute.

Returns

  • ids (Vector{Int64}): The vector of resource ids with non-negative attribute.

Examples

julia> ids_with_nonneg(gen, max_cap_mw)
source
GenX.ids_with_nonnegMethod
ids_with_nonneg(rs::Vector{T}, f::Function) where T <: AbstractResource

Function for finding resources in a vector rs where the attribute specified by name is non-negative.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • name::Symbol: The name of the attribute.

Returns

  • ids (Vector{Int64}): The vector of resource ids with non-negative attribute.

Examples

julia> ids_with_nonneg(gen, max_cap_mw)
source
GenX.ids_with_policyMethod
ids_with_policy(rs::Vector{T}, f::Function; tag::Int64) where T <: AbstractResource

Function for finding resources in a vector rs where the policy specified by f with tag equal to tag is positive.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • f::Function: The policy getter function.
  • tag::Int64: The tag of the policy.

Returns

  • ids (Vector{Int64}): The vector of resource ids with a positive value for policy f and tag tag.
source
GenX.ids_with_policyMethod

idswithpolicy(rs::Vector{T}, name::Symbol; tag::Int64) where T <: AbstractResource

Function for finding resources in a vector rs where the policy specified by name with tag equal to tag is positive.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • name::Symbol: The name of the policy.
  • tag::Int64: The tag of the policy.

Returns

  • ids (Vector{Int64}): The vector of resource ids with a positive value for policy name and tag tag.
source
GenX.ids_with_positiveMethod
ids_with_positive(rs::Vector{T}, f::Function) where T <: AbstractResource

Function for finding indices of resources in a vector rs where the attribute specified by f is positive.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • f::Function: The getter of the attribute.

Returns

  • ids (Vector{Int64}): The vector of resource ids with positive attribute.

Examples

julia> ids_with_positive(gen, max_cap_mw)
+3-element Vector{Int64}:
+ 3 
+ 4
+ 5
+julia> max_cap_mw(gen[3])
+4.888236
source
GenX.ids_with_positiveMethod
ids_with_positive(rs::Vector{T}, name::Symbol) where T <: AbstractResource

Function for finding indices of resources in a vector rs where the attribute specified by name is positive.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • name::Symbol: The name of the attribute.

Returns

  • Vector{Int64}: The vector of resource ids with positive attribute.

Examples

julia> ids_with_positive(gen, :max_cap_mw)
+3-element Vector{Int64}:
+ 3 
+ 4
+ 5
+julia> max_cap_mw(gen[3])
+4.888236
source
GenX.must_runMethod
must_run(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all must-run resources in the vector rs.

source
GenX.resource_by_nameMethod
resource_by_name(rs::Vector{<:AbstractResource}, name::AbstractString)

Find the resource with name in the vector rs.

Arguments

  • rs: A vector of resources.
  • name: The name of the resource.

Returns

  • AbstractResource: The resource with the name name.
source
GenX.resources_in_retrofit_cluster_by_ridMethod
resources_in_retrofit_cluster_by_rid(rs::Vector{<:AbstractResource}, cluster_id::String)

Find RID's of resources with retrofit cluster id `clusterid`.

Arguments

  • rs::Vector{<:AbstractResource}: The vector of resources.
  • cluster_id::String: The retrofit cluster id.

Returns

  • Vector{Int64}: The vector of resource ids in the retrofit cluster.
source
GenX.solarMethod
solar(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all co-located solar resources in the vector rs.

source
GenX.storageMethod
storage(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all storage resources in the vector rs.

source
GenX.storage_ac_chargeMethod
storage_ac_charge(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all co-located storage resources in the vector rs that charge AC.

source
GenX.storage_ac_dischargeMethod
storage_ac_discharge(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all co-located storage resources in the vector rs that discharge AC.

source
GenX.storage_dc_chargeMethod
storage_dc_charge(rs::Vector{T}) where T <: AbstractResource
+Returns the indices of all co-located storage resources in the vector `rs` that charge DC.
source
GenX.storage_dc_dischargeMethod
storage_dc_discharge(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all co-located storage resources in the vector rs that discharge DC.

source
GenX.thermalMethod
thermal(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all thermal resources in the vector rs.

source
GenX.validate_boolean_attributeMethod
validate_boolean_attribute(r::AbstractResource, attr::Symbol)

Validate that the attribute attr in the resource r is boolean {0, 1}.

Arguments

  • r::AbstractResource: The resource.
  • attr::Symbol: The name of the attribute.
source
GenX.vreMethod
vre(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all Vre resources in the vector rs.

source
GenX.vre_storMethod
vre_stor(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all VRE_STOR resources in the vector rs.

source
GenX.windMethod
wind(rs::Vector{T}) where T <: AbstractResource

Returns the indices of all co-located wind resources in the vector rs.

source
GenX.@interfaceMacro
interface(name, default=default_zero, type=AbstractResource)

Define a function interface for accessing the attribute specified by name in a resource of type type.

Arguments

  • name: The name of the attribute.
  • default: The default value to return if the attribute is not found.
  • type: The type of the resource.

Returns

  • Function: The generated function.

Examples

julia> @interface max_cap_mw 0 Vre
+julia> max_cap_mw(gen.Vre[3])
+4.888236
+julia> max_cap_mw.(gen.Vre) # vectorized
+5-element Vector{Float64}:
+  0.0
+  0.0
+  4.888236
+ 20.835569
+  9.848441999999999
source
diff --git a/previews/PR670/Model_Reference/Resources/retrofit/index.html b/previews/PR670/Model_Reference/Resources/retrofit/index.html new file mode 100644 index 0000000000..0c83928888 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/retrofit/index.html @@ -0,0 +1,4 @@ + +Retrofit · GenX.jl

Retrofit

GenX.retrofitMethod
retrofit(EP::Model, inputs::Dict)

This function defines the constraints for operation of retrofit technologies, including but not limited to carbon capture and thermal energy storage.

For retrofittable source technologies $y$ and retrofit technologies $r$ in the same region $z$ and retrofit cluster $id$, (i.e. $y \in RS(id)$ and $r \in RO(id)$), the total retrofit capacity $\Omega_{r}$ that may be installed is constrained by the available retrofittable capacity $P_{y}$ as well as the efficiency ${ef}_{r}$ of the retrofit technology.

\[\begin{aligned} + \sum_{y \in RS(id)}P_{y} = \sum_{r \in RO(id)}\frac{\Omega_{r}}{{ef}_{r}} \quad \quad \quad \quad \forall id \in {RETROFIT}, +\end{aligned}\]

where ${RETROFIT}$ represents the set of all retrofit IDs (clusters) in the model.

source
diff --git a/previews/PR670/Model_Reference/Resources/storage/index.html b/previews/PR670/Model_Reference/Resources/storage/index.html new file mode 100644 index 0000000000..b2dc8414fc --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/storage/index.html @@ -0,0 +1,43 @@ + +Storage · GenX.jl

Storage

GenX.storage!Function
storage!(EP::Model, inputs::Dict, setup::Dict)

A wide range of energy storage devices (all $o \in \mathcal{O}$) can be modeled in GenX, using one of two generic storage formulations: (1) storage technologies with symmetric charge and discharge capacity (all $o \in \mathcal{O}^{sym}$), such as Lithium-ion batteries and most other electrochemical storage devices that use the same components for both charge and discharge; and (2) storage technologies that employ distinct and potentially asymmetric charge and discharge capacities (all $o \in \mathcal{O}^{asym}$), such as most thermal storage technologies or hydrogen electrolysis/storage/fuel cell or combustion turbine systems.

If a capacity reserve margin is modeled, variables for virtual charge, $\Pi^{CRM}_{o,z,t}$, and virtual discharge, $\Theta^{CRM}_{o,z,t}$, are created to represent contributions that a storage device makes to the capacity reserve margin without actually generating power. (This functionality can be turned off with the parameter StorageVirtualDischarge in the GenX settings file.) These represent power that the storage device could have discharged or consumed if called upon to do so, based on its available state of charge. Importantly, a dedicated set of variables (those of the form $\Pi^{CRM}_{o,z,t}, \Theta^{CRM}_{o,z,t}$) and constraints are created to ensure that any virtual contributions to the capacity reserve margin could be made as actual charge/discharge if necessary without affecting system operations in any other timesteps. If a capacity reserve margin is not modeled, all related variables are fixed at 0. The overall contribution of storage devices to the system's capacity reserve margin in timestep $t$ is equal to $\sum_{y \in \mathcal{O}} \epsilon_{y,z,p}^{CRM} \times \left(\Theta_{y,z,t} + \Theta^{CRM}_{o,z,t} - \Pi^{CRM}_{o,z,t} - \Pi_{y,z,t} \right)$, and includes both actual and virtual charge and discharge.

\[\begin{aligned} + & \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Storage with symmetric charge and discharge capacity For storage technologies with symmetric charge and discharge capacity (all $o \in \mathcal{O}^{sym}$), charge rate, $\Pi_{o,z,t}$, and virtual charge rate, $\Pi^{CRM}_{o,z,t}$, are jointly constrained by the total installed power capacity, $\Omega_{o,z}$. Since storage resources generally represent a `cluster' of multiple similar storage devices of the same type/cost in the same zone, GenX permits storage resources to simultaneously charge and discharge (as some units could be charging while others discharge), with the simultaenous sum of charge, $\Pi_{o,z,t}$, discharge, $\Theta_{o,z,t}$, virtual charge, $\Pi^{CRM}_{o,z,t}$, and virtual discharge, $\Theta^{CRM}_{o,z,t}$, also limited by the total installed power capacity, $\Delta^{total}_{o,z}$. These two constraints are as follows:

\[\begin{aligned} +& \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T}\\ +& \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

These constraints are created with the function storage_symmetric!() in storage_symmetric.jl. If reserves are modeled, the following two constraints replace those above:

\[\begin{aligned} +& \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + f^{charge}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T}\\ +& \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + f^{charge}_{o,z,t} + \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} + f^{discharge}_{o,z,t} + r^{discharge}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}^{sym}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

where $f^{charge}_{o,z,t}$ is the contribution of storage resources to frequency regulation while charging, $f^{discharge}_{o,z,t}$ is the contribution of storage resources to frequency regulation while discharging, and $r^{discharge}_{o,z,t}$ is the contribution of storage resources to upward reserves while discharging. Note that as storage resources can contribute to regulation and reserves while either charging or discharging, the proxy variables $f^{charge}_{o,z,t}, f^{discharge}_{o,z,t}$ and $r^{charge}_{o,z,t}, r^{discharge}_{o,z,t}$ are created for storage resources where the total contribution to regulation and reserves, $f_{o,z,t}, r_{o,z,t}$ is the sum of the proxy variables. These constraints are created with the function storage_symmetric_operational_reserves!() in storage_symmetric.jl. Storage with asymmetric charge and discharge capacity For storage technologies with asymmetric charge and discharge capacities (all $o \in \mathcal{O}^{asym}$), charge rate, $\Pi_{o,z,t}$, is constrained by the total installed charge capacity, $\Delta^{total, charge}_{o,z}$, as follows:

\[\begin{aligned} + & \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} \leq \Delta^{total, charge}_{o,z} & \quad \forall o \in \mathcal{O}^{asym}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

These constraints are created with the function storage_asymmetric() in storage_asymmetric.jl. If reserves are modeled, the above constraint is replaced by the following:

\[\begin{aligned} + & \Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + f^{charge}_{o,z,t} \leq \Delta^{total, charge}_{o,z} & \quad \forall o \in \mathcal{O}^{asym}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

where $f^{+}_{y=o,z,t}$ is the contribution of storage resources to frequency regulation while charging. These constraints are created with the function storage_symmetric_operational_reserves!() in storage_asymmetric.jl. All storage resources The following constraints apply to all storage resources, $o \in \mathcal{O}$, regardless of whether the charge/discharge capacities are symmetric or asymmetric. The following two constraints track the state of charge of the storage resources at the end of each time period, relating the volume of energy stored at the end of the time period, $\Gamma_{o,z,t}$, to the state of charge at the end of the prior time period, $\Gamma_{o,z,t-1}$, the charge and discharge decisions in the current time period, $\Pi_{o,z,t}, \Theta_{o,z,t}$, and the self discharge rate for the storage resource (if any), $\eta_{o,z}^{loss}$. The first of these two constraints enforces storage inventory balance for interior time steps $(t \in \mathcal{T}^{interior})$, while the second enforces storage balance constraint for the initial time step $(t \in \mathcal{T}^{start})$.

\[\begin{aligned} + & \Gamma_{o,z,t} =\Gamma_{o,z,t-1} - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,t} + \eta_{o,z}^{charge}\Pi_{o,z,t} - \eta_{o,z}^{loss}\Gamma_{o,z,t-1} \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior}\\ + & \Gamma_{o,z,t} =\Gamma_{o,z,t+\tau^{period}-1} - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,t} + \eta_{o,z}^{charge}\Pi_{o,z,t} - \eta_{o,z}^{loss}\Gamma_{o,z,t+\tau^{period}-1} \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

If a capacity reserve margin is modeled, then the following constraints track the relationship between the virtual charge, $\Pi^{CRM}_{o,z,t}$, and virtual discharge, $\Theta^{CRM}_{o,z,t}$, variables and a third variable, $\Gamma^{CRM}_{o,z,t}$, representing the amount of state of charge that must be held in reserve to enable these virtual capacity reserve margin contributions, ensuring that the storage device could deliver its pledged capacity if called upon to do so without affecting its operations in other timesteps. $\Gamma^{CRM}_{o,z,t}$ is tracked similarly to the devices overall state of charge based on its value in the previous timestep and the virtual charge and discharge in the current timestep. Unlike the regular state of charge, virtual discharge $\Theta^{CRM}_{o,z,t}$ increases $\Gamma^{CRM}_{o,z,t}$ (as more charge must be held in reserve to support more virtual discharge), and $\Pi^{CRM}_{o,z,t}$ reduces $\Gamma^{CRM}_{o,z,t}$.

\[\begin{aligned} + & \Gamma^{CRM}_{o,z,t} =\Gamma^{CRM}_{o,z,t-1} + \frac{1}{\eta_{o,z}^{discharge}}\Theta^{CRM}_{o,z,t} - \eta_{o,z}^{charge}\Pi^{CRM}_{o,z,t} - \eta_{o,z}^{loss}\Gamma^{CRM}_{o,z,t-1} \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior}\\ + & \Gamma^{CRM}_{o,z,t} =\Gamma^{CRM}_{o,z,t+\tau^{period}-1} + \frac{1}{\eta_{o,z}^{discharge}}\Theta^{CRM}_{o,z,t} - \eta_{o,z}^{charge}\Pi^{CRM}_{o,z,t} - \eta_{o,z}^{loss}\Gamma^{CRM}_{o,z,t+\tau^{period}-1} \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

The energy held in reserve, $\Gamma^{CRM}_{o,z,t}$, also acts as a lower bound on the overall state of charge $\Gamma_{o,z,t}$. This ensures that the storage device cannot use state of charge that would not have been available had it been called on to actually contribute its pledged virtual discharge at some earlier timestep. This relationship is described by the following equation:

\[\begin{aligned} + & \Gamma_{o,z,t} \geq \Gamma^{CRM}_{o,z,t} +\end{aligned}\]

When modeling the entire year as a single chronological period with total number of time steps of $\tau^{period}$, storage inventory in the first time step is linked to storage inventory at the last time step of the period representing the year. Alternatively, when modeling the entire year with multiple representative periods, this constraint relates storage inventory in the first timestep of the representative period with the inventory at the last time step of the representative period, where each representative period is made of $\tau^{period}$ time steps. In this implementation, energy exchange between representative periods is not permitted. When modeling representative time periods, GenX enables modeling of long duration energy storage which tracks state of charge (and state of charge held in reserve, if a capacity reserve margin is being modeled) between representative periods enable energy to be moved throughout the year. If there is more than one representative period and LDS has been enabled for resources in Generators.csv, this function calls long_duration_storage() in long_duration_storage.jl to enable this feature. The next constraint limits the volume of energy stored at any time, $\Gamma_{o,z,t}$, to be less than the installed energy storage capacity, $\Delta^{total, energy}_{o,z}$. Finally, the maximum combined discharge and virtual discharge rate for storage resources, $\Pi_{o,z,t} + \Pi^{CRM}_{o,z,t}$, is constrained to be less than the discharge power capacity, $\Omega_{o,z,t}$ or the state of charge at the end of the last period, $\Gamma_{o,z,t-1}$, whichever is less.

\[\begin{aligned} + & \Gamma_{o,z,t} \leq \Delta^{total, energy}_{o,z} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} \leq \Gamma_{o,z,t-1} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

The above constraints are established in storage_all!() in storage_all.jl. If reserves are modeled, two pairs of proxy variables $f^{charge}_{o,z,t}, f^{discharge}_{o,z,t}$ and $r^{charge}_{o,z,t}, r^{discharge}_{o,z,t}$ are created for storage resources, to denote the contribution of storage resources to regulation or reserves while charging or discharging, respectively. The total contribution to regulation and reserves, $f_{o,z,t}, r_{o,z,t}$ is then the sum of the proxy variables:

\[\begin{aligned} + & f_{o,z,t} = f^{charge}_{o,z,t} + f^{dicharge}_{o,z,t} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & r_{o,z,t} = r^{charge}_{o,z,t} + r^{dicharge}_{o,z,t} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

The total storage contribution to frequency regulation ($f_{o,z,t}$) and reserves ($r_{o,z,t}$) are each limited specified fraction of installed discharge power capacity ($\upsilon^{reg}_{y,z}, \upsilon^{rsv}_{y,z}$), reflecting the maximum ramp rate for the storage resource in whatever time interval defines the requisite response time for the regulation or reserve products (e.g., 5 mins or 15 mins or 30 mins). These response times differ by system operator and reserve product, and so the user should define these parameters in a self-consistent way for whatever system context they are modeling.

\[\begin{aligned} + f_{y,z,t} \leq \upsilon^{reg}_{y,z} \times \Delta^{total}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + r_{y,z, t} \leq \upsilon^{rsv}_{y,z}\times \Delta^{total}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{W}, z \in \mathcal{Z}, t \in \mathcal{T} + \end{aligned}\]

When charging, reducing the charge rate is contributing to upwards reserve and frequency regulation as it drops net demand. As such, the sum of the charge rate plus contribution to regulation and reserves up must be greater than zero. Additionally, the discharge rate plus the contribution to regulation must be greater than zero.

\[\begin{aligned} + & \Pi_{o,z,t} - f^{charge}_{o,z,t} - r^{charge}_{o,z,t} \geq 0 & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Theta_{o,z,t} - f^{discharge}_{o,z,t} \geq 0 & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Additionally, when reserves are modeled, the maximum charge rate, virtual charge rate, and contribution to regulation while charging can be no greater than the available energy storage capacity, or the difference between the total energy storage capacity, $\Delta^{total, energy}_{o,z}$, and the state of charge at the end of the previous time period, $\Gamma_{o,z,t-1}$, while accounting for charging losses $\eta_{o,z}^{charge}$. Note that for storage to contribute to reserves down while charging, the storage device must be capable of increasing the charge rate (which increase net load).

\[\begin{aligned} + & \eta_{o,z}^{charge} \times (\Pi_{o,z,t} + \Pi^{CRM}_{o,z,t} + f^{charge}_{o,z,t}) \leq \Delta^{energy, total}_{o,z} - \Gamma_{o,z,t-1} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Finally, the constraints on maximum discharge rate are replaced by the following, to account for capacity contributed to regulation and reserves:

\[\begin{aligned} + & \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} + f^{discharge}_{o,z,t} + r^{discharge}_{o,z,t} \leq \Delta^{total}_{o,z} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Theta_{o,z,t} + \Theta^{CRM}_{o,z,t} + f^{discharge}_{o,z,t} + r^{discharge}_{o,z,t} \leq \Gamma_{o,z,t-1} & \quad \forall o \in \mathcal{O}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

The above reserve related constraints are established by storage_all_operational_reserves!() in storage_all.jl

source
diff --git a/previews/PR670/Model_Reference/Resources/storage_all/index.html b/previews/PR670/Model_Reference/Resources/storage_all/index.html new file mode 100644 index 0000000000..76c8826418 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/storage_all/index.html @@ -0,0 +1,2 @@ + +Storage All · GenX.jl

Storage All

GenX.storage_all!Method
storage_all!(EP::Model, inputs::Dict, setup::Dict)

Sets up variables and constraints common to all storage resources. See storage() in storage.jl for description of constraints.

source
diff --git a/previews/PR670/Model_Reference/Resources/storage_asymmetric/index.html b/previews/PR670/Model_Reference/Resources/storage_asymmetric/index.html new file mode 100644 index 0000000000..6a978368b4 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/storage_asymmetric/index.html @@ -0,0 +1,2 @@ + +Storage Asymmetric · GenX.jl

Storage Asymmetric

GenX.storage_asymmetric!Method
storage_asymmetric!(EP::Model, inputs::Dict, setup::Dict)

Sets up variables and constraints specific to storage resources with asymmetric charge and discharge capacities. See storage() in storage.jl for description of constraints.

source
GenX.storage_asymmetric_operational_reserves!Method
storage_asymmetric_operational_reserves!(EP::Model, inputs::Dict)

Sets up variables and constraints specific to storage resources with asymmetric charge and discharge capacities when reserves are modeled. See storage() in storage.jl for description of constraints.

source
diff --git a/previews/PR670/Model_Reference/Resources/storage_symmetric/index.html b/previews/PR670/Model_Reference/Resources/storage_symmetric/index.html new file mode 100644 index 0000000000..87bc994171 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/storage_symmetric/index.html @@ -0,0 +1,2 @@ + +Storage Symmetric · GenX.jl

Storage Symmetric

GenX.storage_symmetric!Method
storage_symmetric!(EP::Model, inputs::Dict, setup::Dict)

Sets up variables and constraints specific to storage resources with symmetric charge and discharge capacities. See storage() in storage.jl for description of constraints.

source
GenX.storage_symmetric_operational_reserves!Method
storage_symmetric_operational_reserves!(EP::Model, inputs::Dict)

Sets up variables and constraints specific to storage resources with symmetric charge and discharge capacities when reserves are modeled. See storage() in storage.jl for description of constraints.

source
diff --git a/previews/PR670/Model_Reference/Resources/thermal/index.html b/previews/PR670/Model_Reference/Resources/thermal/index.html new file mode 100644 index 0000000000..f8b2c4803a --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/thermal/index.html @@ -0,0 +1,6 @@ + +Thermal · GenX.jl

Thermal

GenX.thermal!Method
thermal!(EP::Model, inputs::Dict, setup::Dict)

The thermal module creates decision variables, expressions, and constraints related to thermal power plants e.g. coal, oil or natural gas steam plants, natural gas combined cycle and combustion turbine plants, nuclear, hydrogen combustion etc. This module uses the following 'helper' functions in separate files: thermal_commit() for thermal resources subject to unit commitment decisions and constraints (if any) and thermal_no_commit() for thermal resources not subject to unit commitment (if any).

source
GenX.thermal_plant_effective_capacityMethod
thermal_plant_effective_capacity(EP::Model,
+                                 inputs::Dict,
+                                 resources::Vector{Int},
+                                 capres_zone::Int,
+                                 timesteps::Vector{Int})::Matrix{Float64}

Effective capacity in a capacity reserve margin zone for certain resources in the given timesteps.

source
diff --git a/previews/PR670/Model_Reference/Resources/thermal_commit/index.html b/previews/PR670/Model_Reference/Resources/thermal_commit/index.html new file mode 100644 index 0000000000..c263365fd7 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/thermal_commit/index.html @@ -0,0 +1,46 @@ + +Thermal Commit · GenX.jl

Thermal Commit

GenX.thermal_commit!Method
thermal_commit!(EP::Model, inputs::Dict, setup::Dict)

This function defines the operating constraints for thermal power plants subject to unit commitment constraints on power plant start-ups and shut-down decision ($y \in UC$).

We model capacity investment decisions and commitment and cycling (start-up, shut-down) of thermal generators using the integer clustering technique developed in Palmintier, 2011, Palmintier, 2013, and Palmintier, 2014. In a typical binary unit commitment formulation, each unit is either on or off. With the clustered unit commitment formulation, one or more cluster(s) of similar generators are clustered by type and zone (typically using heat rate and fixed O\&M cost to create clusters), and the integer commitment state variable for each cluster varies from zero to the number of units in the cluster, $\frac{\Delta^{total}_{y,z}}{\Omega^{size}_{y,z}}$. As discussed in \cite{Palmintier2014}, this approach replaces the large set of binary commitment decisions and associated constraints, which scale directly with the number of individual units, with a smaller set of integer commitment states and constraints, one for each cluster $y$. The dimensionality of the problem thus scales with the number of units of a given type in each zone, rather than by the number of discrete units, significantly improving computational efficiency. However, this method entails the simplifying assumption that all clustered units have identical parameters (e.g., capacity size, ramp rates, heat rate) and that all committed units in a given time step $t$ are operating at the same power output per unit.

Power balance expression

This function adds the sum of power generation from thermal units subject to unit commitment ($\Theta_{y \in UC,t \in T,z \in Z}$) to the power balance expression.

Startup and shutdown events (thermal plant cycling)

Capacitated limits on unit commitment decision variables

Thermal resources subject to unit commitment ($y \in \mathcal{UC}$) adhere to the following constraints on commitment states, startup events, and shutdown events, which limit each decision to be no greater than the maximum number of discrete units installed (as per the following three constraints):

\[\begin{aligned} +\nu_{y,z,t} \leq \frac{\Delta^{\text{total}}_{y,z}}{\Omega^{size}_{y,z}} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} +\chi_{y,z,t} \leq \frac{\Delta^{\text{total}}_{y,z}}{\Omega^{size}_{y,z}} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} +\zeta_{y,z,t} \leq \frac{\Delta^{\text{total}}_{y,z}}{\Omega^{size}_{y,z}} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 1-3 in the code)

where decision $\nu_{y,z,t}$ designates the commitment state of generator cluster $y$ in zone $z$ at time $t$, decision $\chi_{y,z,t}$ represents number of startup decisions, decision $\zeta_{y,z,t}$ represents number of shutdown decisions, $\Delta^{\text{total}}_{y,z}$ is the total installed capacity, and parameter $\Omega^{size}_{y,z}$ is the unit size.

Commitment state constraint linking start-up and shut-down decisions

Additionally, the following constarint maintains the commitment state variable across time, $\nu_{y,z,t}$, as the sum of the commitment state in the prior, $\nu_{y,z,t-1}$, period plus the number of units started in the current period, $\chi_{y,z,t}$, less the number of units shut down in the current period, $\zeta_{y,z,t}$:

\[\begin{aligned} +&\nu_{y,z,t} =\nu_{y,z,t-1} + \chi_{y,z,t} - \zeta_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T}^{interior} \\ +&\nu_{y,z,t} =\nu_{y,z,t +\tau^{period}-1} + \chi_{y,z,t} - \zeta_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T}^{start} +\end{aligned}\]

(See Constraint 4 in the code)

Like other time-coupling constraints, this constraint wraps around to link the commitment state in the first time step of the year (or each representative period), $t \in \mathcal{T}^{start}$, to the last time step of the year (or each representative period), $t+\tau^{period}-1$.

Ramping constraints

Thermal resources subject to unit commitment ($y \in UC$) adhere to the following ramping constraints on hourly changes in power output:

\[\begin{aligned} + \Theta_{y,z,t-1} + f_{y, z, t-1}+r_{y, z, t-1} - \Theta_{y,z,t}-f_{y, z, t}&\leq \kappa^{down}_{y,z} \cdot \Omega^{size}_{y,z} \cdot (\nu_{y,z,t} - \chi_{y,z,t}) & \\[6pt] + \qquad & - \: \rho^{min}_{y,z} \cdot \Omega^{size}_{y,z} \cdot \chi_{y,z,t} & \hspace{0.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} \\[6pt] + \qquad & + \: \text{min}( \rho^{max}_{y,z,t}, \text{max}( \rho^{min}_{y,z}, \kappa^{down}_{y,z} ) ) \cdot \Omega^{size}_{y,z} \cdot \zeta_{y,z,t} & +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t}+f_{y, z, t}+r_{y, z, t} - \Theta_{y,z,t-1}- f_{y, z, t-1} &\leq \kappa^{up}_{y,z} \cdot \Omega^{size}_{y,z} \cdot (\nu_{y,z,t} - \chi_{y,z,t}) & \\[6pt] + \qquad & + \: \text{min}( \rho^{max}_{y,z,t}, \text{max}( \rho^{min}_{y,z}, \kappa^{up}_{y,z} ) ) \cdot \Omega^{size}_{y,z} \cdot \chi_{y,z,t} & \hspace{0.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} \\[6pt] + \qquad & - \: \rho^{min}_{y,z} \cdot \Omega^{size}_{y,z} \cdot \zeta_{y,z,t} & +\end{aligned}\]

(See Constraints 5-6 in the code)

where decision $\Theta_{y,z,t}$, $f_{y, z, t}$, and $r_{y, z, t}$ are respectively, the energy injected into the grid, regulation, and reserve by technology $y$ in zone $z$ at time $t$, parameter $\kappa_{y,z,t}^{up|down}$ is the maximum ramp-up or ramp-down rate as a percentage of installed capacity, parameter $\rho_{y,z}^{min}$ is the minimum stable power output per unit of installed capacity, and parameter $\rho_{y,z,t}^{max}$ is the maximum available generation per unit of installed capacity. These constraints account for the ramping limits for committed (online) units as well as faster changes in power enabled by units starting or shutting down in the current time step.

Minimum and maximum power output

If not modeling regulation and spinning reserves, thermal resources subject to unit commitment adhere to the following constraints that ensure power output does not exceed minimum and maximum feasible levels:

\[\begin{aligned} + \Theta_{y,z,t} \geq \rho^{min}_{y,z} \times \Omega^{size}_{y,z} \times \nu_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t} \leq \rho^{max}_{y,z} \times \Omega^{size}_{y,z} \times \nu_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 7-8 the code)

If modeling reserves and regulation, these constraints are replaced by those established in this thermal_commit_operational_reserves().

Minimum and maximum up and down time

Thermal resources subject to unit commitment adhere to the following constraints on the minimum time steps after start-up before a unit can shutdown again (minimum up time) and the minimum time steps after shut-down before a unit can start-up again (minimum down time):

\[\begin{aligned} + \nu_{y,z,t} \geq \displaystyle \sum_{\hat{t} = t-(\tau^{up}_{y,z}-1)}^t \chi_{y,z,\hat{t}} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \frac{\overline{\Delta_{y,z}} + \Omega_{y,z} - \Delta_{y,z}}{\Omega^{size}_{y,z}} - \nu_{y,z,t} \geq \displaystyle \sum_{\hat{t} = t-(\tau^{down}_{y,z}-1)}^t \zeta_{y,z,\hat{t}} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 9-10 in the code)

where $\tau_{y,z}^{up|down}$ is the minimum up or down time for units in generating cluster $y$ in zone $z$.

Like with the ramping constraints, the minimum up and down constraint time also wrap around from the start of each time period to the end of each period. It is recommended that users of GenX must use longer subperiods than the longest min up/down time if modeling UC. Otherwise, the model will report error.

source
GenX.thermal_commit_operational_reserves!Method
thermal_commit_operational_reserves!(EP::Model, inputs::Dict)

This function is called by the thermal_commit() function when regulation and reserves constraints are active and defines reserve related constraints for thermal power plants subject to unit commitment constraints on power plant start-ups and shut-down decisions.

Maximum contributions to frequency regulation and reserves

When modeling frequency regulation and reserves contributions, thermal units subject to unit commitment adhere to the following constraints which limit the maximum contribution to regulation and reserves in each time step to a specified maximum fraction ($,\upsilon^{rsv}_{y,z}$) of the commitment capacity in that time step ($(\Omega^{size}_{y,z} \cdot \nu_{y,z,t})$):

\[\begin{aligned} + f_{y,z,t} \leq \upsilon^{reg}_{y,z} \times \rho^{max}_{y,z,t} (\Omega^{size}_{y,z} \times \nu_{y,z,t}) \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + r_{y,z,t} \leq \upsilon^{rsv}_{y,z} \times \rho^{max}_{y,z,t} (\Omega^{size}_{y,z} \times \nu_{y,z,t}) \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

where $f_{y,z,t}$ is the frequency regulation contribution limited by the maximum regulation contribution $\upsilon^{reg}_{y,z}$, and $r_{y,z,t}$ is the reserves contribution limited by the maximum reserves contribution $\upsilon^{rsv}_{y,z}$. Limits on reserve contributions reflect the maximum ramp rate for the thermal resource in whatever time interval defines the requisite response time for the regulation or reserve products (e.g., 5 mins or 15 mins or 30 mins). These response times differ by system operator and reserve product, and so the user should define these parameters in a self-consistent way for whatever system context they are modeling.

Minimum and maximum power output

When modeling frequency regulation and spinning reserves contributions, thermal resources subject to unit commitment adhere to the following constraints that ensure the sum of power output and reserve and/or regulation contributions do not exceed minimum and maximum feasible power output:

\[\begin{aligned} + \Theta_{y,z,t} - f_{y,z,t} \geq \rho^{min}_{y,z} \times Omega^{size}_{y,z} \times \nu_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t} + f_{y,z,t} + r_{y,z,t} \leq \rho^{max}_{y,z,t} \times \Omega^{size}_{y,z} \times \nu_{y,z,t} + \hspace{1.5cm} \forall y \in \mathcal{UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

source
diff --git a/previews/PR670/Model_Reference/Resources/thermal_no_commit/index.html b/previews/PR670/Model_Reference/Resources/thermal_no_commit/index.html new file mode 100644 index 0000000000..b79beebed9 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/thermal_no_commit/index.html @@ -0,0 +1,22 @@ + +Thermal No Commit · GenX.jl

Thermal No Commit

GenX.thermal_no_commit!Method
thermal_no_commit!(EP::Model, inputs::Dict, setup::Dict)

This function defines the operating constraints for thermal power plants NOT subject to unit commitment constraints on power plant start-ups and shut-down decisions ($y \in H \setminus UC$).

Ramping limits

Thermal resources not subject to unit commitment ($y \in H \setminus UC$) adhere instead to the following ramping limits on hourly changes in power output:

\[\begin{aligned} + \Theta_{y,z,t-1} - \Theta_{y,z,t} \leq \kappa_{y,z}^{down} \Delta^{\text{total}}_{y,z} \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t} - \Theta_{y,z,t-1} \leq \kappa_{y,z}^{up} \Delta^{\text{total}}_{y,z} \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 1-2 in the code)

This set of time-coupling constraints wrap around to ensure the power output in the first time step of each year (or each representative period), $t \in \mathcal{T}^{start}$, is within the eligible ramp of the power output in the final time step of the year (or each representative period), $t+\tau^{period}-1$.

Minimum and maximum power output

When not modeling regulation and reserves, thermal units not subject to unit commitment decisions are bound by the following limits on maximum and minimum power output:

\[\begin{aligned} + \Theta_{y,z,t} \geq \rho^{min}_{y,z} \times \Delta^{total}_{y,z} + \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t} \leq \rho^{max}_{y,z,t} \times \Delta^{total}_{y,z} + \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

(See Constraints 3-4 in the code)

source
GenX.thermal_no_commit_operational_reserves!Method
thermal_no_commit_operational_reserves!(EP::Model, inputs::Dict)

This function is called by the thermal_no_commit() function when regulation and reserves constraints are active and defines reserve related constraints for thermal power plants not subject to unit commitment constraints on power plant start-ups and shut-down decisions.

Maximum contributions to frequency regulation and reserves

Thermal units not subject to unit commitment adhere instead to the following constraints on maximum reserve and regulation contributions:

\[\begin{aligned} + f_{y,z,t} \leq \upsilon^{reg}_{y,z} \times \rho^{max}_{y,z,t} \Delta^{\text{total}}_{y,z} \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + r_{y,z,t} \leq \upsilon^{rsv}_{y,z} \times \rho^{max}_{y,z,t} \Delta^{\text{total}}_{y,z} \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

where $f_{y,z,t}$ is the frequency regulation contribution limited by the maximum regulation contribution $\upsilon^{reg}_{y,z}$, and $r_{y,z,t}$ is the reserves contribution limited by the maximum reserves contribution $\upsilon^{rsv}_{y,z}$. Limits on reserve contributions reflect the maximum ramp rate for the thermal resource in whatever time interval defines the requisite response time for the regulation or reserve products (e.g., 5 mins or 15 mins or 30 mins). These response times differ by system operator and reserve product, and so the user should define these parameters in a self-consistent way for whatever system context they are modeling.

Minimum and maximum power output

When modeling regulation and spinning reserves, thermal units not subject to unit commitment are bound by the following limits on maximum and minimum power output:

\[\begin{aligned} + \Theta_{y,z,t} - f_{y,z,t} \geq \rho^{min}_{y,z} \times \Delta^{\text{total}}_{y,z} + \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

\[\begin{aligned} + \Theta_{y,z,t} + f_{y,z,t} + r_{y,z,t} \leq \rho^{max}_{y,z,t} \times \Delta^{\text{total}}_{y,z} + \hspace{1cm} \forall y \in \mathcal{H \setminus UC}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

Note there are multiple versions of these constraints in the code in order to avoid creation of unecessary constraints and decision variables for thermal units unable to provide regulation and/or reserves contributions due to input parameters (e.g. Reg_Max=0 and/or RSV_Max=0).

source
diff --git a/previews/PR670/Model_Reference/Resources/vre_stor/index.html b/previews/PR670/Model_Reference/Resources/vre_stor/index.html new file mode 100644 index 0000000000..e1dae56065 --- /dev/null +++ b/previews/PR670/Model_Reference/Resources/vre_stor/index.html @@ -0,0 +1,236 @@ + +Co-located VRE and Storage · GenX.jl

Co-located VRE-Storage Module

GenX.inverter_vre_stor!Method
inverter_vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This function defines the decision variables, expressions, and constraints for the inverter component of each co-located VRE and storage generator.

The total inverter capacity of each resource is defined as the sum of the existing inverter capacity plus the newly invested inverter capacity minus any retired inverter capacity:

\[\begin{aligned} + & \Delta^{total, inv}_{y,z} = (\overline{\Delta^{inv}_{y,z}} + \Omega^{inv}_{y,z} - \Delta^{inv}_{y,z}) \quad \forall y \in \mathcal{VS}^{inv}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more inverter capacity than existing inverter capacity:

\[\begin{aligned} + & \Delta^{inv}_{y,z} \leq \overline{\Delta^{inv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{inv}, z \in \mathcal{Z} + \end{aligned}\]

For resources where $\overline{\Omega^{inv}_{y,z}}$ and $\underline{\Omega^{inv}_{y,z}}$ are defined, then we impose constraints on minimum and maximum capacity:

\[\begin{aligned} + & \Delta^{total, inv}_{y,z} \leq \overline{\Omega^{inv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{inv}, z \in \mathcal{Z} \\ + & \Delta^{total, inv}_{y,z} \geq \underline{\Omega^{inv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{inv}, z \in \mathcal{Z} +\end{aligned}\]

The last constraint ensures that the maximum DC grid exports and imports must be less than the inverter capacity. Without any capacity reserve margin or operating reserves, the constraint is:

\[\begin{aligned} + & \eta^{inverter}_{y,z} \times (\Theta^{pv}_{y,z,t} + \Theta^{dc}_{y,z,t}) + \frac{\Pi_{y,z,t}^{dc}}{\eta^{inverter}_{y,z}} \leq \Delta^{total, inv}_{y,z} \quad \forall y \in \mathcal{VS}^{inv}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

With only capacity reserve margins, the maximum DC grid exports and imports constraint becomes:

\[\begin{aligned} + & \eta^{inverter}_{y,z} \times (\Theta^{pv}_{y,z,t} + \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t}) + \frac{\Pi_{y,z,t}^{dc} + \Pi_{y,z,t}^{CRM,dc}}{\eta^{inverter}_{y,z}} \\ + & \leq \Delta^{total, inv}_{y,z} \quad \forall y \in \mathcal{VS}^{inv}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

With only operating reserves, the maximum DC grid exports and imports constraint becomes:

\[\begin{aligned} + & \eta^{inverter}_{y,z} \times (\Theta^{pv}_{y,z,t} + \Theta^{dc}_{y,z,t} + f^{pv}_{y,z,t} + r^{pv}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t}) + \frac{\Pi_{y,z,t}^{dc} + f^{dc,cha}_{y,z,t}}{\eta^{inverter}_{y,z}} \\ + & \leq \Delta^{total, inv}_{y,z} \quad \forall y \in \mathcal{VS}^{inv}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

With both capacity reserve margins and operating reserves, the maximum DC grid exports and imports constraint becomes:

\[\begin{aligned} + & \eta^{inverter}_{y,z} \times (\Theta^{pv}_{y,z,t} + \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t} + f^{pv}_{y,z,t} + r^{pv}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t}) \\ + & + \frac{\Pi_{y,z,t}^{dc} + \Pi_{y,z,t}^{CRM,dc} + f^{dc,cha}_{y,z,t}}{\eta^{inverter}_{y,z}} \leq \Delta^{total, inv}_{y,z} \quad \forall y \in \mathcal{VS}^{inv}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

In addition, this function adds investment and fixed O&M related costs related to the inverter capacity to the objective function:

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{inv}} \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST, inv}_{y,z} \times \Omega^{inv}_{y,z}) + + (\pi^{FOM, inv}_{y,z} \times \Delta^{total,inv}_{y,z})\right) +\end{aligned}\]

source
GenX.investment_charge_vre_stor!Method
investment_charge_vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This function activates the decision variables and constraints for asymmetric storage resources (independent charge and discharge power capacities (any STOR flag = 2)). For asymmetric storage resources, the function is enabled so charging and discharging can occur either through DC or AC capabilities. For example, a storage resource can be asymmetrically charged and discharged via DC capabilities or a storage resource could be charged via AC capabilities and discharged through DC capabilities. This module is configured such that both AC and DC charging (or discharging) cannot simultaneously occur.

The total charge/discharge DC and AC capacities of each resource are defined as the sum of the existing charge/discharge DC and AC capacities plus the newly invested charge/discharge DC and AC capacities minus any retired charge/discharge DC and AC capacities:

\[\begin{aligned} + & \Delta^{total,dc,dis}_{y,z} =(\overline{\Delta^{dc,dis}_{y,z}}+\Omega^{dc,dis}_{y,z}-\Delta^{dc,dis}_{y,z}) \quad \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,dc,cha}_{y,z} =(\overline{\Delta^{dc,cha}_{y,z}}+\Omega^{dc,cha}_{y,z}-\Delta^{dc,cha}_{y,z}) \quad \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,dis}_{y,z} =(\overline{\Delta^{ac,dis}_{y,z}}+\Omega^{ac,dis}_{y,z}-\Delta^{ac,dis}_{y,z}) \quad \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,cha}_{y,z} =(\overline{\Delta^{ac,cha}_{y,z}}+\Omega^{ac,cha}_{y,z}-\Delta^{ac,cha}_{y,z}) \quad \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more capacity than existing capacity:

\[\begin{aligned} + &\Delta^{dc,dis}_{y,z} \leq \overline{\Delta^{dc,dis}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z} \\ + &\Delta^{dc,cha}_{y,z} \leq \overline{\Delta^{dc,cha}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z} \\ + &\Delta^{ac,dis}_{y,z} \leq \overline{\Delta^{ac,dis}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z} \\ + &\Delta^{ac,cha}_{y,z} \leq \overline{\Delta^{ac,cha}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega_{y,z}^{dc,dis}}, \overline{\Omega_{y,z}^{dc,cha}}, \overline{\Omega_{y,z}^{ac,dis}}, \overline{\Omega_{y,z}^{ac,cha}}$ and $\underline{\Omega_{y,z}^{dc,dis}}, \underline{\Omega_{y,z}^{dc,cha}}, \underline{\Omega_{y,z}^{ac,dis}}, \underline{\Omega_{y,z}^{ac, cha}}$ are defined, then we impose constraints on minimum and maximum charge/discharge DC and AC power capacity:

\[\begin{aligned} + & \Delta^{total,dc,dis}_{y,z} \leq \overline{\Omega}^{dc,dis}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,dc,dis}_{y,z} \geq \underline{\Omega}^{dc,dis}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,dc,cha}_{y,z} \leq \overline{\Omega}^{dc,cha}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z} \\ + & \Delta^{total,dc,cha}_{y,z} \geq \underline{\Omega}^{dc,cha}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,dis}_{y,z} \leq \overline{\Omega}^{ac,dis}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,dis}_{y,z} \geq \underline{\Omega}^{ac,dis}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,cha}_{y,z} \leq \overline{\Omega}^{ac,cha}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z} \\ + & \Delta^{total,ac,cha}_{y,z} \geq \underline{\Omega}^{ac,cha}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z} \\ +\end{aligned}\]

Furthermore, for storage technologies with asymmetric charge and discharge capacities (all $y \in \mathcal{VS}^{asym,dc,dis}, y \in \mathcal{VS}^{asym,dc,cha}, y \in \mathcal{VS}^{asym,ac,dis}, y \in \mathcal{VS}^{asym,ac,cha}$), the charge rate, $\Pi^{dc}_{y,z,t}, \Pi^{ac}_{y,z,t}$, is constrained by the total installed charge capacity, $\Delta^{total,dc,cha}_{y,z}, \Delta^{total,ac,cha}_{y,z}$. Similarly the discharge rate, $\Theta^{dc}_{y,z,t}, \Theta^{ac}_{y,z,t}$, is constrained by the total installed discharge capacity, $\Delta^{total,dc,dis}_{y,z}, \Delta^{total,ac,dis}_{y,z}$. Without any activated capacity reserve margin policies or operating reserves, the constraints are as follows:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} \leq \Delta^{total,dc,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{dc}_{y,z,t} \leq \Delta^{total,dc,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} \leq \Delta^{total,ac,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{ac}_{y,z,t} \leq \Delta^{total,ac,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

Adding only the capacity reserve margin constraints, the asymmetric charge and discharge DC and AC rates plus the 'virtual' charge and discharge DC and AC rates are constrained by the total installed charge and discharge DC and AC capacities:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t} \leq \Delta^{total,dc,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t} \leq \Delta^{total,dc,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} \leq \Delta^{total,ac,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} \leq \Delta^{total,ac,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

Adding only the operating reserve constraints, the asymmetric charge and discharge DC and AC rates plus the contributions to frequency regulation and operating reserves (both DC and AC) are constrained by the total installed charge and discharge DC and AC capacities:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t} \leq \Delta^{total,dc,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{dc}_{y,z,t} + f^{dc,cha}_{y,z,t} \leq \Delta^{total,dc,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} \leq \Delta^{total,ac,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{ac}_{y,z,t} + f^{ac,cha}_{y,z,t} \leq \Delta^{total,ac,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

With both capacity reserve margin and operating reserve constraints, the asymmetric charge and discharge DC and AC rate constraints follow:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t} \leq \Delta^{total,dc,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t} + f^{dc,cha}_{y,z,t} \leq \Delta^{total,dc,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} \leq \Delta^{total,ac,dis}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} + f^{ac,cha}_{y,z,t} \leq \Delta^{total,ac,cha}_{y,z} \quad \forall y \in \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

In addition, this function adds investment and fixed O&M costs related to charge/discharge AC and DC capacities to the objective function:

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{asym,dc,dis} } \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST,dc,dis}_{y,z} \times \Omega^{dc,dis}_{y,z}) + + (\pi^{FOM,dc,dis}_{y,z} \times \Delta^{total,dc,dis}_{y,z})\right) \\ + & + \sum_{y \in \mathcal{VS}^{asym,dc,cha} } \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST,dc,cha}_{y,z} \times \Omega^{dc,cha}_{y,z}) + + (\pi^{FOM,dc,cha}_{y,z} \times \Delta^{total,dc,cha}_{y,z})\right) \\ + & + \sum_{y \in \mathcal{VS}^{asym,ac,dis} } \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST,ac,dis}_{y,z} \times \Omega^{ac,dis}_{y,z}) + + (\pi^{FOM,ac,dis}_{y,z} \times \Delta^{total,ac,dis}_{y,z})\right) \\ + & + \sum_{y \in \mathcal{VS}^{asym,ac,cha} } \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST,ac,cha}_{y,z} \times \Omega^{ac,cha}_{y,z}) + + (\pi^{FOM,ac,cha}_{y,z} \times \Delta^{total,ac,cha}_{y,z})\right) +\end{aligned}\]

source
GenX.lds_vre_stor!Method
lds_vre_stor!(EP::Model, inputs::Dict)

This function defines the decision variables, expressions, and constraints for any long duration energy storage component of each co-located VRE and storage generator ( there is more than one representative period and LDS_VRE_STOR=1 in the Vre_and_stor_data.csv).

These constraints follow the same formulation that is outlined by the function long_duration_storage!() in the storage module. One constraint changes, which links the state of charge between the start of periods for long duration energy storage resources because there are options to charge and discharge these resources through AC and DC capabilities. The main linking constraint changes to:

\[\begin{aligned} + & \Gamma_{y,z,(m-1)\times \tau^{period}+1 } =\left(1-\eta_{y,z}^{loss}\right) \times \left(\Gamma_{y,z,m\times \tau^{period}} -\Delta Q_{y,z,m}\right) \\ + & - \frac{\Theta^{dc}_{y,z,(m-1) \times \tau^{period}+1}}{\eta_{y,z}^{discharge,dc}} - \frac{\Theta^{ac}_{y,z,(m-1)\times \tau^{period}+1}}{\eta_{y,z}^{discharge,ac}} \\ + & + \eta_{y,z}^{charge,dc} \times \Pi^{dc}_{y,z,(m-1)\times \tau^{period}+1} + \eta_{y,z}^{charge,ac} \times \Pi^{ac}_{y,z,(m-1)\times \tau^{period}+1} \quad \forall y \in \mathcal{VS}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} +\end{aligned}\]

The rest of the long duration energy storage constraints are copied and applied to the co-located VRE and storage module for any long duration energy storage resources $y \in \mathcal{VS}^{LDES}$ from the long-duration storage module. Capacity reserve margin constraints for long duration energy storage resources are further elaborated upon in vre_stor_capres!().

source
GenX.solar_vre_stor!Method
solar_vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This function defines the decision variables, expressions, and constraints for the solar PV component of each co-located VRE and storage generator.

The total solar PV capacity of each resource is defined as the sum of the existing solar PV capacity plus the newly invested solar PV capacity minus any retired solar PV capacity:

\[\begin{aligned} + & \Delta^{total, pv}_{y,z} = (\overline{\Delta^{pv}_{y,z}} + \Omega^{pv}_{y,z} - \Delta^{pv}_{y,z}) \quad \forall y \in \mathcal{VS}^{pv}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more solar PV capacity than existing solar PV capacity:

\[\begin{aligned} + & \Delta^{pv}_{y,z} \leq \overline{\Delta^{pv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{pv}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega^{pv}_{y,z}}$ and $\underline{\Omega^{pv}_{y,z}}$ are defined, then we impose constraints on minimum and maximum capacity:

\[\begin{aligned} + & \Delta^{total, pv}_{y,z} \leq \overline{\Omega^{pv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{pv}, z \in \mathcal{Z} \\ + & \Delta^{total, pv}_{y,z} \geq \underline{\Omega^{pv}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{pv}, z \in \mathcal{Z} +\end{aligned}\]

If there is a fixed ratio for capacity (rather than co-optimizing interconnection sizing) of solar PV built to capacity of inverter built ($\eta_{y,z}^{ILR,pv}$), also known as the inverter loading ratio, then we impose the following constraint:

\[\begin{aligned} + & \Delta^{total, pv}_{y, z} = \eta^{ILR,pv}_{y, z} \times \Delta^{total, inv}_{y, z} \quad \forall y \in \mathcal{VS}^{pv}, \forall z \in Z +\end{aligned}\]

The last constraint defines the maximum power output in each time step from the solar PV component. Without any operating reserves, the constraint is:

\[\begin{aligned} + & \Theta^{pv}_{y, z, t} \leq \rho^{max, pv}_{y, z, t} \times \Delta^{total,pv}_{y, z} \quad \forall y \in \mathcal{VS}^{pv}, \forall z \in Z, \forall t \in T +\end{aligned}\]

With operating reserves, the maximum power output in each time step from the solar PV component must account for procuring some of the available capacity for frequency regulation ($f^{pv}_{y,z,t}$) and upward operating (spinning) reserves ($r^{pv}_{y,z,t}$):

\[\begin{aligned} + & \Theta^{pv}_{y, z, t} + f^{pv}_{y,z,t} + r^{pv}_{y,z,t} \leq \rho^{max, pv}_{y, z, t} \times \Delta^{total,pv}_{y, z} \quad \forall y \in \mathcal{VS}^{pv}, \forall z \in Z, \forall t \in T +\end{aligned}\]

In addition, this function adds investment, fixed O&M, and variable O&M costs related to the solar PV capacity to the objective function:

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{pv}} \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST, pv}_{y,z} \times \Omega^{pv}_{y,z}) + (\pi^{FOM, pv}_{y,z} \times \Delta^{total,pv}_{y,z}) \right) \\ + & + \sum_{y \in \mathcal{VS}^{pv}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM, pv}_{y,z} \times \eta^{inverter}_{y,z} \times \Theta^{pv}_{y,z,t}) +\end{aligned}\]

source
GenX.stor_vre_stor!Method
stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This function defines the decision variables, expressions, and constraints for the storage component of each co-located VRE and storage generator. A wide range of energy storage devices (all $y \in \mathcal{VS}^{stor}$) can be modeled in GenX, using one of two generic storage formulations: (1) storage technologies with symmetric charge and discharge capacity (all $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{sym,ac}$), such as lithium-ion batteries and most other electrochemical storage devices that use the same components for both charge and discharge; and (2) storage technologies that employ distinct and potentially asymmetric charge and discharge capacities (all $y \in \mathcal{VS}^{asym,dc,dis} \cup y \in \mathcal{VS}^{asym,dc,cha} \cup y \in \mathcal{VS}^{asym,ac,dis} \cup y \in \mathcal{VS}^{asym,ac,cha}$), such as most thermal storage technologies or hydrogen electrolysis/storage/fuel cell or combustion turbine systems. The following constraints apply to all storage resources, $y \in \mathcal{VS}^{stor}$, regardless of whether or not the storage has symmetric or asymmetric charging/discharging capabilities or varying durations of discharge.

The total storage energy capacity of each resource is defined as the sum of the existing storage energy capacity plus the newly invested storage energy capacity minus any retired storage energy capacity:

\[\begin{aligned} + & \Delta^{total,energy}_{y,z} = (\overline{\Delta^{energy}_{y,z}}+\Omega^{energy}_{y,z}-\Delta^{energy}_{y,z}) \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more energy capacity than existing energy capacity:

\[\begin{aligned} + &\Delta^{energy}_{y,z} \leq \overline{\Delta^{energy}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega_{y,z}^{energy}}$ and $\underline{\Omega_{y,z}^{energy}}$ are defined, then we impose constraints on minimum and maximum energy capacity:

\[\begin{aligned} + & \Delta^{total,energy}_{y,z} \leq \overline{\Omega}^{energy}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z} \\ + & \Delta^{total,energy}_{y,z} \geq \underline{\Omega}^{energy}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z} +\end{aligned}\]

The following two constraints track the state of charge of the storage resources at the end of each time period, relating the volume of energy stored at the end of the time period, $\Gamma_{y,z,t}$, to the state of charge at the end of the prior time period, $\Gamma_{y,z,t-1}$, the DC and AC charge and discharge decisions in the current time period, $\Pi^{dc}_{y,z,t}, \Pi^{ac}_{y,z,t}, \Theta^{dc}_{y,z,t}, \Theta^{ac}_{y,z,t}$, and the self discharge rate for the storage resource (if any), $\eta_{y,z}^{loss}$. When modeling the entire year as a single chronological period with total number of time steps of $\tau^{period}$, storage inventory in the first time step is linked to storage inventory at the last time step of the period representing the year. Alternatively, when modeling the entire year with multiple representative periods, this constraint relates storage inventory in the first timestep of the representative period with the inventory at the last time step of the representative period, where each representative period is made of $\tau^{period}$ time steps. In this implementation, energy exchange between representative periods is not permitted. When modeling representative time periods, GenX enables modeling of long duration energy storage which tracks state of charge between representative periods enable energy to be moved throughout the year. If there is more than one representative period and LDS_VRE_STOR=1 has been enabled for resources in Vre_and_stor_data.csv, this function calls lds_vre_stor!() to enable this feature. The first of these two constraints enforces storage inventory balance for interior time steps $(t \in \mathcal{T}^{interior})$, while the second enforces storage balance constraint for the initial time step $(t \in \mathcal{T}^{start})$:

\[\begin{aligned} + & \Gamma_{y,z,t} = \Gamma_{y,z,t-1} - \frac{\Theta^{dc}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} - \frac{\Theta^{ac}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} + \eta_{y,z}^{charge,dc} \times \Pi^{dc}_{y,z,t} + \eta_{y,z}^{charge,ac} \times \Pi^{ac}_{y,z,t} \\ + & - \eta_{y,z}^{loss} \times \Gamma_{y,z,t-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior}\\ + & \Gamma_{y,z,t} = \Gamma_{y,z,t+\tau^{period}-1} - \frac{\Theta^{dc}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} - \frac{\Theta^{ac}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} + \eta_{y,z}^{charge,dc} \times \Pi^{dc}_{y,z,t} + \eta_{y,z}^{charge,ac} \times \Pi^{ac}_{y,z,t} \\ + & - \eta_{y,z}^{loss} \times \Gamma_{y,z,t+\tau^{period}-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

The last constraint limits the volume of energy stored at any time, $\Gamma_{y,z,t}$, to be less than the installed energy storage capacity, $\Delta^{total, energy}_{y,z}$.

\[\begin{aligned} + & \Gamma_{y,z,t} \leq \Delta^{total, energy}_{y,z} & \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

The next set of constraints only apply to symmetric storage resources (all $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{sym,ac}$). For storage technologies with symmetric charge and discharge capacity (all $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{sym,ac}$), since storage resources generally represent a 'cluster' of multiple similar storage devices of the same type/cost in the same zone, GenX permits storage resources to simultaneously charge and discharge (as some units could be charging while others discharge). The simultaneous sum of DC and AC charge, $\Pi^{dc}_{y,z,t}, \Pi^{ac}_{y,z,t}$, and discharge, $\Theta^{dc}_{y,z,t}, \Theta^{ac}_{y,z,t}$, is limited by the total installed energy capacity, $\Delta^{total, energy}_{o,z}$, multiplied by the power to energy ratio, $\mu_{y,z}^{dc,stor}, \mu_{y,z}^{ac,stor}$. Without any capacity reserve margin constraints or operating reserves, the symmetric AC and DC storage resources are constrained as:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + \Pi^{dc}_{y,z,t} \leq \mu^{dc,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,dc}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + \Pi^{ac}_{y,z,t} \leq \mu^{ac,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,ac}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Symmetric storage resources with only capacity reserve margin constraints follow a similar constraint that incorporates the 'virtual' discharging and charging that occurs and limits the simultaneously charging, discharging, virtual charging, and virtual discharging of the battery resource:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t} + \Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t} \\ + & \leq \mu^{dc,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,dc}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} + \Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} \\ + & \leq \mu^{ac,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,ac}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Symmetric storage resources only subject to operating reserves have additional variables to represent contributions of frequency regulation and upwards operating reserves while the storage is charging DC or AC ($f^{dc,cha}_{y,z,t}, f^{ac,cha}_{y,z,t}$) and discharging DC or AC ($f^{dc,dis}_{y,z,t}, f^{ac,dis}_{y,z,t}, r^{dc,dis}_{y,z,t}, r^{ac,dis}_{y,z,t}$). Note that as storage resources can contribute to regulation and reserves while either charging or discharging, the proxy variables $f^{dc,cha}_{y,z,t}, f^{ac,cha}_{y,z,t}, f^{dc,dis}_{y,z,t}, f^{ac,dis}_{y,z,t}, r^{dc,dis}_{y,z,t}, r^{ac,dis}_{y,z,t}$ are created for storage components.

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t} + \Pi^{dc}_{y,z,t} + f^{dc,cha}_{y,z,t} \\ + & \leq \mu^{dc,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,dc}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} + \Pi^{ac}_{y,z,t} + f^{ac,cha}_{y,z,t} \\ + & \leq \mu^{ac,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,ac}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

For symmetric storage resources with both capacity reserve margin and operating reserves, DC and AC resources are subject to the following constraints:

\[\begin{aligned} + & \Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t} + \Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t} + f^{dc,cha}_{y,z,t} \\ + & \leq \mu^{dc,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,dc}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} + \Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} + f^{ac,cha}_{y,z,t} \\ + & \leq \mu^{ac,stor}_{y,z} \times \Delta^{total,energy}_{y,z} \quad \forall y \in \mathcal{VS}^{sym,ac}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Long duration energy storage constraints are activated by the function lds_vre_stor!(). Asymmetric storage resource constraints are activated by the function investment_charge_vre_stor!().

In addition, this function adds investment, fixed O&M, and variable O&M costs related to the storage capacity to the objective function:

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{stor}} \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) \\ + & + \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM,dc,dis}_{y,z} \times \eta^{inverter}_{y,z} \times \Theta^{dc}_{y,z,t}) \\ + & + \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM,dc,cha}_{y,z} \times \frac{\Pi^{dc}_{y,z,t}}{\eta^{inverter}_{y,z}}) \\ + & + \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM,ac,dis}_{y,z} \times \Theta^{ac}_{y,z,t}) \\ + & + \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,cha}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM,ac,cha}_{y,z} \times \Pi^{ac}_{y,z,t}) +\end{aligned}\]

source
GenX.vre_stor!Method
vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This module enables the modeling of 1) co-located VRE and energy storage technologies, and 2) optimized interconnection sizing for VREs. Utility-scale solar PV and/or wind VRE technologies can be modeled at the same site with or without storage technologies. Storage resources can be charged/discharged behind the meter through the inverter (DC) and through AC charging/discharging capabilities. Each resource can be configured to have any combination of the following components: solar PV, wind, DC discharging/charging storage, and AC discharging/charging storage resources. For storage resources, both long duration energy storage and short-duration energy storage can be modeled, via asymmetric or symmetric charging and discharging options. Each resource connects to the grid via a grid connection component, which is the only required decision variable that each resource must have. If the configured resource has either solar PV and/or DC discharging/charging storage capabilities, an inverter decision variable is also created. The full module with the decision variables and interactions can be found below.

Configurable Co-located VRE and Storage Module Interactions and Decision Variables Figure. Configurable Co-located VRE and Storage Module Interactions and Decision Variables

This module is split such that functions are called for each configurable component of a co-located resource: inverter_vre_stor(), solar_vre_stor!(), wind_vre_stor!(), stor_vre_stor!(), lds_vre_stor!(), and investment_charge_vre_stor!(). The function vre_stor!() specifically ensures that all necessary functions are called to activate the appropriate constraints, creates constraints that apply to multiple components (i.e. inverter and grid connection balances and maximums), and activates all of the policies that have been created (minimum capacity requirements, maximum capacity requirements, capacity reserve margins, operating reserves, and energy share requirements can all be turned on for this module). Note that not all of these variables are indexed by each co-located VRE and storage resource (for example, some co-located resources may only have a solar PV component and battery technology or just a wind component). Thus, the function vre_stor!() ensures indexing issues do not arise across the various potential configurations of co-located VRE and storage module but showcases all constraints as if each decision variable (that may be only applicable to certain components) is indexed by each $y \in \mathcal{VS}$ for readability.

The first constraint is created with the function vre_stor!() and exists for all resources, regardless of the VRE and storage components that each resource contains and regardless of the policies invoked for the module. This constraint represents the energy balance, ensuring net DC power (discharge of battery, PV generation, and charge of battery) and net AC power (discharge of battery, wind generation, and charge of battery) are equal to the technology's total discharging to and charging from the grid:

\[\begin{aligned} + & \Theta_{y,z,t} - \Pi_{y,z,t} = \Theta_{y,z,t}^{wind} + \Theta_{y,z,t}^{ac} - \Pi_{y,z,t}^{ac} + \eta^{inverter}_{y,z} \times (\Theta_{y,z,t}^{pv} + \Theta_{y,z,t}^{dc}) - \frac{\Pi^{dc}_{y,z,t}}{\eta^{inverter}_{y,z}} \\ + & \forall y \in \mathcal{VS}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

The second constraint is also created with the function vre_stor!() and exists for all resources, regardless of the VRE and storage components that each resource contains. However, this constraint changes when either or both capacity reserve margins and operating reserves are activated. The following constraint enforces that the maximum grid exports and imports must be less than the grid connection capacity (without any policies):

\[\begin{aligned} + & \Theta_{y,z,t} + \Pi_{y,z,t} \leq \Delta^{total}_{y,z} & \quad \forall y \in \mathcal{VS}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

The second constraint with only capacity reserve margins activated is:

\[\begin{aligned} + & \Theta_{y,z,t} + \Pi_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} + \eta^{inverter}_{y,z} \times \Theta^{CRM,dc}_{y,z,t} + \frac{\Pi^{CRM,dc}_{y,z,t}}{\eta^{inverter}_{y,z}} \\ + & \leq \Delta^{total}_{y,z} \quad \forall y \in \mathcal{VS}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

The second constraint with only operating reserves activated is:

\[\begin{aligned} + & \Theta_{y,z,t} + \Pi_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} + f^{ac,cha}_{y,z,t} + f^{wind}_{y,z,t} + r^{wind}_{y,z,t} \\ + & + \eta^{inverter}_{y,z} \times (f^{pv}_{y,z,t} + r^{pv}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t}) + \frac{f^{dc,cha}_{y,z,t}}{\eta^{inverter}_{y,z}} \leq \Delta^{total}_{y,z} \quad \forall y \in \mathcal{VS}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

The second constraint with both capacity reserve margins and operating reserves activated is:

\[\begin{aligned} + & \Theta_{y,z,t} + \Pi_{y,z,t} + \Theta^{CRM,ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t} + f^{ac,dis}_{y,z,t} + r^{ac,dis}_{y,z,t} + f^{ac,cha}_{y,z,t} + f^{wind}_{y,z,t} + r^{wind}_{y,z,t} \\ + & + \eta^{inverter}_{y,z} \times (\Theta^{CRM,dc}_{y,z,t} + f^{pv}_{y,z,t} + r^{pv}_{y,z,t} + f^{dc,dis}_{y,z,t} + r^{dc,dis}_{y,z,t}) + \frac{\Pi^{CRM,dc}_{y,z,t} + f^{dc,cha}_{y,z,t}}{\eta^{inverter}_{y,z}} \\ + & \leq \Delta^{total}_{y,z} \quad \forall y \in \mathcal{VS}, \forall z \in \mathcal{Z}, \forall t \in \mathcal{T} +\end{aligned}\]

The rest of the constraints are dependent upon specific configurable components within the module and are listed below.

source
GenX.vre_stor_capres!Method
vre_stor_capres!(EP::Model, inputs::Dict, setup::Dict)

This function activates capacity reserve margin constraints for co-located VRE and storage resources. The capacity reserve margin formulation for GenX is further elaborated upon in cap_reserve_margin!(). For co-located resources ($y \in \mathcal{VS}$), the available capacity to contribute to the capacity reserve margin is the net injection into the transmission network (which can come from the solar PV, wind, and/or storage component) plus the net virtual injection corresponding to charge held in reserve (which can only come from the storage component), derated by the derating factor. If a capacity reserve margin is modeled, variables for virtual charge DC, $\Pi^{CRM, dc}_{y,z,t}$, virtual charge AC, $\Pi^{CRM, ac}_{y,z,t}$, virtual discharge DC, $\Theta^{CRM, dc}_{y,z,t}$, virtual discharge AC, $\Theta^{CRM, ac}_{o,z,t}$, and virtual state of charge, $\Gamma^{CRM}_{y,z,t}$, are created to represent contributions that a storage device makes to the capacity reserve margin without actually generating power. These represent power that the storage device could have discharged or consumed if called upon to do so, based on its available state of charge. Importantly, a dedicated set of variables and constraints are created to ensure that any virtual contributions to the capacity reserve margin could be made as actual charge/discharge if necessary without affecting system operations in any other timesteps (similar to the standalone storage capacity reserve margin constraints).

If a capacity reserve margin is modeled, then the following constraints track the relationship between the virtual charge variables, $\Pi^{CRM,dc}_{y,z,t}, \Pi^{CRM,ac}_{y,z,t}$, virtual discharge variables, $\Theta^{CRM, dc}_{y,z,t}, \Theta^{CRM, ac}_{y,z,t}$, and the virtual state of charge, $\Gamma^{CRM}_{y,z,t}$, representing the amount of state of charge that must be held in reserve to enable these virtual capacity reserve margin contributions and ensuring that the storage device could deliver its pledged capacity if called upon to do so without affecting its operations in other timesteps. $\Gamma^{CRM}_{y,z,t}$ is tracked similarly to the devices' overall state of charge based on its value in the previous timestep and the virtual charge and discharge in the current timestep. Unlike the regular state of charge, virtual discharge $\Theta^{CRM,dc}_{y,z,t}, \Theta^{CRM,ac}_{y,z,t}$ increases $\Gamma^{CRM}_{y,z,t}$ (as more charge must be held in reserve to support more virtual discharge), and the virtual charge $\Pi^{CRM,dc}_{y,z,t}, \Pi^{CRM,ac}_{y,z,t}$ reduces $\Gamma^{CRM}_{y,z,t}$. Similar to the state of charge constraints in the stor_vre_stor!() function, the first of these two constraints enforces storage inventory balance for interior time steps $(t \in \mathcal{T}^{interior})$, while the second enforces storage balance constraint for the initial time step $(t \in \mathcal{T}^{start})$:

\[\begin{aligned} + & \Gamma^{CRM}_{y,z,t} = \Gamma^{CRM}_{y,z,t-1} + \frac{\Theta^{CRM, dc}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} + \frac{\Theta^{CRM,ac}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} - \eta_{y,z}^{charge,dc} \times \Pi^{CRM,dc}_{y,z,t} - \eta_{y,z}^{charge,ac} \times \Pi^{CRM, ac}_{y,z,t} \\ + & - \eta_{y,z}^{loss} \times \Gamma^{CRM}_{y,z,t-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T}^{interior}\\ + & \Gamma^{CRM}_{y,z,t} = \Gamma^{CRM}_{y,z,t+\tau^{period}-1} + \frac{\Theta^{CRM,dc}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} + \frac{\Theta^{CRM,ac}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} - \eta_{y,z}^{charge,dc} \times \Pi^{CRM,dc}_{y,z,t} - \eta_{y,z}^{charge,ac} \times \Pi^{CRM,ac}_{y,z,t} \\ + & - \eta_{y,z}^{loss} \times \Gamma^{CRM}_{y,z,t+\tau^{period}-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T}^{start} +\end{aligned}\]

The energy held in reserve, $\Gamma^{CRM}_{y,z,t}$, also acts as a lower bound on the overall state of charge $\Gamma_{y,z,t}$. This ensures that the storage device cannot use state of charge that would not have been available had it been called on to actually contribute its pledged virtual discharge at some earlier timestep. This relationship is described by the following constraint (as also outlined in the storage module):

\[\begin{aligned} + & \Gamma_{y,z,t} \geq \Gamma^{CRM}_{y,z,t} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} \\ +\end{aligned}\]

The overall contribution of the co-located VRE and storage resources to the system's capacity reserve margin in timestep $t$ is equal to (including both actual and virtual DC and AC charge and discharge):

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{pv}} (\epsilon_{y,z,p}^{CRM} \times \eta^{inverter}_{y,z} \times \rho^{max,pv}_{y,z,t} \times \Delta^{total,pv}_{y,z}) \\ + & + \sum_{y \in \mathcal{VS}^{wind}} (\epsilon_{y,z,p}^{CRM} \times \rho^{max,wind}_{y,z,t} \times \Delta^{total,wind}_{y,z}) \\ + & + \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}} (\epsilon_{y,z,p}^{CRM} \times \eta^{inverter}_{y,z} \times (\Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t})) \\ + & + \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}} (\epsilon_{y,z,p}^{CRM} \times (\Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t})) \\ + & - \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} (\epsilon_{y,z,p}^{CRM} \times \frac{\Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t}}{\eta^{inverter}_{y,z}}) \\ + & - \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} (\epsilon_{y,z,p}^{CRM} \times (\Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t})) +\end{aligned}\]

If long duration energy storage resources exist, a separate but similar set of variables and constraints is used to track the evolution of energy held in reserves across representative periods, which is elaborated upon in the long_duration_storage!() function. The main linking constraint follows (due to the capabilities of virtual DC and AC discharging and charging):

\[\begin{aligned} + & \Gamma^{CRM}_{y,z,(m-1)\times \tau^{period}+1} = \left(1-\eta_{y,z}^{loss}\right)\times \left(\Gamma^{CRM}_{y,z,m\times \tau^{period}} -\Delta Q_{y,z,m}\right) \\ + & + \frac{\Theta^{CRM,dc}_{y,z,(m-1)\times \tau^{period}+1}}{\eta_{y,z}^{discharge,dc}} + \frac{\Theta^{CRM,ac}_{y,z,(m-1)\times \tau^{period}+1}}{\eta_{y,z}^{discharge,ac}} \\ + & - \eta_{y,z}^{charge,dc} \times \Pi^{CRM,dc}_{y,z,(m-1)\times \tau^{period}+1} - \eta_{y,z}^{charge,ac} \times \Pi^{CRM,ac}_{y,z,(m-1)\times \tau^{period}+1} \\ + & \forall y \in \mathcal{VS}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} +\end{aligned}\]

All other constraints are identical to those used to track the actual state of charge, except with the new variables for the representation of 'virtual' state of charge, build up storage inventory and state of charge at the beginning of each period.

source
GenX.vre_stor_operational_reserves!Method
vre_stor_operational_reserves!(EP::Model, inputs::Dict, setup::Dict)

This function activates either or both frequency regulation and operating reserve options for co-located VRE-storage resources. Co-located VRE and storage resources ($y \in \mathcal{VS}$) have six pairs of auxilary variables to reflect contributions to regulation and reserves when generating electricity from solar PV or wind resources, DC charging and discharging from storage resources, and AC charging and discharging from storage resources. The primary variables ($f_{y,z,t}$ & $r_{y,z,t}$) becomes equal to the sum of these auxilary variables as follows:

\[\begin{aligned} + & f_{y,z,t} = f^{pv}_{y,z,t} + f^{wind}_{y,z,t} + f^{dc,dis}_{y,z,t} + f^{dc,cha}_{y,z,t} + f^{ac,dis}_{y,z,t} + f^{ac,cha}_{y,z,t} & \quad \forall y \in \mathcal{VS}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & r_{y,z,t} = r^{pv}_{y,z,t} + r^{wind}_{y,z,t} + r^{dc,dis}_{y,z,t} + r^{dc,cha}_{y,z,t} + r^{ac,dis}_{y,z,t} + r^{ac,cha}_{y,z,t} & \quad \forall y \in \mathcal{VS}, z \in \mathcal{Z}, t \in \mathcal{T}\\ +\end{aligned}\]

Furthermore, the frequency regulation and operating reserves require the maximum contribution from the entire resource to be a specified fraction of the installed grid connection capacity:

\[\begin{aligned} + f_{y,z,t} \leq \upsilon^{reg}_{y,z} \times \Delta^{total}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + r_{y,z,t} \leq \upsilon^{rsv}_{y,z}\times \Delta^{total}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{VS}, z \in \mathcal{Z}, t \in \mathcal{T} + \end{aligned}\]

The following constraints follow if the configurable co-located resource has any type of storage component. When charging, reducing the DC and AC charge rate is contributing to upwards reserve and frequency regulation as it drops net demand. As such, the sum of the DC and AC charge rate plus contribution to regulation and reserves up must be greater than zero. Additionally, the DC and AC discharge rate plus the contribution to regulation must be greater than zero:

\[\begin{aligned} + & \Pi^{dc}_{y,z,t} - f^{dc,cha}_{y,z,t} - r^{dc,cha}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Pi^{ac}_{y,z,t} - f^{ac,cha}_{y,z,t} - r^{ac,cha}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,cha}, z \in \mathcal{Z}, t \in \mathcal{T}\\ + & \Theta^{dc}_{y,z,t} - f^{dc,dis}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{ac}_{y,z,t} - f^{ac,dis}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Additionally, when reserves are modeled, the maximum DC and AC charge rate and contribution to regulation while charging can be no greater than the available energy storage capacity, or the difference between the total energy storage capacity, $\Delta^{total, energy}_{y,z}$, and the state of charge at the end of the previous time period, $\Gamma_{y,z,t-1}$, while accounting for charging losses $\eta_{y,z}^{charge,dc}, \eta_{y,z}^{charge,ac}$. Note that for storage to contribute to reserves down while charging, the storage device must be capable of increasing the charge rate (which increases net load):

\[\begin{aligned} + & \eta_{y,z}^{charge,dc} \times (\Pi^{dc}_{y,z,t} + f^{dc,cha}_{o,z,t}) + \eta_{y,z}^{charge,ac} \times (\Pi^{ac}_{y,z,t} + f^{ac,cha}_{o,z,t}) \\ + & \leq \Delta^{energy, total}_{y,z} - \Gamma_{y,z,t-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Finally, the maximum DC and AC discharge rate and contributions to the frequency regulation and operating reserves must be less than the state of charge in the previous time period, $\Gamma_{y,z,t-1}$. Without any capacity reserve margin policies activated, the constraint is as follows:

\[\begin{aligned} + & \frac{\Theta^{dc}_{y,z,t}+f^{dc,dis}_{y,z,t}+r^{dc,dis}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} + \frac{\Theta^{ac}_{y,z,t}+f^{ac,dis}_{y,z,t}+r^{ac,dis}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} \\ + & \leq \Gamma_{y,z,t-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

With the capacity reserve margin policies, the maximum DC and AC discharge rate accounts for both contributions to the capacity reserve margin and operating reserves as follows:

\[\begin{aligned} + & \frac{\Theta^{dc}_{y,z,t}+\Theta^{CRM,dc}_{y,z,t}+f^{dc,dis}_{y,z,t}+r^{dc,dis}_{y,z,t}}{\eta_{y,z}^{discharge,dc}} + \frac{\Theta^{ac}_{y,z,t}+\Theta^{CRM,ac}_{y,z,t}+f^{ac,dis}_{y,z,t}+r^{ac,dis}_{y,z,t}}{\eta_{y,z}^{discharge,ac}} \\ + & \leq \Gamma_{y,z,t-1} \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

Lastly, if the co-located resource has a variable renewable energy component, the solar PV and wind resource can also contribute to frequency regulation reserves and must be greater than zero:

\[\begin{aligned} + & \Theta^{pv}_{y,z,t} - f^{pv}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{pv}, z \in \mathcal{Z}, t \in \mathcal{T} \\ + & \Theta^{wind}_{y,z,t} - f^{wind}_{y,z,t} \geq 0 & \quad \forall y \in \mathcal{VS}^{wind}, z \in \mathcal{Z}, t \in \mathcal{T} +\end{aligned}\]

source
GenX.wind_vre_stor!Method
wind_vre_stor!(EP::Model, inputs::Dict, setup::Dict)

This function defines the decision variables, expressions, and constraints for the wind component of each co-located VRE and storage generator.

The total wind capacity of each resource is defined as the sum of the existing wind capacity plus the newly invested wind capacity minus any retired wind capacity:

\[\begin{aligned} + & \Delta^{total, wind}_{y,z} = (\overline{\Delta^{wind}_{y,z}} + \Omega^{wind}_{y,z} - \Delta^{wind}_{y,z}) \quad \forall y \in \mathcal{VS}^{wind}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more wind capacity than existing wind capacity:

\[\begin{aligned} + & \Delta^{wind}_{y,z} \leq \overline{\Delta^{wind}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{wind}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega^{wind}_{y,z}}$ and $\underline{\Omega^{wind}_{y,z}}$ are defined, then we impose constraints on minimum and maximum capacity:

\[\begin{aligned} + & \Delta^{total, wind}_{y,z} \leq \overline{\Omega^{wind}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{wind}, z \in \mathcal{Z} \\ + & \Delta^{total, wind}_{y,z} \geq \underline{\Omega^{wind}_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{VS}^{wind}, z \in \mathcal{Z} +\end{aligned}\]

If there is a fixed ratio for capacity (rather than co-optimizing interconnection sizing) of wind built to capacity of grid connection built ($\eta_{y,z}^{ILR,wind}$), then we impose the following constraint:

\[\begin{aligned} + & \Delta^{total, wind}_{y, z} = \eta^{ILR,wind}_{y, z} \times \Delta^{total}_{y, z} \quad \forall y \in \mathcal{VS}^{wind}, \forall z \in Z +\end{aligned}\]

The last constraint defines the maximum power output in each time step from the wind component. Without any operating reserves, the constraint is:

\[\begin{aligned} + & \Theta^{wind}_{y, z, t} \leq \rho^{max, wind}_{y, z, t} \times \Delta^{total,wind}_{y, z} \quad \forall y \in \mathcal{VS}^{wind}, \forall z \in Z, \forall t \in T +\end{aligned}\]

With operating reserves, the maximum power output in each time step from the wind component must account for procuring some of the available capacity for frequency regulation ($f^{wind}_{y,z,t}$) and upward operating (spinning) reserves ($r^{wind}_{y,z,t}$):

\[\begin{aligned} + & \Theta^{wind}_{y, z, t} + f^{wind}_{y,z,t} + r^{wind}_{y,z,t} \leq \rho^{max, wind}_{y, z, t} \times \Delta^{total,wind}_{y, z} \quad \forall y \in \mathcal{VS}^{wind}, \forall z \in Z, \forall t \in T +\end{aligned}\]

In addition, this function adds investment, fixed O&M, and variable O&M costs related to the wind capacity to the objective function:

\[\begin{aligned} + & \sum_{y \in \mathcal{VS}^{wind}} \sum_{z \in \mathcal{Z}} + \left( (\pi^{INVEST, wind}_{y,z} \times \Omega^{wind}_{y,z}) + (\pi^{FOM, wind}_{y,z} \times \Delta^{total,wind}_{y,z}) \right) \\ + & + \sum_{y \in \mathcal{VS}^{wind}} \sum_{z \in \mathcal{Z}} \sum_{t \in \mathcal{T}} (\pi^{VOM, wind}_{y,z} \times \Theta^{wind}_{y,z,t}) +\end{aligned}\]

source
GenX.write_vre_storMethod
write_vre_stor(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage specific files.

source
GenX.write_vre_stor_capacityMethod
write_vre_stor_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage capacities.

source
GenX.write_vre_stor_chargeMethod
write_vre_stor_charge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage charging decision variables/expressions.

source
GenX.write_vre_stor_dischargeMethod
write_vre_stor_discharge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage discharging decision variables/expressions.

source
diff --git a/previews/PR670/Model_Reference/TDR/index.html b/previews/PR670/Model_Reference/TDR/index.html new file mode 100644 index 0000000000..4b417d673a --- /dev/null +++ b/previews/PR670/Model_Reference/TDR/index.html @@ -0,0 +1,3 @@ + +Time-domain Reduction · GenX.jl

Time Domain Reduction (TDR)

GenX.RemoveConstColsFunction
RemoveConstCols(all_profiles, all_col_names)

Remove and store the columns that do not vary during the period.

source
GenX.check_conditionMethod
check_condition(Threshold, R, OldColNames, ScalingMethod, TimestepsPerRepPeriod)

Check whether the greatest Euclidean deviation in the input data and the clustered representation is within a given proportion of the "maximum" possible deviation.

(1 for Normalization covers 100%, 4 for Standardization covers ~95%)

source
GenX.cluster_inputsFunction
cluster_inputs(inpath, settings_path, mysetup, stage_id=-99, v=false; random=true)

Use kmeans or kmedoids to cluster raw demand profiles and resource capacity factor profiles into representative periods. Use Extreme Periods to capture noteworthy periods or periods with notably poor fits.

In Demand_data.csv, include the following:

  • Timesteps_per_Rep_Period - Typically 168 timesteps (e.g., hours) per period, this designates the length of each representative period.
  • UseExtremePeriods - Either 1 or 0, this designates whether or not to include outliers (by performance or demand/resource extreme) as their own representative periods. This setting automatically includes the periods with maximum demand, minimum solar cf and minimum wind cf as extreme periods.
  • ClusterMethod - Either 'kmeans' or 'kmedoids', this designates the method used to cluster periods and determine each point's representative period.
  • ScalingMethod - Either 'N' or 'S', this designates directs the module to normalize ([0,1]) or standardize (mean 0, variance 1) the input data.
  • MinPeriods - The minimum number of periods used to represent the input data. If using UseExtremePeriods, this must be at least three. If IterativelyAddPeriods if off, this will be the total number of periods.
  • MaxPeriods - The maximum number of periods - both clustered periods and extreme periods - that may be used to represent the input data.
  • IterativelyAddPeriods - Either 1 or 0, this designates whether or not to add periods until the error threshold between input data and represented data is met or the maximum number of periods is reached.
  • Threshold - Iterative period addition will end if the period farthest (Euclidean Distance) from its representative period is within this percentage of the total possible error (for normalization) or ~95% of the total possible error (for standardization). E.g., for a threshold of 0.01, every period must be within 1% of the spread of possible error before the clustering iterations will terminate (or until the max number of periods is reached).
  • IterateMethod - Either 'cluster' or 'extreme', this designates whether to add clusters to the kmeans/kmedoids method or to set aside the worst-fitting periods as a new extreme periods.
  • nReps - The number of times to repeat each kmeans/kmedoids clustering at the same setting.
  • DemandWeight - Default 1, this is an optional multiplier on demand columns in order to prioritize better fits for demand profiles over resource capacity factor profiles.
  • WeightTotal - Default 8760, the sum to which the relative weights of representative periods will be scaled.
  • ClusterFuelPrices - Either 1 or 0, this indicates whether or not to use the fuel price time series in Fuels_data.csv in the clustering process. If 'no', this function will still write Fuels_data_clustered.csv with reshaped fuel prices based on the number and size of the representative weeks, assuming a constant time series of fuel prices with length equal to the number of timesteps in the raw input data.
  • MultiStageConcatenate - (Only considered if MultiStage = 1 in genx_settings.yml) If 1, this designates that the model should time domain reduce the input data of all model stages together. Else if 0, [still in development] the model will time domain reduce only the first stage and will apply the periods of each other model stage to this set of representative periods by closest Eucliden distance.

For co-located VRE-STOR resources, all capacity factors must be in the Generatorsvariability.csv file in addition to separate Vreandstorsolarvariability.csv and Vreandstorwind_variability.csv files. The co-located solar PV and wind profiles for co-located resources will be separated into different CSV files to be read by loading the inputs after the clustering of the inputs has occurred.

source
GenX.get_absolute_extremeMethod
get_absolute_extreme(DF, statKey, col_names, ConstCols)

Get the period index of the single timestep with the minimum or maximum demand or capacity factor.

source
GenX.get_demand_multipliersFunction
get_demand_multipliers(ClusterOutputData, ModifiedData, M, W, DemandCols, TimestepsPerRepPeriod, NewColNames, NClusters, Ncols)

Get multipliers to linearly scale clustered demand profiles L zone-wise such that their weighted sum equals the original zonal total demand. Scale demand profiles later using these multipliers in order to ensure that a copy of the original demand is kept for validation.

Find $k_z$ such that:

\[\sum_{i \in I} L_{i,z} = \sum_{t \in T, m \in M} C_{t,m,z} \cdot \frac{w_m}{T} \cdot k_z \: \: \: \forall z \in Z\]

where $Z$ is the set of zones, $I$ is the full time domain, $T$ is the length of one period (e.g., 168 for one week in hours), $M$ is the set of representative periods, $L_{i,z}$ is the original zonal demand profile over time (hour) index $i$, $C_{i,m,z}$ is the demand in timestep $i$ for representative period $m$ in zone $z$, $w_m$ is the weight of the representative period equal to the total number of hours that one hour in representative period $m$ represents in the original profile, and $k_z$ is the zonal demand multiplier returned by the function.

source
GenX.get_extreme_periodFunction
get_extreme_period(DF, GDF, profKey, typeKey, statKey,
+   ConstCols, demand_col_names, solar_col_names, wind_col_names)

Identify extreme week by specification of profile type (Demand, PV, Wind), measurement type (absolute (timestep with min/max value) vs. integral (period with min/max summed value)), and statistic (minimum or maximum). I.e., the user could want the hour with the most demand across the whole system to be included among the extreme periods. They would select "Demand", "System, "Absolute, and "Max".

source
GenX.get_integral_extremeMethod
get_integral_extreme(GDF, statKey, col_names, ConstCols)

Get the period index with the minimum or maximum demand or capacity factor summed over the period.

source
GenX.get_worst_period_idxMethod
get_worst_period_idx(R)

Get the index of the period that is farthest from its representative period by Euclidean distance.

source
GenX.parse_dataMethod
parse_data(myinputs)

Get demand, solar, wind, and other curves from the input data.

source
GenX.rmse_scoreMethod
rmse_score(y_true, y_pred)

Calculates Root Mean Square Error.

\[RMSE = \sqrt{\frac{1}{n}\Sigma_{i=1}^{n}{\Big(\frac{d_i -f_i}{\sigma_i}\Big)^2}}\]

source
GenX.scale_weightsFunction
scale_weights(W, H)

Linearly scale weights W such that they sum to the desired number of timesteps (hours) H.

\[w_j \leftarrow H \cdot \frac{w_j}{\sum_i w_i} \: \: \: \forall w_j \in W\]

source
GenX.run_timedomainreduction!Function

Run the GenX time domain reduction on the given case folder

case - folder for the case stage_id - possibly something to do with MultiStage verbose - print extra outputs

This function overwrites the time-domain-reduced inputs if they already exist.

source
diff --git a/previews/PR670/Model_Reference/core/index.html b/previews/PR670/Model_Reference/core/index.html new file mode 100644 index 0000000000..b6d9070613 --- /dev/null +++ b/previews/PR670/Model_Reference/core/index.html @@ -0,0 +1,130 @@ + +Core · GenX.jl

Core

Discharge

GenX.discharge!Method
discharge!(EP::Model, inputs::Dict, setup::Dict)

This module defines the power decision variable $\Theta_{y,t} \forall y \in \mathcal{G}, t \in \mathcal{T}$, representing energy injected into the grid by resource $y$ by at time period $t$. This module additionally defines contributions to the objective function from variable costs of generation (variable O&M) from all resources $y \in \mathcal{G}$ over all time periods $t \in \mathcal{T}$:

\[\begin{aligned} + Obj_{Var\_gen} = + \sum_{y \in \mathcal{G} } \sum_{t \in \mathcal{T}}\omega_{t}\times(\pi^{VOM}_{y})\times \Theta_{y,t} +\end{aligned}\]

source
GenX.investment_discharge!Method
investment_discharge!(EP::Model, inputs::Dict, setup::Dict)

This function defines the expressions and constraints keeping track of total available power generation/discharge capacity across all resources as well as constraints on capacity retirements. The total capacity of each resource is defined as the sum of the existing capacity plus the newly invested capacity minus any retired capacity. Note for storage and co-located resources, additional energy and charge power capacity decisions and constraints are defined in the storage and co-located VRE and storage module respectively.

\[\begin{aligned} +& \Delta^{total}_{y,z} =(\overline{\Delta_{y,z}}+\Omega_{y,z}-\Delta_{y,z}) \forall y \in \mathcal{G}, z \in \mathcal{Z} +\end{aligned}\]

One cannot retire more capacity than existing capacity.

\[\begin{aligned} +&\Delta_{y,z} \leq \overline{\Delta_{y,z}} + \hspace{4 cm} \forall y \in \mathcal{G}, z \in \mathcal{Z} +\end{aligned}\]

For resources where $\overline{\Omega_{y,z}}$ and $\underline{\Omega_{y,z}}$ is defined, then we impose constraints on minimum and maximum power capacity.

\[\begin{aligned} +& \Delta^{total}_{y,z} \leq \overline{\Omega}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{G}, z \in \mathcal{Z} \\ +& \Delta^{total}_{y,z} \geq \underline{\Omega}_{y,z} + \hspace{4 cm} \forall y \in \mathcal{G}, z \in \mathcal{Z} +\end{aligned}\]

In addition, this function adds investment and fixed O\&M related costs related to discharge/generation capacity to the objective function:

\[\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) +\end{aligned}\]

source
GenX.write_multi_stage_capacities_dischargeMethod
write_multi_stage_capacities_discharge(outpath::String, settings_d::Dict)

This function writes the file capacities_multi_stage.csv to the Results directory. This file contains starting resource capacities from the first model stage and end resource capacities for the first and all subsequent model stages.

inputs:

  • outpath – String which represents the path to the Results directory.
  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
source
GenX.write_virtual_dischargeMethod
write_virtual_discharge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the "virtual" discharge of each storage technology. Virtual discharge is used to allow storage resources to contribute to the capacity reserve margin without actually discharging.

source

Non-served Energy

GenX.non_served_energy!Method
non_served_energy!(EP::Model, inputs::Dict, setup::Dict)

This function defines the non-served energy/curtailed demand decision variable $\Lambda_{s,t,z} \forall s \in \mathcal{S}, \forall t \in \mathcal{T}, z \in \mathcal{Z}$, representing the total amount of demand curtailed in demand segment $s$ at time period $t$ in zone $z$. The first segment of non-served energy, $s=1$, is used to denote the cost of involuntary demand curtailment (e.g. emergency load shedding or rolling blackouts), specified as the value of $n_{1}^{slope}$. Additional segments, $s \geq 2$ can be used to specify a segment-wise approximation of a price elastic demand curve, or segments of price-responsive curtailable demands (aka demand response). Each segment denotes a price/cost at which the segment of demand is willing to curtail consumption, $n_{s}^{slope}$, representing the marginal willingness to pay for electricity of this segment of demand (or opportunity cost incurred when demand is not served) and a maximum quantity of demand in this segment, $n_{s}^{size}$, specified as a share of demand in each zone in each time step, $D_{t,z}.$ Note that the current implementation assumes demand segments are an equal share of hourly demand in all zones. This function defines contributions to the objective function from the cost of non-served energy/curtailed demand from all demand curtailment segments $s \in \mathcal{S}$ over all time periods $t \in \mathcal{T}$ and all zones $z \in \mathcal{Z}$:

\[\begin{aligned} + Obj_{NSE} = + \sum_{s \in \mathcal{S} } \sum_{t \in \mathcal{T}} \sum_{z \in \mathcal{Z}}\omega_{t} \times n_{s}^{slope} \times \Lambda_{s,t,z} +\end{aligned}\]

Contributions to the power balance expression from non-served energy/curtailed demand from each demand segment $s \in \mathcal{S}$ are also defined as:

\[\begin{aligned} + PowerBal_{NSE} = + \sum_{s \in \mathcal{S} } \Lambda_{s,t,z} + \hspace{4 cm} \forall s \in \mathcal{S}, t \in \mathcal{T} +\end{aligned}\]

Bounds on curtailable demand Demand curtailed in each segment of curtailable demands $s \in \mathcal{S}$ cannot exceed a maximum allowable share of demand:

\[\begin{aligned} + \Lambda_{s,t,z} \leq (n_{s}^{size} \times D_{t,z}) + \hspace{4 cm} \forall s \in \mathcal{S}, t \in \mathcal{T}, z\in \mathcal{Z} +\end{aligned}\]

Additionally, total demand curtailed in each time step cannot exceed total demand:

\[\begin{aligned} + \sum_{s \in \mathcal{S} } \Lambda_{s,t,z} \leq D_{t,z} + \hspace{4 cm} \forall t \in \mathcal{T}, z\in \mathcal{Z} +\end{aligned}\]

source

Operational Reserves

GenX.load_operational_reserves!Method
load_operational_reserves!(setup::Dict,path::AbstractString, inputs::Dict)

Read input parameters related to frequency regulation and operating reserve requirements

source
GenX.operational_reserves!Method
operational_reserves!(EP::Model, inputs::Dict, setup::Dict)

This function sets up reserve decisions and constraints, using the operationalreservescore()and operational_reserves_contingency() functions.

source
GenX.operational_reserves_contingency!Method
operational_reserves_contingency!(EP::Model, inputs::Dict, setup::Dict)

This function establishes several different versions of contingency reserve requirement expression, $CONTINGENCY$ used in the operationalreservescore() function below.

Contingency operational reserves represent requirements for upward ramping capability within a specified time frame to compensated for forced outages or unplanned failures of generators or transmission lines (e.g. N-1 contingencies).

There are three options for the $Contingency$ expression, depending on user settings: 1. a static contingency, in which the contingency requirement is set based on a fixed value (in MW) specified in the '''Operational_reserves.csv''' input file; 2. a dynamic contingency based on installed capacity decisions, in which the largest 'installed' generator is used to determine the contingency requirement for all time periods; and 3. dynamic unit commitment based contingency, in which the largest 'committed' generator in any time period is used to determine the contingency requirement in that time period.

Note that the two dynamic contigencies are only available if unit commitment is being modeled.

Static contingency Option 1 (static contingency) is expressed by the following constraint:

\[\begin{aligned} + Contingency = \epsilon^{contingency} +\end{aligned}\]

where $\epsilon^{contingency}$ is static contingency requirement in MWs.

Dynamic capacity-based contingency Option 2 (dynamic capacity-based contingency) is expressed by the following constraints:

\[\begin{aligned} + &Contingency \geq \Omega^{size}_{y,z} \times \alpha^{Contingency,Aux}_{y,z} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ + &\alpha^{Contingency,Aux}_{y,z} \leq \Delta^{\text{total}}_{y,z} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ + &\alpha^{Contingency,Aux}_{y,z} \geq M_y \times \Delta^{\text{total}}_{y,z} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ +\end{aligned}\]

where $M_y$ is a `big M' constant equal to the largest possible capacity that can be installed for generation cluster $y$, and $\alpha^{Contingency,Aux}_{y,z} \in [0,1]$ is a binary auxiliary variable that is forced by the second and third equations above to be 1 if the total installed capacity $\Delta^{\text{total}}_{y,z} > 0$ for any generator $y \in \mathcal{UC}$ and zone $z$, and can be 0 otherwise. Note that if the user specifies contingency option 2, and is also using the linear relaxation of unit commitment constraints, the capacity size parameter for units in the set $\mathcal{UC}$ must still be set to a discrete unit size for this contingency to work as intended.

Dynamic commitment-based contingency Option 3 (dynamic commitment-based contingency) is expressed by the following set of constraints:

\[\begin{aligned} + & Contingency \geq \Omega^{size}_{y,z} \times Contingency\_Aux_{y,z,t} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ + & Contingency\_Aux_{y,z,t} \leq \nu_{y,z,t} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ + & Contingency\_Aux_{y,z,t} \geq M_y \times \nu_{y,z,t} & \forall y \in \mathcal{UC}, z \in \mathcal{Z}\\ +\end{aligned}\]

where $M_y$ is a `big M' constant equal to the largest possible capacity that can be installed for generation cluster $y$, and $Contingency\_Aux_{y,z,t} \in [0,1]$ is a binary auxiliary variable that is forced by the second and third equations above to be 1 if the commitment state for that generation cluster $\nu_{y,z,t} > 0$ for any generator $y \in \mathcal{UC}$ and zone $z$ and time period $t$, and can be 0 otherwise. Note that this dynamic commitment-based contingency can only be specified if discrete unit commitment decisions are used (e.g. it will not work if relaxed unit commitment is used).

source
GenX.operational_reserves_core!Method
operational_reserves_core!(EP::Model, inputs::Dict, setup::Dict)

This function creates decision variables related to frequency regulation and reserves provision and constraints setting overall system requirements for regulation and operating reserves.

Regulation and reserves decisions $f_{y,t,z} \geq 0$ is the contribution of generation or storage resource $y \in Y$ in time $t \in T$ and zone $z \in Z$ to frequency regulation

\[r_{y,t,z} \geq 0\]

is the contribution of generation or storage resource $y \in Y$ in time $t \in T$ and zone $z \in Z$ to operating reserves up

We assume frequency regulation is symmetric (provided in equal quantity towards both upwards and downwards regulation). To reduce computational complexity, operating reserves are only modeled in the upwards direction, as downwards reserves requirements are rarely binding in practice.

Storage resources ($y \in \mathcal{O}$) have two pairs of auxilary variables to reflect contributions to regulation and reserves when charging and discharging, where the primary variables ($f_{y,z,t}$ \& $r_{y,z,t}$) becomes equal to sum of these auxilary variables.

Co-located VRE-STOR resources are described further in the reserves function for colocated VRE and storage resources (vre_stor_operational_reserves!()).

Unmet operating reserves

\[unmet\_rsv_{t} \geq 0\]

denotes any shortfall in provision of operating reserves during each time period $t \in T$

There is a penalty $C^{rsv}$ added to the objective function to penalize reserve shortfalls, equal to:

\[\begin{aligned} + C^{rvs} = \sum_{t \in T} \omega_t \times unmet\_rsv_{t} +\end{aligned}\]

Frequency regulation requirements

Total requirements for frequency regulation (aka primary reserves) in each time step $t$ are specified as fractions of hourly demand (to reflect demand forecast errors) and variable renewable avaialblity in the time step (to reflect wind and solar forecast errors).

\[\begin{aligned} +& \sum_{y \in Y, z \in Z} f_{y,t,z} \geq \epsilon^{demand}_{reg} \times \sum_{z \in Z} \mathcal{D}_{z,t} + \epsilon^{vre}_{reg} \times (\sum_{z \in Z} \rho^{max}_{y,z,t} \times \Delta^{\text{total}}_{y,z} \\ +& + \sum_{z \in Z} \rho^{max,pv}_{y,z,t} \times \Delta^{\text{total,pv}}_{y,z} + \sum_{z \in Z} \rho^{max,wind}_{y,z,t} \times \Delta^{\text{total,wind}}_{y,z}) \quad \forall t \in T +\end{aligned}\]

where $\mathcal{D}_{z,t}$ is the forecasted electricity demand in zone $z$ at time $t$ (before any demand flexibility); $\rho^{max}_{y,z,t}$ is the forecasted capacity factor for standalone variable renewable resources $y \in VRE$ and zone $z$ in time step $t$; $\rho^{max,pv}_{y,z,t}$ is the forecasted capacity factor for co-located solar PV resources $y \in \mathcal{VS}^{pv}$ and zone $z$ in time step $t$; $\rho^{max,wind}_{y,z,t}$ is the forecasted capacity factor for co-located wind resources $y \in \mathcal{VS}^{pv}$ and zone $z$ in time step $t$; $\Delta^{\text{total,pv}}_{y,z}$ is the total installed capacity of co-located solar PV resources $y \in \mathcal{VS}^{pv}$ and zone $z$; $\Delta^{\text{total,wind}}_{y,z}$ is the total installed capacity of co-located wind resources $y \in \mathcal{VS}^{wind}$ and zone $z$; and $\epsilon^{demand}_{reg}$ and $\epsilon^{vre}_{reg}$ are parameters specifying the required frequency regulation as a fraction of forecasted demand and variable renewable generation.

Operating reserve requirements

Total requirements for operating reserves in the upward direction (aka spinning reserves or contingency reserces or secondary reserves) in each time step $t$ are specified as fractions of time step's demand (to reflect demand forecast errors) and variable renewable avaialblity in the time step (to reflect wind and solar forecast errors) plus the largest planning contingency (e.g. potential forced generation outage).

\[\begin{aligned} + & \sum_{y \in Y, z \in Z} r_{y,z,t} + r^{unmet}_{t} \geq \epsilon^{demand}_{rsv} \times \sum_{z \in Z} \mathcal{D}_{z,t} + \epsilon^{vre}_{rsv} \times (\sum_{z \in Z} \rho^{max}_{y,z,t} \times \Delta^{\text{total}}_{y,z} \\ + & + \sum_{z \in Z} \rho^{max,pv}_{y,z,t} \times \Delta^{\text{total,pv}}_{y,z} + \sum_{z \in Z} \rho^{max,wind}_{y,z,t} \times \Delta^{\text{total,wind}}_{y,z}) + Contingency \quad \forall t \in T +\end{aligned}\]

where $\mathcal{D}_{z,t}$ is the forecasted electricity demand in zone $z$ at time $t$ (before any demand flexibility); $\rho^{max}_{y,z,t}$ is the forecasted capacity factor for standalone variable renewable resources $y \in VRE$ and zone $z$ in time step $t$; $\rho^{max,pv}_{y,z,t}$ is the forecasted capacity factor for co-located solar PV resources $y \in \mathcal{VS}^{pv}$ and zone $z$ in time step $t$; $\rho^{max,wind}_{y,z,t}$ is the forecasted capacity factor for co-located wind resources $y \in \mathcal{VS}^{wind}$ and zone $z$ in time step $t$; $\Delta^{\text{total}}_{y,z}$ is the total installed capacity of standalone variable renewable resources $y \in VRE$ and zone $z$; $\Delta^{\text{total,pv}}_{y,z}$ is the total installed capacity of co-located solar PV resources $y \in \mathcal{VS}^{pv}$ and zone $z$; $\Delta^{\text{total,wind}}_{y,z}$ is the total installed capacity of co-located wind resources $y \in \mathcal{VS}^{wind}$ and zone $z$; and $\epsilon^{demand}_{rsv}$ and $\epsilon^{vre}_{rsv}$ are parameters specifying the required contingency reserves as a fraction of forecasted demand and variable renewable generation. $Contingency$ is an expression defined in the operational_reserves_contingency!() function meant to represent the largest N-1 contingency (unplanned generator outage) that the system operator must carry operating reserves to cover and depends on how the user wishes to specify contingency requirements.

source

Transmission

GenX.dcopf_transmission!Method
function dcopf_transmission!(EP::Model, inputs::Dict, setup::Dict)

The addtional constraints imposed upon the line flows in the case of DC-OPF are as follows: For the definition of the line flows, in terms of the voltage phase angles:

\[\begin{aligned} + & \Phi_{l,t}=\mathcal{B}_{l} \times (\sum_{z\in \mathcal{Z}}{(\varphi^{map}_{l,z} \times \theta_{z,t})}) \quad \forall l \in \mathcal{L}, \; \forall t \in \mathcal{T}\\ +\end{aligned}\]

For imposing the constraint of maximum allowed voltage phase angle difference across lines:

\[\begin{aligned} + & \sum_{z\in \mathcal{Z}}{(\varphi^{map}_{l,z} \times \theta_{z,t})} \leq \Delta \theta^{\max}_{l} \quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + & \sum_{z\in \mathcal{Z}}{(\varphi^{map}_{l,z} \times \theta_{z,t})} \geq -\Delta \theta^{\max}_{l} \quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ +\end{aligned}\]

Finally, we enforce the reference voltage phase angle constraint:

\[\begin{aligned} +\theta_{1,t} = 0 \quad \forall t \in \mathcal{T} +\end{aligned}\]

source
GenX.investment_transmission!Method
function investment_transmission!(EP::Model, inputs::Dict, setup::Dict)
+    The function model transmission expansion and adds transmission reinforcement or construction costs to the objective function. 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^{cap}_{l}$.
+    ```math
+    \begin{aligned}
+        & \sum_{l \in \mathcal{L}}\left(\pi^{TCAP}_{l} \times \bigtriangleup\varphi^{cap}_{l}\right)
+    \end{aligned}
+    ```
+    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.
+    **Accounting for Transmission Between Zones**
+    Available transmission capacity between zones is set equal to the existing line's maximum power transfer capacity, $\overline{\varphi^{cap}_{l}}$, plus any transmission capacity added on that line (for lines eligible for expansion in the set $\mathcal{E}$). 
+    \begin{aligned}
+    &\varphi^{cap}_{l} = \overline{\varphi^{cap}_{l}} , &\quad \forall l \in (\mathcal{L} \setminus \mathcal{E} ),\forall t  \in \mathcal{T}\\
+    % trasmission expansion
+    &\varphi^{cap}_{l} = \overline{\varphi^{cap}_{l}} + \bigtriangleup\varphi^{cap}_{l} , &\quad \forall l \in \mathcal{E},\forall t  \in \mathcal{T}        
+    \end{aligned}
+    The additional transmission capacity, $\bigtriangleup\varphi^{cap}_{l} $, is constrained by a maximum allowed reinforcement, $\overline{\bigtriangleup\varphi^{cap}_{l}}$, for each line $l \in \mathcal{E}$.
+    \begin{aligned}
+    & \bigtriangleup\varphi^{cap}_{l}  \leq \overline{\bigtriangleup\varphi^{cap}_{l}}, &\quad \forall l \in \mathcal{E}
+    \end{aligned}
source
GenX.transmission!Method
transmission!(EP::Model, inputs::Dict, setup::Dict)

This function establishes decisions, expressions, and constraints related to transmission power flows between model zones and associated transmission losses (if modeled).

Power flow and transmission loss terms are also added to the power balance constraint for each zone:

\[\begin{aligned} +& - \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))} +\end{aligned}\]

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 is used for this term. 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 below). Accounting for Transmission Between Zones Power flow, $\Phi_{l,t}$, on each line (or more likely a `path' aggregating flows across multiple parallel lines) is constrained to be less than or equal to the line's power transfer capacity, $\varphi^{cap}_{l}$, plus any transmission capacity added on that line (for lines eligible for expansion in the set $\mathcal{E}$). The additional transmission capacity, $\bigtriangleup\varphi^{cap}_{l} $, is constrained by a maximum allowed reinforcement, $\overline{\bigtriangleup\varphi^{cap}_{l}}$, for each line $l \in \mathcal{E}$.

\[\begin{aligned} + % trasmission constraints + &-\varphi^{cap}_{l} \leq \Phi_{l,t} \leq \varphi^{cap}_{l} , &\quad \forall l \in \mathcal{L},\forall t \in \mathcal{T}\\ +\end{aligned}\]

Accounting for Transmission Losses Transmission losses due to power flows can be accounted for in three different ways. The first option is to neglect losses entirely, setting the value of the losses function to zero for all lines at all hours. The second option is to assume that losses are a fixed percentage, $\varphi^{loss}_{l}$, of the magnitude of power flow on each line, $\mid \Phi_{l,t} \mid$ (e.g., losses are a linear function of power flows). Finally, the third option is to calculate losses, $\ell_{l,t}$, by approximating a quadratic-loss function of power flow across the line using a piecewise-linear function with total number of segments equal to the size of the set $\mathcal{M}$.

\[\begin{aligned} +%configurable losses formulation + & \beta_{l,t}(\cdot) = \begin{cases} 0 & \text{if~} \text{losses.~0} \\ \\ \varphi^{loss}_{l}\times \mid \Phi_{l,t} \mid & \text{if~} \text{losses.~1} \\ \\ \ell_{l,t} &\text{if~} \text{losses.~2} \end{cases}, &\quad \forall l \in \mathcal{L},\forall t \in \mathcal{T} +\end{aligned}\]

For the second option, an absolute value approximation is utilized to calculate the magnitude of the power flow on each line (reflecting the fact that negative power flows for a line linking nodes $i$ and $j$ represents flows from node $j$ to $i$ and causes the same magnitude of losses as an equal power flow from $i$ to $j$). This absolute value function is linearized such that the flow in the line must be equal to the subtraction of the auxiliary variable for flow in the positive direction, $\Phi^{+}_{l,t}$, and the auxiliary variable for flow in the negative direction, $\Phi^{+}_{l,t}$, of the line. Then, the magnitude of the flow is calculated as the sum of the two auxiliary variables. The sum of positive and negative directional flows are also constrained by the line flow capacity.

\[\begin{aligned} +% trasmission losses simple + &\Phi_{l,t} = \Phi^{+}_{l,t} - \Phi^{-}_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mid \Phi_{l,t} \mid = \Phi^{+}_{l,t} + \Phi^{-}_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\Phi^{+}_{l,t} + \Phi^{-}_{l,t} \leq \varphi^{cap}_{l}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} +\end{aligned}\]

If discrete unit commitment decisions are modeled, ``phantom losses'' can be observed wherein the auxiliary variables for flows in both directions ($\Phi^{+}_{l,t}$ and $\Phi^{-}_{l,t}$) are both increased to produce increased losses so as to avoid cycling a thermal generator and incurring start-up costs or opportunity costs related to minimum down times. This unrealistic behavior can be eliminated via inclusion of additional constraints and a set of auxiliary binary variables, $ON^{+}_{l,t} \in {0,1} \forall l \in \mathcal{L}$. Then the following additional constraints are created:

\[\begin{aligned} + \Phi^{+}_{l,t} \leq TransON^{+}_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + \Phi^{-}_{l,t} \leq \varphi^{cap}_{l} -TransON^{+}_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} +\end{aligned}\]

where $TransON^{+}_{l,t}$ is a continuous variable, representing the product of the binary variable $ON^{+}_{l,t}$ and the expression, $\varphi^{cap}_{l}$. This product cannot be defined explicitly, since it will lead to a bilinear expression involving two variables. Instead, we enforce this definition via the Glover's Linearization as shown below (also referred McCormick Envelopes constraints for bilinear expressions, which is exact when one of the variables is binary).

\[\begin{aligned} + TransON^{+}_{l,t} \leq (\overline{varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) \times TransON^{+}_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} \\ + TransON^{+}_{l,t} \leq \varphi^{cap}_{l}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} \\ + TransON^{+}_{l,t} \geq \varphi^{cap}_{l} - (\overline{\varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) \times(1- TransON^{+}_{l,t}), &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} \\ +\end{aligned}\]

These constraints permit only the positive or negative auxiliary flow variables to be non-zero at a given time period, not both. For the third option, losses are calculated as a piecewise-linear approximation of a quadratic function of power flows. In order to do this, we represent the absolute value of the line flow variable by the sum of positive stepwise flow variables $(\mathcal{S}^{+}_{m,l,t}, \mathcal{S}^{-}_{m,l,t})$, associated with each partition of line losses computed using the corresponding linear expressions. This can be understood as a segmentwise linear fitting (or first order approximation) of the quadratic losses function. The first constraint below computes the losses a the accumulated sum of losses for each linear stepwise segment of the approximated quadratic function, including both positive domain and negative domain segments. A second constraint ensures that the stepwise variables do not exceed the maximum size per segment. The slope and maximum size for each segment are calculated as per the method in \cite{Zhang2013}.

\[\begin{aligned} + & \ell_{l,t} = \frac{\varphi^{ohm}_{l}}{(\varphi^{volt}_{l})^2}\bigg( \sum_{m \in \mathcal{M}}( S^{+}_{m,l}\times \mathcal{S}^{+}_{m,l,t} + S^{-}_{m,l}\times \mathcal{S}^{-}_{m,l,t}) \bigg), &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} \\ + & \text{\quad Where:} \\ + & \quad S^{+}_{m,l} = \frac{2+4 \times \sqrt{2}\times (m-1)}{1+\sqrt{2} \times (2 \times M-1)} (\overline{\varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) &\quad \forall m \in [1 \colon M], l \in \mathcal{L} \\ + & \quad S^{-}_{m,l} = \frac{2+4 \times \sqrt{2}\times (m-1)}{1+\sqrt{2} \times (2 \times M-1)} (\overline{\varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) &\quad \forall m \in [1 \colon M], l \in \mathcal{L}\\ + & \\ + & \mathcal{S}^{+}_{m,l,t}, \mathcal{S}^{-}_{m,l,t} <= \overline{\mathcal{S}_{m,l}} &\quad \forall m \in [1:M], l \in \mathcal{L}, t \in \mathcal{T} \\ + & \text{\quad Where:} \\ + & \quad \overline{S_{l,z}} = \begin{cases} \frac{(1+\sqrt{2})}{1+\sqrt{2} \times (2 \times M-1)} (\overline{\varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) & \text{if~} m = 1 \\ + \frac{2 \times \sqrt{2} }{1+\sqrt{2} \times (2 \times M-1)} (\overline{\varphi^{cap}_{l}} + \overline{\bigtriangleup\varphi^{cap}_{l}}) & \text{if~} m > 1 \end{cases} +\end{aligned}\]

Next, a constraint ensures that the sum of auxiliary segment variables ($m \geq 1$) minus the "zero" segment (which allows values to go into the negative domain) from both positive and negative domains must total the actual power flow across the line, and a constraint ensures that the sum of negative and positive flows do not exceed the flow capacity of the line.

\[\begin{aligned} + &\sum_{m \in [1:M]} (\mathcal{S}^{+}_{m,l,t}) - \mathcal{S}^{+}_{0,l,t} = \Phi_{l,t}, &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\sum_{m \in [1:M]} (\mathcal{S}^{-}_{m,l,t}) - \mathcal{S}^{-}_{0,l,t} = - \Phi_{l,t} +\end{aligned}\]

As with losses option 2, this segment-wise approximation of a quadratic loss function also permits 'phantom losses' to avoid cycling thermal units when discrete unit commitment decisions are modeled. In this case, the additional constraints below are also added to ensure that auxiliary segments variables do not exceed maximum value per segment and that they are filled in order; i.e., one segment cannot be non-zero unless prior segment is at its maximum value. Binary constraints deal with absolute value of power flow on each line. If the flow is positive, $\mathcal{S}^{+}_{0,l,t}$ must be zero; if flow is negative, $\mathcal{S}^{+}_{0,l,t}$ must be positive and takes on value of the full negative flow, forcing all $\mathcal{S}^{+}_{m,l,t}$ other segments ($m \geq 1$) to be zero. Conversely, if the flow is negative, $\mathcal{S}^{-}_{0,l,t}$ must be zero; if flow is positive, $\mathcal{S}^{-}_{0,l,t}$ must be positive and takes on value of the full positive flow, forcing all $\mathcal{S}^{-}_{m,l,t}$ other segments ($m \geq 1$) to be zero. Requiring segments to fill in sequential order and binary variables to ensure variables reflect the actual direction of power flows are both necessary to eliminate ``phantom losses'' from the solution. These constraints and binary decisions are ommited if the model is fully linear.

\[\begin{aligned} + &\mathcal{S}^{+}_{m,l,t} <= \overline{\mathcal{S}_{m,l}} \times ON^{+}_{m,l,t}, &\quad \forall m \in [1:M], \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mathcal{S}^{-}_{m,l,t} <= \overline{\mathcal{S}_{m,l}} \times ON^{-}_{m,l,t}, &\quad \forall m \in[1:M], \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mathcal{S}^{+}_{m,l,t} \geq ON^{+}_{m+1,l,t} \times \overline{\mathcal{S}_{m,l}}, &\quad \forall m \in [1:M], \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mathcal{S}^{-}_{m,l,t} \geq ON^{-}_{m+1,l,t} \times \overline{\mathcal{S}_{m,l}} , &\quad \forall m \in [1:M], \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mathcal{S}^{+}_{0,l,t} \leq \varphi^{max}_{l} \times (1- ON^{+}_{1,l,t}), &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ + &\mathcal{S}^{-}_{0,l,t} \leq \varphi^{max}_{l} \times (1- ON^{-}_{1,l,t}), &\quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T} +\end{aligned}\]

source

Unit Commitment

GenX.ucommit!Method
ucommit!(EP::Model, inputs::Dict, setup::Dict)

This function creates decision variables and cost expressions associated with thermal plant unit commitment or start-up and shut-down decisions (cycling on/off)

Unit commitment decision variables:

This function defines the following decision variables:

\[\nu_{y,t,z}\]

designates the commitment state of generator cluster $y$ in zone $z$ at time $t$; $\chi_{y,t,z}$ represents number of startup decisions in cluster $y$ in zone $z$ at time $t$; $\zeta_{y,t,z}$ represents number of shutdown decisions in cluster $y$ in zone $z$ at time $t$.

Cost expressions:

The total cost of start-ups across all generators subject to unit commitment ($y \in UC$) and all time periods, t is expressed as:

\[\begin{aligned} + C^{start} = \sum_{y \in UC, t \in T} \omega_t \times start\_cost_{y,t} \times \chi_{y,t} +\end{aligned}\]

The sum of start-up costs is added to the objective function.

source

CO2

GenX.co2!Method
co2!(EP::Model, inputs::Dict)

This function creates expressions to account for CO2 emissions as well as captured and sequestrated CO2 from thermal generators. It also has the capability to model the negative CO2 emissions from bioenergy with carbon capture and storage.

***** Expressions *****

For thermal generators which combust fuels (e.g., coal, natural gas, and biomass), the net CO2 emission to the environment is a function of fuel consumption, CO2 emission factor, CO2 capture fraction, and whether the feedstock is biomass. Biomass is a factor in this equation because biomass generators are assumed to generate zero net CO2 emissions, or negative net CO2 emissions in the case that the CO2 they emit is captured and sequestered underground.

If a user wishes to represent a generator that combusts biomass, then in the resource .csv files, the "Biomass" column (boolean, 1 or 0), which represents if a generator $y$ uses biomass or not, should be set to 1. The CO2 emissions from such a generator will be assumed to be zero without CCS and negative with CCS.

The CO2 emissions from generator $y$ at time $t$ are determined by total fuel consumption (MMBTU) multiplied by the CO2 content of the fuel (tCO2/MMBTU), and by (1 - Biomass [0 or 1] - CO2 capture fraction [a fraction, between 0 - 1]). The CO2 capture fraction could be differernt during the steady-state and startup events (generally startup events have a lower CO2 capture fraction), so we use distinct CO2 capture fractions to determine the emissions. In short, the CO2 emissions for a generator depend on the CO2 emission factor from fuel combustion, the CO2 capture fraction, and whether the generator uses biomass.

\[\begin{aligned} +eEmissionsByPlant_{g,t} = (1-Biomass_y- CO2\_Capture\_Fraction_y) * vFuel_{y,t} * CO2_{content} + (1-Biomass_y- CO2\_Capture\_Fraction\_Startup_y) * eStartFuel_{y,t} * CO2_{content} +\hspace{1cm} \forall y \in G, \forall t \in T, Biomass_y \in {{0,1}} +\end{aligned}\]

Where $Biomass_y$ represents a binary variable (1 or 0) that determines if the generator $y$ uses biomass, and $CO2\_Capture\_Fraction_y$ represents a fraction for CO2 capture rate.

In addition to CO2 emissions, for generators with a non-zero CO2 capture rate, we also determine the amount of CO2 being captured and sequestered. The CO2 emissions from generator $y$ at time $t$, denoted by $eEmissionsCaptureByPlant_{g,t}$, are determined by total fuel consumption (MMBTU) multiplied by the $CO_2$ content of the fuel (tCO2/MMBTU), times CO2 capture rate.

\[\begin{aligned} +eEmissionsCaptureByPlant_{g,t} = CO2\_Capture\_Fraction_y * vFuel_{y,t} * CO2_{content} + CO2\_Capture\_Fraction\_Startup_y * eStartFuel_{y,t} * CO2_{content} +\hspace{1cm} \forall y \in G, \forall t \in T +\end{aligned}\]

source
GenX.write_co2Method
write_co2(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting time-dependent CO2 emissions by zone.

source

Fuel

GenX.fuel!Method
fuel!(EP::Model, inputs::Dict, setup::Dict)

This function creates expressions to account for total fuel consumption (e.g., coal, natural gas, hydrogen, etc). It also has the capability to model heat rates that are a function of load via a piecewise-linear approximation.

***** Expressions ****** Users have two options to model the fuel consumption as a function of power generation: (1). Use a constant heat rate, regardless of the minimum load or maximum load; and (2). Use the PiecewiseFuelUsage-related parameters to model the fuel consumption via a piecewise-linear approximation of the heat rate curves. By using this option, users can represent the fact that most generators have a decreasing heat rate as a function of load.

(1). Constant heat rate. The fuel consumption for power generation $vFuel_{y,t}$ is determined by power generation ($vP_{y,t}$) mutiplied by the corresponding heat rate ($Heat\_Rate_y$). The fuel costs for power generation and start fuel for a plant $y$ at time $t$, denoted by $eCFuelOut_{y,t}$ and $eFuelStart$, are determined by fuel consumption ($vFuel_{y,t}$ and $eStartFuel$) multiplied by the fuel costs ($/MMBTU) (2). Piecewise-linear approximation With this formulation, the heat rate of generators becomes a function of load. In reality this relationship takes a nonlinear form, but we model it through a piecewise-linear approximation:

\[\begin{aligned} +vFuel_{y,t} >= vP_{y,t} * h_{y,x} + U_{g,t}* f_{y,x} +\hspace{1cm} \forall y \in G, \forall t \in T, \forall x \in X +\end{aligned}\]

Where $h_{y,x}$ represents the heat rate slope for generator $y$ in segment $x$ [MMBTU/MWh], $f_{y,x}$ represents the heat rate intercept (MMBTU) for a generator $y$ in segment $x$ [MMBTU], and $U_{y,t}$ represents the commitment status of a generator $y$ at time $t$. These parameters are optional inputs to the resource .csv files. When Unit commitment is on, if a user provides slope and intercept, the standard heat rate (i.e., HeatRateMMBTUperMWh) will not be used. When unit commitment is off, the model will always use the standard heat rate. The user should determine the slope and intercept parameters based on the CapSize of the plant. For example, when a plant is operating at the full load (i.e., power output equal to the CapSize), the fuel usage determined by the effective segment divided by Cap_Size should be equal to the heat rate at full-load.

Since fuel consumption and fuel costs are postive, the optimization will force the fuel usage to be equal to the highest fuel usage segment for any given value of vP. When the power output is zero, the commitment variable $U_{g,t}$ will bring the intercept to be zero such that the fuel consumption is zero when thermal units are offline.

In order to run piecewise fuel consumption module, the unit commitment must be turned on (UC = 1 or 2), and users should provide PWFUSlope* and PWFUIntercept* for at least one segment.

To enable resources to use multiple fuels during both startup and normal operational processes, three additional variables were added: fuel $i$ consumption by plant $y$ at time $t$ ($vMulFuel_{y,i,t}$); startup fuel consumption for single-fuel plants ($vStartFuel_{y,t}$); and startup fuel consumption for multi-fuel plants ($vMulStartFuel_{y,i,t}$). By making startup fuel consumption variables, the model can choose the startup fuel to meet the constraints.

For plants using multiple fuels:

During startup, heat input from multiple startup fuels are equal to startup fuel requirements in plant $y$ at time $t$: $StartFuelMMBTUperMW$ times $Capsize$.

\[\begin{aligned} +\sum_{i \in \mathcal{I} } vMulStartFuels_{y, i, t}= CapSize_{y} \times StartFuelMMBTUperMW_{y} \times vSTART_{y,t} +\end{aligned}\]

During normal operation, the sum of fuel consumptions from multiple fuels dividing by the correspnding heat rates, respectively, is equal to $vPower$ in plant $y$ at time $t$.

\[\begin{aligned} +\sum_{i \in \mathcal{I} } \frac{vMulFuels_{y, i, t}} {HeatRate_{i,y} } = vPower_{y,t} +\end{aligned}\]

There are also constraints on how much heat input each fuel can provide, which are specified by $MinCofire$ and $MaxCofire$. ```math \begin{aligned} vMulFuels{y, i, t} >= vPower{y,t} \times MinCofire{i} \end{aligned} \begin{aligned} vMulFuels{y, i, t} <= vPower{y,t} \times MaxCofire{i} \end{aligned}

source
diff --git a/previews/PR670/Model_Reference/generate_model/index.html b/previews/PR670/Model_Reference/generate_model/index.html new file mode 100644 index 0000000000..c4a1017625 --- /dev/null +++ b/previews/PR670/Model_Reference/generate_model/index.html @@ -0,0 +1,21 @@ + +Generate the Model · GenX.jl

Generating the model

GenX.generate_modelMethod
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
diff --git a/previews/PR670/Model_Reference/load_inputs/index.html b/previews/PR670/Model_Reference/load_inputs/index.html new file mode 100644 index 0000000000..7f5e090091 --- /dev/null +++ b/previews/PR670/Model_Reference/load_inputs/index.html @@ -0,0 +1,8 @@ + +Inputs Functions · GenX.jl

Reading Input Files

GenX.get_systemfiles_pathMethod
get_systemfiles_path(setup::Dict, TDR_directory::AbstractString, path::AbstractString)

Determine the directory based on the setup parameters.

This function checks if the TimeDomainReduction setup parameter is equal to 1 and if time domain reduced files exist in the data directory. If the condition is met, it returns the path to the TDR_results data directory. Otherwise, it returns the system directory specified in the setup.

Parameters:

  • setup: Dict{String, Any} - The GenX settings parameters containing TimeDomainReduction and SystemFolder information.
  • TDR_directory: String - The data directory where files are located.
  • path: String - Path to the case folder.

Returns:

  • String: The directory path based on the setup parameters.
source
GenX.load_inputsMethod
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

Fuels Data

GenX.load_fuels_data!Method
load_fuels_data!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to fuel costs and CO$_2$ content of fuels

source

Generators Input Data

GenX._get_policyfile_infoMethod
_get_policyfile_info()

Internal function to get policy file information.

Returns

policyfile_info (NamedTuple): A tuple containing policy file information.
source
GenX._get_resource_infoMethod
_get_resource_info()

Internal function to get resource information (filename and GenX type) for each type of resource available in GenX.

resource_info (NamedTuple): A tuple containing resource information.
source
GenX._get_summary_mapMethod
_get_summary_map()

Internal function to get a map of GenX resource type their corresponding names in the summary table.

source
GenX.add_attributes_to_resource!Method
add_attributes_to_resource!(resource::AbstractResource, new_symbols::Vector{Symbol}, new_values::T) where T <: DataFrameRow

Adds a set of new attributes (names and corresponding values) to a resource. The resource is modified in-place.

Arguments

  • resource::AbstractResource: The resource to add attributes to.
  • new_symbols::Vector{Symbol}: Vector of symbols containing the names of the new attributes.
  • new_values::DataFrameRow: DataFrameRow containing the values of the new attributes.
source
GenX.add_df_to_resources!Method
add_df_to_resources!(resources::Vector{<:AbstractResource}, module_in::DataFrame)

Adds the data contained in a DataFrame to a vector of resources. Each row in the DataFrame corresponds to a resource. If the name of the resource in the DataFrame matches a name of a resource in the model, all the columns of that DataFrameRow are added as new attributes to the corresponding resource.

Arguments

  • resources::Vector{<:AbstractResource}: A vector of resources.
  • module_in::DataFrame: The dataframe to add.
source
GenX.add_id_to_resource_df!Method
add_id_to_resource_df!(df::DataFrame, indices::AbstractVector)

Adds a new column 'id' to the DataFrame with the provided resource indices. The dataframe is modified in-place.

Arguments

  • df::DataFrame: The input DataFrame to which the indices are to be added.
  • indices::AbstractVector: The array of indices to be added as a new column.
source
GenX.add_module_to_resources!Method
add_module_to_resources!(resources::Vector{<:AbstractResource}, module_in::DataFrame)

Reads module dataframe and adds columns as new attributes to the resources in the model if the resource name in the module file matches a resource name in the model. The module file is assumed to have a column named "resource" containing the resource names.

Arguments

  • resources::Vector{<:AbstractResource}: A vector of resources.
  • module_in::DataFrame: The dataframe with the columns to add to the resources.
source
GenX.add_modules_to_resources!Method
add_modules_to_resources!(resources::Vector{<:AbstractResource}, setup::Dict, resources_path::AbstractString)

Reads module dataframes, loops over files and adds columns as new attributes to the resources in the model.

Arguments

  • resources::Vector{<:AbstractResource}: A vector of resources.
  • setup (Dict): A dictionary containing GenX settings.
  • resources_path::AbstractString: The path to the resources folder.
source
GenX.add_policies_to_resources!Method
add_policies_to_resources!(resources::Vector{<:AbstractResource}, resources_path::AbstractString)

Reads policy files and adds policies-related attributes to resources in the model.

Arguments

  • resources::Vector{<:AbstractResource}: Vector of resources in the model.
  • resources_path::AbstractString: The path to the resources folder.
source
GenX.add_policy_to_resources!Method
add_policy_to_resources!(resources::Vector{<:AbstractResource}, path::AbstractString, filename::AbstractString)

Loads a single policy file and adds the columns as new attributes to resources in the model if the resource name in the policy file matches a resource name in the model. The policy file is assumed to have a column named "resource" containing the resource names.

Arguments

  • resources::Vector{<:AbstractResource}: A vector of resources.
  • path::AbstractString: The path to the policy file.
  • filename::AbstractString: The name of the policy file.
source
GenX.add_resources_to_input_data!Method
add_resources_to_input_data!(inputs::Dict, setup::Dict, case_path::AbstractString, gen::Vector{<:AbstractResource})

Adds resources to the inputs Dict with the key "RESOURCES" together with sevaral sets of resource indices that are used inside GenX to construct the optimization problem. The inputs Dict is modified in-place.

Arguments

  • inputs (Dict): Dictionary to store the GenX input data.
  • setup (Dict): Dictionary containing GenX settings.
  • case_path (AbstractString): Path to the case.
  • gen (Vector{<:AbstractResource}): Array of GenX resources.
source
GenX.check_resourceMethod
check_resource(resources::Vector{T})::Vector{String} where T <: AbstractResource

Validate the consistency of a vector of GenX resources Reports any errors/warnings as a vector of messages.

source
GenX.compute_resource_indicesMethod
compute_resource_indices(resources_in::DataFrame, offset::Int64)

Computes the indices for the resources loaded from a single dataframe by shifting the indices by an offset value.

Arguments

  • resources_in::DataFrame: The input DataFrame containing the resources.
  • offset::Int64: The offset value to be added to the indices.

Returns

  • UnitRange{Int64}: An array of indices.
source
GenX.create_resource_arrayFunction
create_resource_array(resource_folder::AbstractString, resources_info::NamedTuple, scale_factor::Float64=1.0)

Construct the array of resources from multiple files of different types located in the specified resource_folder. The resources_info NamedTuple contains the filename and GenX type for each type of resource available in GenX.

Arguments

  • resource_folder::AbstractString: The path to the folder containing the resource files.
  • resources_info::NamedTuple: A NamedTuple that maps a resource type to its filename and GenX type.
  • scale_factor::Float64: A scaling factor to adjust the attributes of the resources (default: 1.0).

Returns

  • Vector{<:AbstractResource}: An array of GenX resources.

Raises

  • Error: If no resources data is found. Check the data path or the configuration file "genx_settings.yml" inside Settings.
source
GenX.create_resource_arrayMethod
create_resource_array(setup::Dict, resources_path::AbstractString)

Function that loads and scales resources data from folder specified in resources_path and returns an array of GenX resources.

Arguments

  • setup (Dict): Dictionary containing GenX settings.
  • resources_path (AbstractString): The path to the resources folder.

Returns

  • resources (Vector{<:AbstractResource}): An array of scaled resources.
source
GenX.create_resources_sametypeMethod
create_resources_sametype(resource_in::DataFrame, ResourceType)

This function takes a DataFrame resource_in and a GenX ResourceType type, and converts the DataFrame to an array of AbstractResource of the specified type.

Arguments

  • resource_in::DataFrame: The input DataFrame containing the resources belonging to a specific type.
  • ResourceType: The GenX type of resources to be converted to.

Returns

  • resources::Vector{ResourceType}: An array of resources of the specified type.
source
GenX.dataframerow_to_dictMethod
dataframerow_to_dict(dfr::DataFrameRow)

Converts a DataFrameRow to a Dict.

Arguments

  • dfr::DataFrameRow: The DataFrameRow to be converted.

Returns

  • Dict: Dictionary containing the DataFrameRow data.
source
GenX.load_multi_fuels_data!Method
load_multi_fuels_data!(inputs::Dict, gen::Vector{<:AbstractResource}, setup::Dict, path::AbstractString)

Function for reading input parameters related to multi fuels

source
GenX.load_resource_dfMethod
load_resource_df(path::AbstractString, scale_factor::Float64, resource_type::Type)

Function to load and scale the dataframe of a given resource.

Arguments

  • path::AbstractString: Path to the resource dataframe.
  • scale_factor::Float64: Scaling factor for the resource data.
  • resource_type::Type: GenX type of the resource.

Returns

  • resource_in::DataFrame: The loaded and scaled resource data.
source
GenX.load_resources_data!Method
load_resources_data!(inputs::Dict, setup::Dict, case_path::AbstractString, resources_path::AbstractString)

This function loads resources data from the resources_path folder and create the GenX data structures and add them to the inputs Dict.

Arguments

  • inputs (Dict): A dictionary to store the input data.
  • setup (Dict): A dictionary containing GenX settings.
  • case_path (AbstractString): The path to the case folder.
  • resources_path (AbstractString): The path to the case resources folder.

Raises: DeprecationWarning: If the Generators_data.csv file is found, a deprecation warning is issued, together with an error message.

source
GenX.process_piecewisefuelusage!Method
process_piecewisefuelusage!(setup::Dict, case_path::AbstractString, gen::Vector{<:AbstractResource}, inputs::Dict)

Reads piecewise fuel usage data from the vector of generators, create a PWFU_data that contain processed intercept and slope (i.e., heat rate) and add them to the inputs dictionary.

Arguments

  • setup::Dict: The dictionary containing the setup parameters
  • case_path::AbstractString: The path to the case folder
  • gen::Vector{<:AbstractResource}: The vector of generators in the model
  • inputs::Dict: The dictionary containing the input data
source
GenX.scale_columns!Method
scale_columns!(df::DataFrame, columns_to_scale::Vector{Symbol}, scale_factor::Float64)

Scales in-place the columns in columns_to_scale of a dataframe df by a scale_factor.

Arguments

  • df (DataFrame): A dataframe containing data to scale.
  • columns_to_scale (Vector{Symbol}): A vector of column names to scale.
  • scale_factor (Float64): A scaling factor for energy and currency units.
source
GenX.scale_resources_data!Method
scale_resources_data!(resource_in::DataFrame, scale_factor::Float64)

Scales resources attributes in-place if necessary. Generally, these scalings converts energy and power units from MW to GW and /MW to M/GW. Both are done by dividing the values by 1000. See documentation for descriptions of each column being scaled.

Arguments

  • resource_in (DataFrame): A dataframe containing data for a specific resource.
  • scale_factor (Float64): A scaling factor for energy and currency units.
source
GenX.scale_vre_stor_data!Method
scale_vre_stor_data!(vre_stor_in::DataFrame, scale_factor::Float64)

Scales vre_stor attributes in-place if necessary. Generally, these scalings converts energy and power units from MW to GW and /MW to M/GW. Both are done by dividing the values by 1000. See documentation for descriptions of each column being scaled.

Arguments

  • vre_stor_in (DataFrame): A dataframe containing data for co-located VREs and storage.
  • scale_factor (Float64): A scaling factor for energy and currency units.
source
GenX.split_storage_resources!Method
split_storage_resources!(inputs::Dict, gen::Vector{<:AbstractResource})

For co-located VRE-storage resources, this function returns the storage type (1. long-duration or short-duration storage, 2. symmetric or asymmetric storage) for charging and discharging capacities

source
GenX.summaryMethod
summary(rs::Vector{<:AbstractResource})

Prints a summary of the resources loaded into the model.

Arguments

  • rs (Vector{<:AbstractResource}): An array of GenX resources.
source
GenX.update_retrofit_idMethod
update_retrofit_id(r::AbstractResource)

Updates the retrofitid of a resource that can be retrofit or is a retrofit option by appending the region to the retrofitid.

Arguments

  • r::AbstractResource: The resource to update.
source
GenX.validate_policy_dataframe!Method
validate_policy_dataframe!(filename::AbstractString, policy_in::DataFrame)

Validate the policy dataframe by checking if it has any attributes and if the column names are valid. The dataframe is modified in-place.

Arguments

  • filename::AbstractString: The name of the policy file.
  • policy_in::DataFrame: The policy dataframe.
source
GenX.validate_policy_filesMethod
validate_policy_files(resource_policies_path::AbstractString, setup::Dict)

Validate the policy files by checking if they exist in the specified folder and if the setup flags are consistent with the files found.

Arguments

  • resource_policies_path::AbstractString: The path to the policy files.
  • setup::Dict: Dictionary containing GenX settings.

Returns

  • warning messages if the polcies are set to 1 in settings but the files are not found in the resourcepoliciespath.

!isfile(joinpath(resourcepoliciespath, filename))

source

Variability of Generators' Outputs

GenX.load_generators_variability!Method
load_generators_variability!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to hourly maximum capacity factors for generators, storage, and flexible demand resources

source

Demand Data

GenX.load_demand_data!Method
load_demand_data!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to electricity demand (load)

source
GenX.prevent_doubled_timedomainreductionMethod
prevent_doubled_timedomainreduction(path::AbstractString)

This function prevents TimeDomainReduction from running on a case which already has more than one Representative Period or has more than one Sub_Weight specified.

source

Transmission Network

GenX.load_network_data!Method
load_network_data!(setup::Dict, path::AbstractString, inputs_nw::Dict)

Function for reading input parameters related to the electricity transmission network

source
GenX.load_network_map_from_listMethod
load_network_map_from_list(network_var::DataFrame, Z, L, list_columns)

Loads the network map from a list-style interface

..., Network_Lines, Start_Zone, End_Zone, ...
+                 1,           1,       2,
+                 2,           1,       3,
source
GenX.load_network_map_from_matrixMethod
load_network_map_from_matrix(network_var::DataFrame, Z, L)

Loads the network map from a matrix-style interface

..., Network_Lines, z1, z2, z3, ...
+                 1,  1, -1,  0,
+                 2,  1,  0, -1,

This is equivalent to the list-style interface where the zone zN with entry +1 is the starting zone of the line and the zone with entry -1 is the ending zone of the line.

source

Minimum Capacity Requirements

GenX.load_minimum_capacity_requirement!Method
load_minimum_capacity_requirement!(path::AbstractString, inputs::Dict, setup::Dict)

Read input parameters related to mimimum capacity requirement constraints (e.g. technology specific deployment mandates)

source

Capacity Reserve Margin

GenX.load_cap_reserve_margin!Method
load_cap_reserve_margin!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to planning reserve margin constraints

source
GenX.load_cap_reserve_margin_trans!Method
load_cap_reserve_margin_trans!(setup::Dict, inputs::Dict, network_var::DataFrame)

Read input parameters related to participation of transmission imports/exports in capacity reserve margin constraint.

source

CO$_2$ Emissions Cap

GenX.load_co2_cap!Method
load_co2_cap!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to CO$_2$ emissions cap constraints

source

Energy Share Requirement

GenX.load_energy_share_requirement!Method
load_energy_share_requirement!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to mimimum energy share requirement constraints (e.g. renewable portfolio standard or clean electricity standard policies)

source

Mapping Representative Time Periods

GenX.load_period_map!Method
load_period_map!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to mapping of representative time periods to full chronological time series

source

Variability of the Solar PV and Wind Components' Outputs (for Co-located Storage Resources)

GenX.load_vre_stor_variability!Method
load_vre_stor_variability!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to hourly maximum capacity factors for the solar PV (DC capacity factors) component and wind (AC capacity factors) component of co-located generators

source

Functions for developers

Standardized loading of dataframes from CSV files

GenX.extract_matrix_from_dataframeMethod
extract_matrix_from_dataframe(df::DataFrame, columnprefix::AbstractString)

Finds all columns in the dataframe which are of the form columnprefix_[Integer], and extracts them in order into a matrix. The function also checks that there's at least one column with this prefix, and that all columns numbered from 1...N exist.

This is now acceptable:

ESR_1, other_thing, ESR_3, ESR_2,
+  0.1,           1,   0.3,   0.2,
+  0.4,           2,   0.6,   0.5,
source
GenX.file_existsMethod
file_exists(dir::AbstractString, basenames::Vector{String})::Bool

Checks that a file exists in a directory under (at least) one of a list of 'aliases'.

source
GenX.load_dataframeMethod
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.load_dataframeMethod
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
GenX.validate_df_colsMethod
validate_df_cols(df::DataFrame, df_name::AbstractString, required_cols::Vector{AbstractString})

Check that the dataframe has all the required columns.

Arguments

  • df::DataFrame: the dataframe to check
  • df_name::AbstractString: the name of the dataframe, for error messages
  • required_cols::Vector{AbstractString}: the names of the required columns
source
diff --git a/previews/PR670/Model_Reference/maintenance_overview/index.html b/previews/PR670/Model_Reference/maintenance_overview/index.html new file mode 100644 index 0000000000..af5bc884e3 --- /dev/null +++ b/previews/PR670/Model_Reference/maintenance_overview/index.html @@ -0,0 +1,2 @@ + +Maintenance · GenX.jl

Optimized Scheduled Maintenance

Added in v0.4

In the real world, some types of resources (notably, fission) require regular scheduled maintenance, which often takes several weeks. During this time, the plant produces no power. This module allows GenX to find the best time of year for plants to undergo maintenance.

Scheduled maintenance is implemented only for thermal plants with unit commitment (THERM=1).

Description of the maintenance model

A plant requires a single contiguous period of $h \ge 1$ hours of maintenance, every $y \ge 1$ years. For each plant, the best time to start the maintenance period is determined by the optimizer.

During maintenance, the plant cannot be "commited", and therefore

  • uses no fuel,
  • produces no power,
  • and does not contribute to reserves.

Additionally,

  • the plant does not contribute to any Capacity Reserve Margin.

Treatment of plants that require maintenance only every few years

GenX models a long-term equilibrium, and each problem generally represents a single full year. If a plant requires maintenance every $y$ years, we take the simplification that at least $1/y$ of the plants must undergo maintenance in the modeled year.

See also "Interaction with integer unit commitment" below.

Reduction of number of possible start dates

This module creates constraints which work across long periods, and consequently can be very expensive to solve. In order to reduce the expense, the set of possible maintenance start dates can be limited. Rather than have maintenance potentially start every hour, one can have possible start dates which are once per day, once per week, etc. (In reality, maintenance is likely scheduled months in advance, so optimizing down to the hour may not be realistic anyway.)

How to use

There are four columns which need to be added to the plant data, i.e. in Generators_data.csv:

  1. MAINT should be 1 for plants that require maintenance and 0 otherwise.
  2. Maintenance_Duration is the number of hours the maintenance period lasts.
  3. Maintenance_Cycle_Length_Years. If 1, maintenance every year, if 3 maintenance every 3 years, etc.
  4. Maintenance_Begin_Cadence. Spacing between hours in which maintenance can start.

The last three fields must be integers which are greater than 0. They are ignored for any plants which do not require maintenance.

Maintenance_Duration must be less than the total number of hours in the year.

If Maintenance_Begin_Cadence is 1 then the maintenance can begin in any hour. If it is 168 then it can begin in hours 1, 169, 337, etc.

Restrictions on use

The maintenance module has these restrictions:

  • More than a single maintenance period per year (i.e. every three months) is not possible in the current formulation.
  • Only full-year cases can be run; there must be only one "representative period".

It would not make sense to model a month-long maintenance period when the year is modeled as a series of representative weeks, for example.

Interaction with integer unit commitment

If integer unit commitment is on (UCommit=1) this module may not produce correct results; there may be more maintenance than the user wants. This is because the formulation specifies that the number of plants that go down for maintenance in the simulated year must be at least (the number of plants in the zone)/(the maintenance cycle length in years). As a reminder, the number of plants is eTotalCap / Cap_Size.

If there were three 500 MW plants (total 1500 MW) in a zone, and they require maintenance every three years (Maintenance_Cycle_Length_Years=3), the formulation will work properly: one of the three plants will go under maintenance.

But if there was only one 500 MW plant, and it requires maintenance every 3 years, the constraint will still make it do maintenance every year, because ceil(1/3) is 1. The whole 500 MW plant will do maintenance. This is the unexpected behavior.

However, if integer unit commitment was relaxed to "linearized" unit commitment (UCommit=2), the model will have only 500 MW / 3 = 166.6 MW worth of this plant do maintenance.

Hint: pre-scheduling maintenance

If you want to pre-schedule when maintenance occurs, you might not need this module. Instead, you could set the maximum power output of the plant to zero for a certain period, or make its fuel extremely expensive during that time. However, the plant would still be able to contribute to the Capacity Reserve Margin.

Outputs produced

If at least one plant has MAINT=1, a file maint_down.csv will be written listing how many plants are down for maintenance in each timestep.

Notes on mathematical formulation

The formulation of the maintenance state is very similar to the formulation of unit commitment.

There is a variable called something like vMSHUT which is analogous to vSTART and controls the start of the maintenance period. There is another variable called something like vMDOWN analogous to vCOMMIT which controls the maintenance status in any hour.

A constraint ensures that the value of vMDOWN in any hour is always more than the number of vMSHUTs in the previous Maintenance_Duration hours.

Another constraint ensures that the number of plants committed (vCOMMIT) at any one time plus the number of plants under maintenance (vMDOWN) is less than the total number of plants.

Developer note: adding maintenance to a resource

The maintenance formulation is applied on a per-resource basis, by calling the function GenX.maintenance_formulation!.

See GenX.maintenance_formulation_thermal_commit! for an example of how to apply it to a new resource.

  • The resource must have a vCOMMIT-like variable which is proportional to maximum the power output, etc at any given timestep.
  • The resource must have a eTotalCap-like quantity and a Cap_Size-like parameter; only the ratio of the two is used.
diff --git a/previews/PR670/Model_Reference/methodofmorris/index.html b/previews/PR670/Model_Reference/methodofmorris/index.html new file mode 100644 index 0000000000..f5879a20c6 --- /dev/null +++ b/previews/PR670/Model_Reference/methodofmorris/index.html @@ -0,0 +1,2 @@ + +Method of Morris · GenX.jl

Method of Morris

GenX.MatSpreadType
morris(EP::Model, path::AbstractString, setup::Dict, inputs::Dict, outpath::AbstractString, OPTIMIZER)

We apply the Method of Morris developed by Morris, M., 1991 in order to identify the input parameters that produce the largest change on total system cost. Method of Morris falls under the simplest class of one-factor-at-a-time (OAT) screening techniques. It assumes l levels per input factor and generates a set of trajectories through the input space. As such, the Method of Morris generates a grid of uncertain model input parameters, $x_i, i=1, ..., k$,, where the range $[x_i^{-}, x_i^{+}$ of each uncertain input parameter i is split into l intervals of equal length. Each trajectory starts at different realizations of input parameters chosen at random and are built by successively selecting one of the inputs randomly and moving it to an adjacent level. These trajectories are used to estimate the mean and the standard deviation of each input parameter on total system cost. A high estimated mean indicates that the input parameter is important; a high estimated standard deviation indicates important interactions between that input parameter and other inputs.

source
diff --git a/previews/PR670/Model_Reference/mga/index.html b/previews/PR670/Model_Reference/mga/index.html new file mode 100644 index 0000000000..51cdbc184e --- /dev/null +++ b/previews/PR670/Model_Reference/mga/index.html @@ -0,0 +1,9 @@ + +Modeling to Generate Alternatives · GenX.jl

Modeling to Generate Alternatives

GenX.mgaMethod
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:

\[\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_{zr} = \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_{zr}$ is a random objective fucntion coefficient betwen $[0,100]$ 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 above, $\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
diff --git a/previews/PR670/Model_Reference/policies/index.html b/previews/PR670/Model_Reference/policies/index.html new file mode 100644 index 0000000000..5879e179dc --- /dev/null +++ b/previews/PR670/Model_Reference/policies/index.html @@ -0,0 +1,35 @@ + +Policies · GenX.jl

Emission mitigation policies

Capacity Reserve Margin

GenX.cap_reserve_margin!Function
cap_reserve_margin!(EP::Model, inputs::Dict, setup::Dict)

Instead of modeling capacity reserve margin requirement (a.k.a. capacity market or resource adequacy requirement) using an annual constraint, we model each requirement with hourly constraint by simulating the activation of the capacity obligation. We define capacity reserve margin constraint for subsets of zones, $z \in \mathcal{Z}^{CRM}_{p}$, and each subset stands for a locational deliverability area (LDA) or a reserve sharing group. For thermal resources, the available capacity is the total capacity in the LDA derated by the outage rate, $\epsilon_{y,z,p}^{CRM}$. For variable renewable energy ($y \in \mathcal{VRE}$), the available capacity is the maximum discharge potential in time step $t$ derated by the derating factor. For standalone storage and co-located VRE and storage resources ($y \in \mathcal{O} \cup \mathcal{VS}$) the available capacity is the net injection into the transmission network plus the net virtual injection corresponding to charge held in reserve, derated by the derating factor. For information on how each component contributes to the capacity reserve margin formulation for co-located VRE and storage resources, see vre_stor_capres!(). For flexible demand resources ($y \in \mathcal{DF}$), the available capacity is the net injection into the transmission network in time step $t$ derated by the derating factor, also stored in the parameter, $\epsilon_{y,z,p}^{CRM}$. If the imported capacity is eligible to provide capacity to the CRM constraint, the inbound powerflow on all lines $\mathcal{L}_{p}^{in}$ in time step $t$ will be derated to form the available capacity from outside of the LDA. The reverse is true as well: the outbound derated powerflow on all lines $\mathcal{L}_{p}^{out}$ in time step $t$ is taken out from the total available capacity. The derating factor should be equal to the expected availability of the resource during periods when the capacity reserve constraint is binding (e.g. accounting for forced outages during supply constrained periods) and is similar to derating factors used in the capacity markets. On top of the flexible demand resources, demand curtailment can also provide capacity (i.e., demand response or load management). We allow all segments of voluntary demand curtailment, $s \geq 2 \in S$, to contribute to capacity requirements. The first segment $s = 1 \in S$ corresponds to involuntary demand curtailment or emergency load shedding at the price cap or value of lost demand, and thus does not contribute to reserve requirements. Note that the time step-weighted sum of the shadow prices of this constraint corresponds to the capacity market payments reported by ISOs with mandate capacity market mechanism.

\[\begin{aligned} + & \sum_{z \in \mathcal{Z}^{CRM}_{p}} \Big( \sum_{y \in \mathcal{H}} \epsilon_{y,z,p}^{CRM} \times \Delta^{\text{total}}_{y,z} + \sum_{y \in \mathcal{VRE}} \epsilon_{y,z,p}^{CRM} \times \rho^{max}_{y,z,t} \\ + & + \sum_{y \in \mathcal{O}} \epsilon_{y,z,p}^{CRM} \times \left(\Theta_{y,z,t} + \Theta^{CRM}_{o,z,t} - \Pi^{CRM}_{o,z,t} - \Pi_{y,z,t} \right) + \sum_{y \in \mathcal{DF}} \epsilon_{y,z,p}^{CRM} \times \left(\Pi_{y,z,t} - \Theta_{y,z,t} \right) \\ + & + \sum_{y \in \mathcal{VS}^{pv}} (\epsilon_{y,z,p}^{CRM} \times \eta^{inverter}_{y,z} \times \rho^{max,pv}_{y,z,t} \times \Delta^{total,pv}_{y,z}) \\ + & + \sum_{y \in \mathcal{VS}^{wind}} (\epsilon_{y,z,p}^{CRM} \times \rho^{max,wind}_{y,z,t} \times \Delta^{total,wind}_{y,z}) \\ + & + \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,dis}} (\epsilon_{y,z,p}^{CRM} \times \eta^{inverter}_{y,z} \times (\Theta^{dc}_{y,z,t} + \Theta^{CRM,dc}_{y,z,t})) \\ + & + \sum_{y \in \mathcal{VS}^{sym,ac} \cup \mathcal{VS}^{asym,ac,dis}} (\epsilon_{y,z,p}^{CRM} \times (\Theta^{ac}_{y,z,t} + \Theta^{CRM,ac}_{y,z,t})) \\ + & - \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} (\epsilon_{y,z,p}^{CRM} \times \frac{\Pi^{dc}_{y,z,t} + \Pi^{CRM,dc}_{y,z,t}}{\eta^{inverter}_{y,z}}) \\ + & - \sum_{y \in \mathcal{VS}^{sym,dc} \cup \mathcal{VS}^{asym,dc,cha}} (\epsilon_{y,z,p}^{CRM} \times (\Pi^{ac}_{y,z,t} + \Pi^{CRM,ac}_{y,z,t})) \\ + & + \sum_{l \in \mathcal{L}_{p}^{in}} \epsilon_{y,z,p}^{CRM} \times \Phi_{l,t} - \sum_{l \in \mathcal{L}_{p}^{out}} \epsilon_{y,z,p}^{CRM} \times \Phi_{l,t} + + \sum_{s \geq 2} \Lambda_{s,t,z} \Big) \\ + & \geq \sum_{z \in \mathcal{Z}^{CRM}_{p}} \left( \left(1 + RM_{z,p}^{CRM} \right) \times D_{z,t} \right) \hspace{1 cm} \forall t \in \mathcal{T}, \forall p\in \mathcal{P}^{CRM} +\end{aligned}\]

Note that multiple capacity reserve margin requirements can be specified covering different individual zones or aggregations of zones, where the total number of constraints is specified by the GenX settings parameter CapacityReserveMargin (where this parameter should be an integer value > 0). The expressions establishing the capacity reserve margin contributions of each technology class are included in their respective technology modules.

source

CO$_2$ Constraint Policy

GenX.co2_cap!Function
co2_cap!(EP::Model, inputs::Dict, setup::Dict)

This policy constraints mimics the CO$_2$ emissions cap and permit trading systems, allowing for emissions trading across each zone for which the cap applies. The constraint $p \in \mathcal{P}^{CO_2}$ can be flexibly defined for mass-based or rate-based emission limits for one or more model zones, where zones can trade CO$_2$ emissions permits and earn revenue based on their CO$_2$ allowance. Note that if the model is fully linear (e.g. no unit commitment or linearized unit commitment), the dual variable of the emissions constraints can be interpreted as the marginal CO$_2$ price per tonne associated with the emissions target. Alternatively, for integer model formulations, the marginal CO$_2$ price can be obtained after solving the model with fixed integer/binary variables.

The CO$_2$ emissions limit can be defined in one of the following ways: a) a mass-based limit defined in terms of annual CO$_2$ emissions budget (in million tonnes of CO2), b) a demand-side rate-based limit defined in terms of tonnes CO$_2$ per MWh of fulfilled demand and c) a generation-side rate-based limit defined in terms of tonnes CO$_2$ per MWh of generation.

Mass-based emissions constraint

Mass-based emission limits are implemented in the following expression. For each constraint, $p \in \mathcal{P}^{CO_2}_{mass}$, we define a set of zones $z \in \mathcal{Z}^{CO_2}_{p,mass}$ that can trade CO$_2$ allowance. Input data for each constraint $p \in \mathcal{P}^{CO_2}_{mass}$ requires the CO$_2$ allowance/ budget for each model zone, $\epsilon^{CO_{2}}_{z,p, mass}$, to be provided in terms of million metric tonnes. For every generator $y$, the parameter $\epsilon_{y,z}^{CO_2}$ reflects the specific $CO_2$ emission intensity in tCO$_2$/MWh associated with its operation. The resulting constraint is given as:

\[\begin{aligned} + \sum_{z \in \mathcal{Z}^{CO_2}_{p,mass}} \sum_{y \in \mathcal{G}} \sum_{t \in \mathcal{T}} \left(\epsilon_{y,z}^{CO_2} \times \omega_{t} \times \Theta_{y,z,t} \right) + & \leq \sum_{z \in \mathcal{Z}^{CO_2}_{p,mass}} \epsilon^{CO_{2}}_{z,p, mass} \hspace{1 cm} \forall p \in \mathcal{P}^{CO_2}_{mass} +\end{aligned}\]

In the above constraint, we include both power discharge and charge term for each resource to account for the potential for CO$_2$ emissions (or removal when considering negative emissions technologies) associated with each step. Note that if a limit is applied to each zone separately, then the set $\mathcal{Z}^{CO_2}_{p,mass}$ will contain only one zone with no possibility of trading. If a system-wide emission limit constraint is applied, then $\mathcal{Z}^{CO_2}_{p,mass}$ will be equivalent to a set of all zones.

Demand-side rate-based emissions constraint

We modify the right hand side of the above mass-based constraint, $p \in \mathcal{P}^{CO_2}_{demand}$, to set emissions target based on a CO$_2$ emission rate limit in tCO$_2$/MWh $\times$ the total demand served (fulfilled) in each zone. In the following constraint, total demand served takes into account non-served energy and storage related losses. Here, $\epsilon_{z,p,demand}^{maxCO_2}$ denotes the emission limit in terms on tCO$_2$/MWh.

\[\begin{aligned} + \sum_{z \in \mathcal{Z}^{CO_2}_{p,demand}} \sum_{y \in \mathcal{G}} \sum_{t \in \mathcal{T}} \left(\epsilon_{y,z}^{CO_2} \times \omega_{t} \times \Theta_{y,t,z} \right) + \leq & \sum_{z \in \mathcal{Z}^{CO_2}_{p,demand}} \sum_{t \in \mathcal{T}} + \left(\epsilon_{z,p,demand}^{CO_2} \times \omega_{t} \times D_{z,t} \right) \\ + + & \sum_{z \in \mathcal{Z}^{CO_2}_{p,demand}} \sum_{y \in \mathcal{O} \cup \mathcal{VS}^{stor}} \sum_{t \in \mathcal{T}} + \left(\epsilon_{z,p,demand}^{CO_2} \times \omega_{t} \times \left(\Pi_{y,t,z} - \Theta_{y,t,z} \right) \right) \\ + - & \sum_{z \in \mathcal{Z}^{CO_2}_{p,demand}} \sum_{s \in \mathcal{S} } \sum_{t \in \mathcal{T}} \left(\epsilon_{z,p,demand}^{CO_2} \times \omega_{t} \times \Lambda_{s,z,t}\right) \hspace{1 cm} \forall p \in \mathcal{P}^{CO_2}_{demand} +\end{aligned}\]

Generator-side emissions rate-based constraint

Similarly, a generation based emission constraint is defined by setting the emission limit based on the total generation times the carbon emission rate limit in tCO$_2$/MWh of the region. The resulting constraint is given as:

\[\begin{aligned} +\sum_{z \in \mathcal{Z}^{CO_2}_{p,gen}} \sum_{y \in \mathcal{G}} \sum_{t \in \mathcal{T}} & \left(\epsilon_{y,z}^{CO_2} \times \omega_{t} \times \Theta_{y,t,z} \right) \\ + \leq \sum_{z \in \mathcal{Z}^{CO_2}_{p,gen}} \sum_{y \in \mathcal{G}} \sum_{t \in \mathcal{T}} & \left(\epsilon_{z,p,gen}^{CO_2} \times \omega_{t} \times \Theta_{y,t,z} \right) \hspace{1 cm} \forall p \in \mathcal{P}^{CO_2}_{gen} +\end{aligned}\]

Note that the generator-side rate-based constraint can be used to represent a fee-rebate (``feebate'') system: the dirty generators that emit above the bar ($\epsilon_{z,p,gen}^{maxCO_2}$) have to buy emission allowances from the emission regulator in the region $z$ where they are located; in the same vein, the clean generators get rebates from the emission regulator at an emission allowance price being the dual variable of the emissions rate constraint.

source

Energy Share Requirement

GenX.load_energy_share_requirement!Function
load_energy_share_requirement!(setup::Dict, path::AbstractString, inputs::Dict)

Read input parameters related to mimimum energy share requirement constraints (e.g. renewable portfolio standard or clean electricity standard policies)

source
GenX.energy_share_requirement!Function
energy_share_requirement!(EP::Model, inputs::Dict, setup::Dict)

This function establishes constraints that can be flexibily applied to define alternative forms of policies that require generation of a minimum quantity of megawatt-hours from a set of qualifying resources, such as renewable portfolio standard (RPS) or clean electricity standard (CES) policies prevalent in different jurisdictions. These policies usually require that the annual MWh generation from a subset of qualifying generators has to be higher than a pre-specified percentage of demand from qualifying zones. The implementation allows for user to define one or multiple RPS/CES style minimum energy share constraints, where each constraint can cover different combination of model zones to mimic real-world policy implementation (e.g. multiple state policies, multiple RPS tiers or overlapping RPS and CES policies). The number of energy share requirement constraints is specified by the user by the value of the GenX settings parameter EnergyShareRequirement (this value should be an integer >=0). For each constraint $p \in \mathcal{P}^{ESR}$, we define a subset of zones $z \in \mathcal{Z}^{ESR}_{p} \subset \mathcal{Z}$ that are eligible for trading renewable/clean energy credits to meet the corresponding renewable/clean energy requirement. For each energy share requirement constraint $p \in \mathcal{P}^{ESR}$, we specify the share of total demand in each eligible model zone, $z \in \mathcal{Z}^{ESR}_{p}$, that must be served by qualifying resources, $\mathcal{G}_{p}^{ESR} \subset \mathcal{G}$:

\[\begin{aligned} +&\sum_{z \in \mathcal{Z}_{p}^{ESR}} \sum_{y \in \mathcal{G}_{p}^{ESR}} \sum_{t \in \mathcal{T}} (\omega_{t} \times \Theta_{y,z,t}) \geq \sum_{z \in \mathcal{Z}^{ESR}_{p}} \sum_{t \in \mathcal{T}} (\mu_{p,z}^{ESR} \times \omega_{t} \times D_{z,t}) + \\ +&\sum_{y \in \mathcal{VS}^{stor}} \sum_{z \in \mathcal{Z}^{ESR}_{p}} \sum_{t \in \mathcal{T}} \left(\mu_{p,z}^{ESR} \times \omega_{t} \times (\frac{\Pi^{dc}_{y,z,t}}{\eta_{y,z}^{inverter}} + \Pi^{ac}_{y,z,t} - \eta_{y,z}^{inverter} \times \Theta^{dc}_{y,z,t} - \Theta^{ac}_{y,z,t}) \right) + \\ +&\sum_{y \in \mathcal{O}} \sum_{z \in \mathcal{Z}^{ESR}_{p}} \sum_{t \in \mathcal{T}} \left(\mu_{p,z}^{ESR} \times \omega_{t} \times (\Pi_{y,z,t} - \Theta_{y,z,t}) \right) \hspace{1 cm} \forall p \in \mathcal{P}^{ESR} \\ +\end{aligned}\]

The final two terms in the summation above adds roundtrip storage losses to the total demand to which the energy share obligation applies. This term is included in the constraint if the GenX setup parameter StorageLosses=1. If StorageLosses=0, this term is removed from the constraint. In practice, most existing renewable portfolio standard policies do not account for storage losses when determining energy share requirements. However, with 100% RPS or CES policies enacted in several jurisdictions, policy makers may wish to include storage losses in the minimum energy share, as otherwise there will be a difference between total generation and total demand that will permit continued use of non-qualifying resources (e.g. emitting generators).

source

Minimum Capacity Requirement

GenX.minimum_capacity_requirement!Function
minimum_capacity_requirement!(EP::Model, inputs::Dict, setup::Dict)

The minimum capacity requirement constraint allows for modeling minimum deployment of a certain technology or set of eligible technologies across the eligible model zones and can be used to mimic policies supporting specific technology build out (i.e. capacity deployment targets/mandates for storage, offshore wind, solar etc.). The default unit of the constraint is in MW. For each requirement $p \in \mathcal{P}^{MinCapReq}$, we model the policy with the following constraint.

\[\begin{aligned} +\sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z} } \left( \epsilon_{y,z,p}^{MinCapReq} \times \Delta^{\text{total}}_{y,z} \right) \geq REQ_{p}^{MinCapReq} \hspace{1 cm} \forall p \in \mathcal{P}^{MinCapReq} +\end{aligned}\]

Note that $\epsilon_{y,z,p}^{MinCapReq}$ is the eligiblity of a generator of technology $y$ in zone $z$ of requirement $p$ and will be equal to $1$ for eligible generators and will be zero for ineligible resources. The dual value of each minimum capacity constraint can be interpreted as the required payment (e.g. subsidy) per MW per year required to ensure adequate revenue for the qualifying resources.

Also note that co-located VRE and storage resources, there are three different components that minimum capacity requirements can be created for. The capacity of solar PV (in AC terms since the capacity is multiplied by the inverter efficiency), the capacity of wind, and the discharge capacity of storage (power to energy ratio times the energy capacity) can all have minimum capacity requirements.

source

Maximum Capacity Requirement

GenX.load_maximum_capacity_requirement!Method
load_maximum_capacity_requirement!(path::AbstractString, inputs::Dict, setup::Dict)

Read input parameters related to maximum capacity requirement constraints (e.g. technology specific deployment mandates)

source
GenX.maximum_capacity_requirement!Method
maximum_capacity_requirement!(EP::Model, inputs::Dict, setup::Dict)

The maximum capacity requirement constraint allows for modeling maximum deployment of a certain technology or set of eligible technologies across the eligible model zones and can be used to mimic policies supporting specific technology build out (i.e. capacity deployment targets/mandates for storage, offshore wind, solar etc.). The default unit of the constraint is in MW. For each requirement $p \in \mathcal{P}^{MaxCapReq}$, we model the policy with the following constraint.

\[\begin{aligned} +\sum_{y \in \mathcal{G} } \sum_{z \in \mathcal{Z} } \left( \epsilon_{y,z,p}^{MaxCapReq} \times \Delta^{\text{total}}_{y,z} \right) \leq REQ_{p}^{MaxCapReq} \hspace{1 cm} \forall p \in \mathcal{P}^{MaxCapReq} +\end{aligned}\]

Note that $\epsilon_{y,z,p}^{MaxCapReq}$ is the eligiblity of a generator of technology $y$ in zone $z$ of requirement $p$ and will be equal to $1$ for eligible generators and will be zero for ineligible resources. The dual value of each maximum capacity constraint can be interpreted as the required payment (e.g. subsidy) per MW per year required to ensure adequate revenue for the qualifying resources.

source
diff --git a/previews/PR670/Model_Reference/solve_model/index.html b/previews/PR670/Model_Reference/solve_model/index.html new file mode 100644 index 0000000000..3529ea216f --- /dev/null +++ b/previews/PR670/Model_Reference/solve_model/index.html @@ -0,0 +1,2 @@ + +Solving the Model · GenX.jl

Solving the Model

GenX.fix_integersMethod
fix_integers(jump_model::Model)

This function fixes the iteger variables ones the model has been solved in order to calculate approximations of dual variables.

Arguments

  • jump_model::Model: a model object containing that has been previously solved.

Returns

nothing (modifies an existing-solved model in the memory). solve() must be run again to solve and getdual veriables

source
GenX.solve_modelMethod
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
diff --git a/previews/PR670/Model_Reference/solver_configuration_api/index.html b/previews/PR670/Model_Reference/solver_configuration_api/index.html new file mode 100644 index 0000000000..3e94af30dd --- /dev/null +++ b/previews/PR670/Model_Reference/solver_configuration_api/index.html @@ -0,0 +1,321 @@ + +Solver Configurations · GenX.jl

Configuring the Solvers

GenX.configure_solverMethod
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.infer_solverMethod
infer_solver(optimizer::Any)

Return the name (String) of the solver to be used in the GenX.configure_solver method according to the solver imported by the user.

source
GenX.rename_keysMethod
rename_keys(attributes:Dict, new_key_names::Dict)

Renames the keys of the attributes dictionary based on old->new pairs in the newkeynames dictionary.

source

Configuring HiGHS

GenX.configure_highsMethod
configure_highs(solver_settings_path::String)

Reads user-specified solver settings from highs_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes HiGHS optimizer instance to be used in the GenX.generate_model() method.

The HiGHS optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided: All the references are in https://github.com/jump-dev/HiGHS.jl, https://github.com/ERGO-Code/HiGHS, and https://highs.dev/

# HiGHS Solver Parameters
+# Common solver settings
+Feasib_Tol: 1.0e-06        # Primal feasibility tolerance # [type: double, advanced: false, range: [1e-10, inf], default: 1e-07]
+Optimal_Tol: 1.0e-03       # Dual feasibility tolerance # [type: double, advanced: false, range: [1e-10, inf], default: 1e-07]
+TimeLimit: Inf             # Time limit # [type: double, advanced: false, range: [0, inf], default: inf]
+Pre_Solve: choose          # Presolve option: "off", "choose" or "on" # [type: string, advanced: false, default: "choose"]
+Method: ipm #choose        #HiGHS-specific solver settings # Solver option: "simplex", "choose" or "ipm" # [type: string, advanced: false, default: "choose"] In order to run a case when the UCommit is set to 1, i.e. MILP instance, set the Method to choose
+
+#HiGHS-specific solver settings
+# Parallel option: "off", "choose" or "on"
+# [type: string, advanced: false, default: "choose"]
+parallel: choose
+
+# Compute cost, bound, RHS and basic solution ranging: "off" or "on"
+# [type: string, advanced: false, default: "off"]
+ranging: off
+
+# Limit on cost coefficient: values larger than this will be treated as infinite
+# [type: double, advanced: false, range: [1e+15, inf], default: 1e+20]
+infinite_cost: 1e+20
+
+# Limit on |constraint bound|: values larger than this will be treated as infinite
+# [type: double, advanced: false, range: [1e+15, inf], default: 1e+20]
+infinite_bound: 1e+20
+
+# Lower limit on |matrix entries|: values smaller than this will be treated as zero
+# [type: double, advanced: false, range: [1e-12, inf], default: 1e-09]
+small_matrix_value: 1e-09
+
+# Upper limit on |matrix entries|: values larger than this will be treated as infinite
+# [type: double, advanced: false, range: [1, inf], default: 1e+15]
+large_matrix_value: 1e+15
+
+# IPM optimality tolerance
+# [type: double, advanced: false, range: [1e-12, inf], default: 1e-08]
+ipm_optimality_tolerance: 1e-08
+
+# Objective bound for termination
+# [type: double, advanced: false, range: [-inf, inf], default: inf]
+objective_bound: Inf
+
+# Objective target for termination
+# [type: double, advanced: false, range: [-inf, inf], default: -inf]
+objective_target: -Inf
+
+# random seed used in HiGHS
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 0]
+random_seed: 0
+
+# number of threads used by HiGHS (0: automatic)
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 0]
+threads: 0
+
+# Debugging level in HiGHS
+# [type: HighsInt, advanced: false, range: {0, 3}, default: 0]
+highs_debug_level: 0
+
+# Analysis level in HiGHS
+# [type: HighsInt, advanced: false, range: {0, 63}, default: 0]
+highs_analysis_level: 0
+
+# Strategy for simplex solver 0 => Choose; 1 => Dual (serial); 2 => Dual (PAMI); 3 => Dual (SIP); 4 => Primal
+# [type: HighsInt, advanced: false, range: {0, 4}, default: 1]
+simplex_strategy: 1
+
+# Simplex scaling strategy: off / choose / equilibration / forced equilibration / max value 0 / max value 1 (0/1/2/3/4/5)
+# [type: HighsInt, advanced: false, range: {0, 5}, default: 1]
+simplex_scale_strategy: 1
+
+# Strategy for simplex crash: off / LTSSF / Bixby (0/1/2)
+# [type: HighsInt, advanced: false, range: {0, 9}, default: 0]
+simplex_crash_strategy: 0
+
+# Strategy for simplex dual edge weights: Choose / Dantzig / Devex / Steepest Edge (-1/0/1/2)
+# [type: HighsInt, advanced: false, range: {-1, 2}, default: -1]
+simplex_dual_edge_weight_strategy: -1
+
+# Strategy for simplex primal edge weights: Choose / Dantzig / Devex / Steepest Edge (-1/0/1/2)
+# [type: HighsInt, advanced: false, range: {-1, 2}, default: -1]
+simplex_primal_edge_weight_strategy: -1
+
+# Iteration limit for simplex solver
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 2147483647]
+simplex_iteration_limit: 2147483647
+
+# Limit on the number of simplex UPDATE operations
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 5000]
+simplex_update_limit: 5000
+
+# Iteration limit for IPM solver
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 2147483647]
+ipm_iteration_limit: 2147483647
+
+# Minimum level of concurrency in parallel simplex
+# [type: HighsInt, advanced: false, range: {1, 8}, default: 1]
+simplex_min_concurrency: 1
+
+# Maximum level of concurrency in parallel simplex
+# [type: HighsInt, advanced: false, range: {1, 8}, default: 8]
+simplex_max_concurrency: 8
+
+# Enables or disables solver output
+# [type: bool, advanced: false, range: {false, true}, default: true]
+output_flag: true
+
+# Enables or disables console logging
+# [type: bool, advanced: false, range: {false, true}, default: true]
+log_to_console: true
+
+# Solution file
+# [type: string, advanced: false, default: ""]
+solution_file: ""
+
+# Log file
+# [type: string, advanced: false, default: ""]
+log_file: ""
+
+# Write the primal and dual solution to a file
+# [type: bool, advanced: false, range: {false, true}, default: false]
+write_solution_to_file: false
+
+# Write the solution in style: 0=>Raw (computer-readable); 1=>Pretty (human-readable) 
+# [type: HighsInt, advanced: false, range: {0, 2}, default: 0]
+write_solution_style: 0
+
+# Write model file
+# [type: string, advanced: false, default: ""]
+write_model_file: ""
+
+# Write the model to a file
+# [type: bool, advanced: false, range: {false, true}, default: false]
+write_model_to_file: false
+
+# Whether symmetry should be detected
+# [type: bool, advanced: false, range: {false, true}, default: true]
+mip_detect_symmetry: true
+
+# MIP solver max number of nodes
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 2147483647]
+mip_max_nodes: 2147483647
+
+# MIP solver max number of nodes where estimate is above cutoff bound
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 2147483647]
+mip_max_stall_nodes: 2147483647
+
+# MIP solver max number of leave nodes
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 2147483647]
+mip_max_leaves: 2147483647
+
+# limit on the number of improving solutions found to stop the MIP solver prematurely
+# [type: HighsInt, advanced: false, range: {1, 2147483647}, default: 2147483647]
+mip_max_improving_sols: 2147483647
+
+# maximal age of dynamic LP rows before they are removed from the LP relaxation
+# [type: HighsInt, advanced: false, range: {0, 32767}, default: 10]
+mip_lp_age_limit: 10
+
+# maximal age of rows in the cutpool before they are deleted
+# [type: HighsInt, advanced: false, range: {0, 1000}, default: 30]
+mip_pool_age_limit: 30
+
+# soft limit on the number of rows in the cutpool for dynamic age adjustment
+# [type: HighsInt, advanced: false, range: {1, 2147483647}, default: 10000]
+mip_pool_soft_limit: 10000
+
+# minimal number of observations before pseudo costs are considered reliable
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 8]
+mip_pscost_minreliable: 8
+
+# minimal number of entries in the cliquetable before neighborhood queries of the conflict graph use parallel processing
+# [type: HighsInt, advanced: false, range: {0, 2147483647}, default: 100000]
+mip_min_cliquetable_entries_for_parallelism: 100000
+
+# MIP solver reporting level
+# [type: HighsInt, advanced: false, range: {0, 2}, default: 1]
+mip_report_level: 1
+
+# MIP feasibility tolerance
+# [type: double, advanced: false, range: [1e-10, inf], default: 1e-06]
+mip_feasibility_tolerance: 1e-06
+
+# effort spent for MIP heuristics
+# [type: double, advanced: false, range: [0, 1], default: 0.05]
+mip_heuristic_effort: 0.05
+
+# tolerance on relative gap, |ub-lb|/|ub|, to determine whether optimality has been reached for a MIP instance
+# [type: double, advanced: false, range: [0, inf], default: 0.0001]
+mip_rel_gap: 0.0001
+
+# tolerance on absolute gap of MIP, |ub-lb|, to determine whether optimality has been reached for a MIP instance
+# [type: double, advanced: false, range: [0, inf], default: 1e-06]
+mip_abs_gap: 1e-06
+
+# Output development messages: 0 => none; 1 => info; 2 => verbose
+# [type: HighsInt, advanced: true, range: {0, 3}, default: 0]
+log_dev_level: 0
+
+# Run the crossover routine for IPX
+# [type: string, advanced: "on", range: {"off", "on"}, default: "off"]
+run_crossover: "off"
+
+# Allow ModelStatus::kUnboundedOrInfeasible
+# [type: bool, advanced: true, range: {false, true}, default: false]
+allow_unbounded_or_infeasible: false
+
+# Use relaxed implied bounds from presolve
+# [type: bool, advanced: true, range: {false, true}, default: false]
+use_implied_bounds_from_presolve: false
+
+# Prevents LP presolve steps for which postsolve cannot maintain a basis
+# [type: bool, advanced: true, range: {false, true}, default: true]
+lp_presolve_requires_basis_postsolve: true
+
+# Use the free format MPS file reader
+# [type: bool, advanced: true, range: {false, true}, default: true]
+mps_parser_type_free: true
+
+# For multiple N-rows in MPS files: delete rows / delete entries / keep rows (-1/0/1)
+# [type: HighsInt, advanced: true, range: {-1, 1}, default: -1]
+keep_n_rows: -1
+
+# Scaling factor for costs
+# [type: HighsInt, advanced: true, range: {-20, 20}, default: 0]
+cost_scale_factor: 0
+
+# Largest power-of-two factor permitted when scaling the constraint matrix
+# [type: HighsInt, advanced: true, range: {0, 30}, default: 20]
+allowed_matrix_scale_factor: 20
+
+# Largest power-of-two factor permitted when scaling the costs
+# [type: HighsInt, advanced: true, range: {0, 20}, default: 0]
+allowed_cost_scale_factor: 0
+
+# Strategy for permuting before simplex
+# [type: HighsInt, advanced: true, range: {-1, 1}, default: -1]
+simplex_permute_strategy: -1
+
+# Max level of dual simplex cleanup
+# [type: HighsInt, advanced: true, range: {0, 2147483647}, default: 1]
+max_dual_simplex_cleanup_level: 1
+
+# Max level of dual simplex phase 1 cleanup
+# [type: HighsInt, advanced: true, range: {0, 2147483647}, default: 2]
+max_dual_simplex_phase1_cleanup_level: 2
+
+# Strategy for PRICE in simplex
+# [type: HighsInt, advanced: true, range: {0, 3}, default: 3]
+simplex_price_strategy: 3
+
+Strategy for solving unscaled LP in simplex
+[type: HighsInt, advanced: true, range: {0, 2}, default: 1]
+simplex_unscaled_solution_strategy: 1
+
+Perform initial basis condition check in simplex
+[type: bool, advanced: true, range: {false, true}, default: true]
+simplex_initial_condition_check: true
+
+No unnecessary refactorization on simplex rebuild
+[type: bool, advanced: true, range: {false, true}, default: true]
+no_unnecessary_rebuild_refactor: true
+
+Tolerance on initial basis condition in simplex
+[type: double, advanced: true, range: [1, inf], default: 1e+14]
+simplex_initial_condition_tolerance: 1e+14
+
+Tolerance on solution error when considering refactorization on simplex rebuild
+[type: double, advanced: true, range: [-inf, inf], default: 1e-08]
+rebuild_refactor_solution_error_tolerance: 1e-08
+
+Tolerance on dual steepest edge weight errors
+[type: double, advanced: true, range: [0, inf], default: inf]
+dual_steepest_edge_weight_error_tolerance: Inf
+
+Threshold on dual steepest edge weight errors for Devex switch
+[type: double, advanced: true, range: [1, inf], default: 10]
+dual_steepest_edge_weight_log_error_threshold: 10.0
+
+Dual simplex cost perturbation multiplier: 0 => no perturbation
+[type: double, advanced: true, range: [0, inf], default: 1]
+dual_simplex_cost_perturbation_multiplier: 1.0
+
+Primal simplex bound perturbation multiplier: 0 => no perturbation
+[type: double, advanced: true, range: [0, inf], default: 1]
+primal_simplex_bound_perturbation_multiplier: 1.0
+
+Dual simplex pivot growth tolerance
+[type: double, advanced: true, range: [1e-12, inf], default: 1e-09]
+dual_simplex_pivot_growth_tolerance: 1e-09
+
+Matrix factorization pivot threshold for substitutions in presolve
+[type: double, advanced: true, range: [0.0008, 0.5], default: 0.01]
+presolve_pivot_threshold: 0.01
+
+Maximal fillin allowed for substitutions in presolve
+[type: HighsInt, advanced: true, range: {0, 2147483647}, default: 10]
+presolve_substitution_maxfillin: 10
+
+Matrix factorization pivot threshold
+[type: double, advanced: true, range: [0.0008, 0.5], default: 0.1]
+factor_pivot_threshold: 0.1
+
+Matrix factorization pivot tolerance
+[type: double, advanced: true, range: [0, 1], default: 1e-10]
+factor_pivot_tolerance: 1e-10
+
+Tolerance to be satisfied before IPM crossover will start
+[type: double, advanced: true, range: [1e-12, inf], default: 1e-08]
+start_crossover_tolerance: 1e-08
+
+Use original HFactor logic for sparse vs hyper-sparse TRANs
+[type: bool, advanced: true, range: {false, true}, default: true]
+use_original_HFactor_logic: true
+
+Check whether LP is candidate for LiDSE
+[type: bool, advanced: true, range: {false, true}, default: true]
+less_infeasible_DSE_check: true
+
+Use LiDSE if LP has right properties
+[type: bool, advanced: true, range: {false, true}, default: true]
+less_infeasible_DSE_choose_row: true
source

Configuring Gurobi

GenX.configure_gurobiMethod
configure_gurobi(solver_settings_path::String)

Reads user-specified solver settings from gurobi_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes Gurobi optimizer instance to be used in the GenX.generate_model() method.

The Gurobi optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

  • FeasibilityTol = 1e-6 (Constraint (primal) feasibility tolerances. See https://www.gurobi.com/documentation/8.1/refman/feasibilitytol.html)
  • OptimalityTol = 1e-4 (Dual feasibility tolerances. See https://www.gurobi.com/documentation/8.1/refman/optimalitytol.html#parameter:OptimalityTol)
  • Presolve = -1 (Controls presolve level. See https://www.gurobi.com/documentation/8.1/refman/presolve.html)
  • AggFill = -1 (Allowed fill during presolve aggregation. See https://www.gurobi.com/documentation/8.1/refman/aggfill.html#parameter:AggFill)
  • PreDual = -1 (Presolve dualization. See https://www.gurobi.com/documentation/8.1/refman/predual.html#parameter:PreDual)
  • TimeLimit = Inf (Limits total time solver. See https://www.gurobi.com/documentation/8.1/refman/timelimit.html)
  • MIPGap = 1e-4 (Relative (p.u. of optimal) mixed integer optimality tolerance for MIP problems (ignored otherwise). See https://www.gurobi.com/documentation/8.1/refman/mipgap2.html)
  • Crossover = -1 (Barrier crossver strategy. See https://www.gurobi.com/documentation/8.1/refman/crossover.html#parameter:Crossover)
  • Method = -1 (Algorithm used to solve continuous models (including MIP root relaxation). See https://www.gurobi.com/documentation/8.1/refman/method.html)
  • BarConvTol = 1e-8 (Barrier convergence tolerance (determines when barrier terminates). See https://www.gurobi.com/documentation/8.1/refman/barconvtol.html)
  • NumericFocus = 0 (Numerical precision emphasis. See https://www.gurobi.com/documentation/8.1/refman/numericfocus.html)
source

Configuring CPLEX

GenX.configure_cplexMethod
configure_cplex(solver_settings_path::String)

Reads user-specified solver settings from cplex_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes CPLEX optimizer instance.

The optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

  • Feasib_Tol,

    sets CPX_PARAM_EPRHS. Control the primal feasibility tolerance. Default is 1e-6.

  • Optimal_Tol,

    sets CPX_PARAM_EPOPT. Control the optimality tolerance. Default is 1e-4.

  • AggFill,

    sets CPX_PARAM_AGGFILL. Control the allowed fill during presolve aggregation. Default is 10.

  • PreDual,

    sets CPX_PARAM_PREDUAL. Decides whether presolve should pass the primal or dual linear programming problem to the LP optimization algorithm. Default is 0.

  • TimeLimit,

    sets CPX_PARAM_TILIM. Limits total solver time. Default is 1e+75.

  • MIPGap,

    sets CPX_PARAM_EPGAP Relative (p.u. of optimal) mixed integer optimality tolerance for MIP problems (ignored otherwise). Default is 1e-3.

  • Method,

    sets CPX_PARAM_LPMETHOD. Algorithm used to solve continuous models (including MIP root relaxation) Default is 0.

  • BarConvTol,

    sets CPX_PARAM_BAREPCOMP. Barrier convergence tolerance (determines when barrier terminates). Default is 1e-8.

  • NumericFocus,

    sets CPX_PARAM_NUMERICALEMPHASIS. Numerical precision emphasis. Default is 0.

  • BarObjRng,

    sets CPX_PARAM_BAROBJRNG. The maximum absolute value of the objective function. Default is 1e+75.

  • SolutionType,

    sets CPX_PARAM_SOLUTIONTYPE. Solution type for LP or QP. Default is 2.

The optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

Any other attributes in the settings file (which typically start with CPX_PARAM_) will also be passed to the solver.

source

Configuring Clp

GenX.configure_clpMethod
configure_clp(solver_settings_path::String)

Reads user-specified solver settings from clp_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes Clp optimizer instance to be used in the GenX.generate_model() method.

The Clp optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

  • PrimalTolerance = 1e-7 (Primal feasibility tolerance)
  • DualTolerance = 1e-7 (Dual feasibility tolerance)
  • DualObjectiveLimit = 1e308 (When using dual simplex (where the objective is monotonically changing), terminate when the objective exceeds this limit)
  • MaximumIterations = 2147483647 (Terminate after performing this number of simplex iterations)
  • MaximumSeconds = -1.0 (Terminate after this many seconds have passed. A negative value means no time limit)
  • LogLevel = 1 (Set to 1, 2, 3, or 4 for increasing output. Set to 0 to disable output)
  • PresolveType = 0 (Set to 1 to disable presolve)
  • SolveType = 5 (Solution method: dual simplex (0), primal simplex (1), sprint (2), barrier with crossover (3), barrier without crossover (4), automatic (5))
  • InfeasibleReturn = 0 (Set to 1 to return as soon as the problem is found to be infeasible (by default, an infeasibility proof is computed as well))
  • Scaling = 3 (0 0ff, 1 equilibrium, 2 geometric, 3 auto, 4 dynamic (later))
  • Perturbation = 100 (switch on perturbation (50), automatic (100), don't try perturbing (102))
source

Configuring Cbc

GenX.configure_cbcMethod
configure_cbc(solver_settings_path::String)

Reads user-specified solver settings from cbc_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes Cbc optimizer instance to be used in the GenX.generate_model() method.

The Cbc optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

  • seconds = 1e-6
  • logLevel = 1e-6
  • maxSolutions = -1
  • maxNodes = -1
  • allowableGap = -1
  • ratioGap = Inf
  • threads = 1
source

Configuring SCIP

GenX.configure_scipMethod
configure_scip(solver_settings_path::String)

Reads user-specified solver settings from scip_settings.yml in the directory specified by the string solver_settings_path.

Returns a MathOptInterface.OptimizerWithAttributes SCIP optimizer instance to be used in the GenX.generate_model() method.

The SCIP optimizer instance is configured with the following default parameters if a user-specified parameter for each respective field is not provided:

  • Dispverblevel = 0
  • limitsgap = 0.05
source
diff --git a/previews/PR670/Model_Reference/utility_functions/index.html b/previews/PR670/Model_Reference/utility_functions/index.html new file mode 100644 index 0000000000..c264f339e8 --- /dev/null +++ b/previews/PR670/Model_Reference/utility_functions/index.html @@ -0,0 +1,4 @@ + +Utility Functions · GenX.jl

Utility Functions

GenX.by_rid_resMethod
by_rid_res(rid::Integer, sym::Symbol, rs::Vector{<:AbstractResource})
+
+This function returns the value of the attribute `sym` for the resource given by the ID `rid`.
source
GenX.hoursafterMethod
hoursafter(p::Int, t::Int, a::Int)

Determines the time index a hours after index t in a landscape starting from t=1 which is separated into distinct periods of length p.

For example, if p = 10, 1 hour after t=9 is t=10, 1 hour after t=10 is t=1, 1 hour after t=11 is t=2

source
GenX.hoursafterMethod
hoursafter(p::Int, t::Int, b::UnitRange)

This is a generalization of hoursafter(... b::Int) to allow for example a=1:3 to fetch a Vector{Int} of the three hours after time index t.

source
GenX.hoursbeforeMethod
hoursbefore(p::Int, t::Int, b::Int)

Determines the time index b hours before index t in a landscape starting from t=1 which is separated into distinct periods of length p.

For example, if p = 10, 1 hour before t=1 is t=10, 1 hour before t=10 is t=9 1 hour before t=11 is t=20

source
GenX.hoursbeforeMethod
hoursbefore(p::Int, t::Int, b::UnitRange)

This is a generalization of hoursbefore(... b::Int) to allow for example b=1:3 to fetch a Vector{Int} of the three hours before time index t.

source
GenX.is_nonzeroMethod
is_nonzero(df::DataFrame, col::Symbol)::BitVector

This function checks if a column in a dataframe is all zeros.

source
diff --git a/previews/PR670/Model_Reference/write_outputs/index.html b/previews/PR670/Model_Reference/write_outputs/index.html new file mode 100644 index 0000000000..724f6eb042 --- /dev/null +++ b/previews/PR670/Model_Reference/write_outputs/index.html @@ -0,0 +1,6 @@ + +Outputs Functions · GenX.jl

Functions for Writing the Different Results/Outputs to Separate Files

GenX.write_annualMethod
write_annual(fullpath::AbstractString, dfOut::DataFrame)

Internal function for writing annual outputs.

source
GenX.write_fulltimeseriesMethod
write_fulltimeseries(fullpath::AbstractString, dataOut::Matrix{Float64}, dfOut::DataFrame)

Internal function for writing full time series outputs. This function wraps the instructions for creating the full time series output files.

source
GenX.write_outputsMethod
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.choose_output_dirMethod
path = choose_output_dir(pathinit)

Avoid overwriting (potentially important) existing results by appending to the directory name

Checks if the suggested output directory already exists. While yes, it appends _1, _2, etc till an unused name is found

source
GenX.dftransposeMethod

df = dftranspose(df::DataFrame, withhead::Bool)

Returns a transpose of a Dataframe.

FIXME: This is for DataFrames@0.20.2, as used in GenX. Versions 0.21+ could use stack and unstack to make further changes while retaining the order

source

Write Status

GenX.write_statusMethod
write_status(path::AbstractString, inputs::Dict, EP::Model)

Function for writing the final solve status of the optimization problem solved.

source

Write CO_2 Cap

GenX.write_co2_capFunction
write_co2_cap(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting carbon price associated with carbon cap constraints.

source

Write Costs

GenX.write_costsMethod
write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the costs pertaining to the objective function (fixed, variable O&M etc.).

source

Write Fuel Consumption

GenX.write_fuel_consumptionFunction
write_fuel_consumption(path::AbstractString, inputs::Dict, setup::Dict, EP::Model).

Write fuel consumption of each power plant.

source

Write Emissions

GenX.write_emissionsMethod
write_emissions(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting time-dependent CO$_2$ emissions by zone.

source

Write Capacities

GenX.write_capacityMethod
write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Model))

Function for writing the diferent capacities for the different generation technologies (starting capacities or, existing capacities, retired capacities, and new-built capacities).

source

Write Capacity Value # TODO: add it

GenX.capacity_reserve_margin_priceMethod
capacity_reserve_margin_price(EP::Model,
+                              inputs::Dict,
+                              setup::Dict,
+                              capres_zone::Int)::Vector{Float64}

Marginal electricity price for each model zone and time step. This is equal to the dual variable of the power balance constraint. When solving a linear program (i.e. linearized unit commitment or economic dispatch) this output is always available; when solving a mixed integer linear program, this can be calculated only if WriteShadowPrices is activated.

Returns a vector, with units of $/MW
source

Write Capacity Factors

GenX.write_capacityfactorMethod
write_capacityfactor(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the capacity factor of different resources. For co-located VRE-storage resources, this value is calculated if the site has either or both a solar PV or wind resource.

source

Write Charge Values

GenX.write_chargeMethod
write_charge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the charging energy values of the different storage technologies.

source

Write Non-served-energy

GenX.write_nseMethod
write_nse(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting non-served energy for every model zone, time step and cost-segment.

source

Write Storage State of Charge

GenX.write_storageMethod
write_storage(path::AbstractString, inputs::Dict,setup::Dict, EP::Model)

Function for writing the capacities of different storage technologies, including hydro reservoir, flexible storage tech etc.

source

Write Storage Dual

GenX.write_storagedualMethod
write_storagedual(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting dual of storage level (state of charge) balance of each resource in each time step.

source

Write Power

GenX.write_powerMethod
write_power(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the different values of power generated by the different technologies in operation.

source

Write Curtailment

GenX.write_curtailmentMethod
write_curtailment(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the curtailment values of the different variable renewable resources (both standalone and co-located).

source

Write Prices

GenX.locational_marginal_priceMethod
locational_marginal_price(EP::Model, inputs::Dict, setup::Dict)

Marginal electricity price for each model zone and time step. This is equal to the dual variable of the power balance constraint. When solving a linear program (i.e. linearized unit commitment or economic dispatch) this output is always available; when solving a mixed integer linear program, this can be calculated only if WriteShadowPrices is activated.

Returns a matrix of size (T, Z).
+Values have units of $/MWh
source
GenX.write_priceMethod
write_price(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting marginal electricity price for each model zone and time step. Marginal electricity price is equal to the dual variable of the power balance constraint. If GenX is configured as a mixed integer linear program, then this output is only generated if WriteShadowPrices flag is activated. If configured as a linear program (i.e. linearized unit commitment or economic dispatch) then output automatically available.

source

Write Reliability

GenX.write_reliabilityMethod
write_reliability(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting dual variable of maximum non-served energy constraint (shadow price of reliability constraint) for each model zone and time step.

source

Write Energy Revenue

GenX.write_energy_revenueMethod
write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing energy revenue from the different generation technologies.

source

Write Subsidy Revenue

GenX.write_subsidy_revenueMethod
write_subsidy_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting subsidy revenue earned if a generator specified Min_Cap is provided in the input file, or if a generator is subject to a Minimum Capacity Requirement constraint. The unit is $.

source

Write Operating Reserve and Regulation Revenue

GenX.write_operating_reserve_regulation_revenueMethod
write_operating_reserve_regulation_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting the operating reserve and regulation revenue earned by generators listed in the input file. GenX will print this file only when operating reserve and regulation are modeled and the shadow price can be obtained from the solver. The revenues are calculated as the operating reserve and regulation contributions in each time step multiplied by the corresponding shadow price, and then the sum is taken over all modeled time steps. The last column is the total revenue received from all operating reserve and regulation constraints. As a reminder, GenX models the operating reserve and regulation at the time-dependent level, and each constraint either stands for an overall market or a locality constraint.

source

Write Capacity Revenue

GenX.write_reserve_margin_revenueMethod
write_reserve_margin_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting the capacity revenue earned by each generator listed in the input file. GenX will print this file only when capacity reserve margin is modeled and the shadow price can be obtained form the solver. Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each capacity reserve margin constraint. The revenue is calculated as the capacity contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. The last column is the total revenue received from all capacity reserve margin constraints. As a reminder, GenX models the capacity reserve margin (aka capacity market) at the time-dependent level, and each constraint either stands for an overall market or a locality constraint.

source

Write Energy Share Requirement Revenue

GenX.write_esr_revenueMethod
write_esr_revenue(path::AbstractString, inputs::Dict, setup::Dict, dfPower::DataFrame, dfESR::DataFrame, EP::Model)

Function for reporting the renewable/clean credit revenue earned by each generator listed in the input file. GenX will print this file only when RPS/CES is modeled and the shadow price can be obtained form the solver. Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue earned from each RPS constraint. The revenue is calculated as the total annual generation (if elgible for the corresponding constraint) multiplied by the RPS/CES price. The last column is the total revenue received from all constraint. The unit is $.

source

Write Net Revenue

GenX.write_net_revenueMethod
write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpRsvRevenue::DataFrame)

Function for writing net revenue of different generation technologies.

source

Write Co-Located VRE and Storage files

GenX.write_vre_storFunction
write_vre_stor(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage specific files.

source
GenX.write_vre_stor_capacityFunction
write_vre_stor_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage capacities.

source
GenX.write_vre_stor_chargeFunction
write_vre_stor_charge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage charging decision variables/expressions.

source
GenX.write_vre_stor_dischargeFunction
write_vre_stor_discharge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for writing the vre-storage discharging decision variables/expressions.

source

Write Multi-stage files

GenX.write_multi_stage_costsFunction
write_multi_stage_costs(outpath::String, settings_d::Dict)

This function writes the file costs_multi_stage.csv to the Results directory. This file contains variable, fixed, startup, network expansion, unmet reserve, and non-served energy costs discounted to year zero.

inputs:

  • outpath – String which represents the path to the Results directory.
  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
source
GenX.write_multi_stage_statsFunction

writemultistagestats(outpath::String, statsd::Dict)

This function writes the file stats_multi_stage.csv. to the Results directory. This file contains the runtime, upper bound, lower bound, and relative optimality gap for each iteration of the DDP algorithm.

inputs:

  • outpath – String which represents the path to the Results directory.
  • stats_d – Dictionary which contains the run time, upper bound, and lower bound of each DDP iteration.
source
GenX.write_multi_stage_settingsFunction
write_multi_stage_settings(outpath::AbstractString, settings_d::Dict)

Function for writing the multi-stage settings file to the output path for future reference.

source
GenX.write_multi_stage_network_expansionFunction
write_multi_stage_network_expansion(outpath::String, settings_d::Dict)

This function writes the file network_expansion_multi_stage.csv to the Results directory. This file contains new transmission capacities for each modeled transmission line for the first and all subsequent model stages.

inputs:

  • outpath – String which represents the path to the Results directory.
  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
source
GenX.write_multi_stage_capacities_chargeFunction
write_multi_stage_capacities_charge(outpath::String, settings_d::Dict)

This function writes the file capacities_charge_multi_stage.csv to the Results directory. This file contains starting resource charge capacities from the first model stage and end resource charge capacities for the first and all subsequent model stages.

inputs:

  • outpath – String which represents the path to the Results directory.
  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
source
GenX.write_multi_stage_capacities_energyFunction
write_multi_stage_capacities_energy(outpath::String, settings_d::Dict)

This function writes the file capacities_energy_multi_stage.csv to the Results directory. This file contains starting resource energy capacities from the first model stage and end resource energy capacities for the first and all subsequent model stages.

inputs:

  • outpath – String which represents the path to the Results directory.
  • settings_d - Dictionary containing settings dictionary configured in the multi-stage settings file multi_stage_settings.yml.
source

Write maintenance files

Write DCOPF files

GenX.write_anglesFunction
write_angles(path::AbstractString, inputs::Dict, setup::Dict, EP::Model)

Function for reporting the bus angles for each model zone and time step if the DC_OPF flag is activated

source

Write Settings files

diff --git a/previews/PR670/Public_API/public_api/index.html b/previews/PR670/Public_API/public_api/index.html new file mode 100644 index 0000000000..a1ba85b71e --- /dev/null +++ b/previews/PR670/Public_API/public_api/index.html @@ -0,0 +1,28 @@ + +Public API · GenX.jl

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:

\[\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_{zr} = \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_{zr}$ is a random objective fucntion coefficient betwen $[0,100]$ 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 above, $\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_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)

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

inputs:

  • 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.
source
diff --git a/previews/PR670/Tutorials/Tutorial_1_configuring_settings/index.html b/previews/PR670/Tutorials/Tutorial_1_configuring_settings/index.html new file mode 100644 index 0000000000..a3f361a3a5 --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_1_configuring_settings/index.html @@ -0,0 +1,268 @@ + +Tutorial 1: Configuring Settings · GenX.jl

Tutorial 1: Configuring Settings

Interactive Notebook of the tutorial

GenX is easy to customize to fit a variety of problems. In this tutorial, we show which settings are available to change, what their defaults are, and how to change them in your code.

What settings are there?

There are 21 settings available to edit in GenX, found in the file genx_settings.yml. These settings are described at the Model settings parameters page of the documentation. The file is located in the settings folder in the working directory. To change the location of the file, edit the settings_path variable in Run.jl within your directory.

Most settings are set as either 0 or 1, which correspond to whether or not to include a specifc feature. For example, to use TimeDomainReduction, you would set its parameter to 0 within genx_settings.yml. If you would like to run GenX without it, you would set its parameter to 1.

Other settings, such as CO2Cap, have more options corresponding to integers, while some settings such as ModelingtoGenerateAlternativeSlack take a numerical input directly (in this case, the slack value). Two settings, Solver and TimeDomainReductionFolder take in text as input. To learn more about different solvers, read here. For TimeDomainReductionFolder, specify the name of the directory you wish to see the results in. For a more comprehensive description of the input options, see the documentation linked above.

To see how changing the settings affects the outputs, see Tutorials 3 and 7.

Below is the settings file for example_systems/1_three_zones:

All genx_settings.yml files in Example_Systems specify most parameters. When configuring your own settings, however, it is not necessary to input all parameters as defaults are specified for each one in configure_settings.jl.

To open genx_settings.yml in Jupyter, use the function YAML.load(open(...)) and navigate to file in the desired directory:

using YAML
+genx_settings_SNE = YAML.load(open("example_systems/1_three_zones/settings/genx_settings.yml"))
    Dict{Any, Any} with 19 entries:
+      "NetworkExpansion"                        => 1
+      "ModelingToGenerateAlternativeIterations" => 3
+      "ParameterScale"                          => 1
+      "EnergyShareRequirement"                  => 0
+      "PrintModel"                              => 0
+      "TimeDomainReduction"                     => 1
+      "Trans_Loss_Segments"                     => 1
+      "CapacityReserveMargin"                   => 0
+      "ModelingtoGenerateAlternativeSlack"      => 0.1
+      "MethodofMorris"                          => 0
+      "StorageLosses"                           => 1
+      "MultiStage"                              => 0
+      "OverwriteResults"                        => 0
+      "UCommit"                                 => 2
+      "ModelingToGenerateAlternatives"          => 0
+      "MaxCapReq"                               => 0
+      "MinCapReq"                               => 1
+      "CO2Cap"                                  => 2
+      "WriteShadowPrices"                       => 1

Since all settings have defaults, you only need to specify the settings you would like to change. In fact, you can leave your settings file completely blank and it will still run! Let's try editing genx_settings in example_systems/1_three_zones to contain no parameters:

new_params = Dict() # Empty dictionary
+YAML.write_file("example_systems/1_three_zones/settings/genx_settings.yml", new_params)

The empty file will look like this:

Now, we run GenX and output the file capacity.csv from the Results folder. To do this, we use the function include, which takes a .jl file and runs it in jupyter notebook:

include("example_systems/1_three_zones/Run.jl")
    Configuring Settings
+    Configuring Solver
+    Loading Inputs
+    Reading Input CSV Files
+    Network.csv Successfully Read!
+    Demand (load) data Successfully Read!
+    Fuels_data.csv Successfully Read!
+
+
+    [ Info: Thermal.csv Successfully Read.
+    [ Info: Vre.csv Successfully Read.
+    [ Info: Storage.csv Successfully Read.
+    [ Info: Resource_minimum_capacity_requirement.csv Successfully Read.
+
+    Summary of resources loaded into the model:
+    -------------------------------------------------------
+    	Resource type 		Number of resources
+    =======================================================
+    	Thermal        		3
+    	VRE            		4
+    	Storage        		3
+    =======================================================
+    Total number of resources: 10
+    -------------------------------------------------------
+    Generators_variability.csv Successfully Read!
+    Validating time basis
+    CSV Files Successfully Read In From GenX-Tutorials/Tutorials/example_systems/1_three_zones
+    Generating the Optimization Model
+    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Fuel Module
+    CO2 Module
+    Investment Transmission Module
+    Transmission Module
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (No Unit Commitment) Resources Module
+    Time elapsed for model building is
+    5.677411542
+    Solving Model
+    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Presolving model
+    402430 rows, 306077 cols, 1216044 nonzeros
+    334406 rows, 273093 cols, 1179498 nonzeros
+    Presolve : Reductions: rows 334406(-208720); columns 273093(-217481); elements 1179498(-265378)
+    Solving the presolved LP
+    IPX model has 334406 rows, 273093 columns and 1179498 nonzeros
+    Input
+        Number of variables:                                273093
+        Number of free variables:                           0
+        Number of constraints:                              334406
+        Number of equality constraints:                     54616
+        Number of matrix entries:                           1179498
+        Matrix range:                                       [4e-07, 1e+01]
+        RHS range:                                          [7e+02, 2e+04]
+        Objective range:                                    [1e-01, 1e+05]
+        Bounds range:                                       [2e+00, 2e+04]
+    Preprocessing
+        Dualized model:                                     no
+        Number of dense columns:                            13
+        Range of scaling factors:                           [5.00e-01, 1.00e+00]
+    IPX version 1.0
+    Interior Point Solve
+     Iter     P.res    D.res            P.obj           D.obj        mu     Time
+       0   7.92e+03 4.68e+04   3.32147673e+12 -5.29874805e+12  3.77e+08       0s
+       1   2.52e+03 9.09e+03  -3.25877734e+12 -7.21381041e+12  1.13e+08       0s
+       2   4.85e+02 2.35e+03  -3.11396725e+11 -5.19621090e+12  2.79e+07       1s
+       3   3.29e+02 3.98e+02  -1.38395149e+11 -1.60011105e+12  1.06e+07       4s
+     Constructing starting basis...
+       4   2.79e+02 2.18e+02  -9.20089553e+10 -1.12770108e+12  6.86e+06      21s
+       5   9.53e+00 7.39e+01   9.91668627e+10 -5.58589863e+11  1.62e+06      24s
+       6   3.40e+00 1.42e+01   5.89999652e+10 -1.16297421e+11  3.82e+05      34s
+       7   9.22e-01 2.82e+00   3.17189476e+10 -3.28274389e+10  1.18e+05      39s
+       8   3.21e-01 9.06e-01   1.54947223e+10 -9.43126058e+09  4.16e+04      44s
+       9   1.43e-01 7.07e-02   1.25444514e+10 -2.07076014e+09  2.26e+04      50s
+      10   7.24e-02 2.69e-02   9.56770103e+09  6.98368943e+08  1.36e+04      59s
+      11   5.85e-02 2.34e-02   9.04050442e+09  9.72471748e+08  1.23e+04      76s
+      12   4.07e-02 1.86e-02   8.41352551e+09  1.36107427e+09  1.07e+04      91s
+      13   2.13e-02 1.04e-02   7.27813663e+09  2.37139743e+09  7.40e+03     106s
+      14   1.23e-02 9.58e-03   6.85995182e+09  2.42302634e+09  6.65e+03     121s
+      15   6.29e-03 8.69e-03   6.30996161e+09  2.53750169e+09  5.63e+03     137s
+      16   5.05e-03 4.32e-03   6.20375634e+09  3.06005287e+09  4.69e+03     153s
+      17   3.10e-03 2.90e-03   5.81685189e+09  3.42321619e+09  3.56e+03     171s
+      18   2.46e-03 1.90e-03   5.69010358e+09  3.69724506e+09  2.97e+03     193s
+      19   2.17e-03 1.40e-03   5.64332584e+09  3.92063823e+09  2.56e+03     211s
+      20   1.68e-03 1.08e-03   5.58574539e+09  4.00019716e+09  2.36e+03     225s
+      21   1.67e-03 1.03e-03   5.58471864e+09  3.99434084e+09  2.36e+03     239s
+      22   1.62e-03 7.41e-04   5.57432205e+09  4.11610384e+09  2.17e+03     253s
+      23   1.30e-03 5.69e-04   5.49997107e+09  4.19795422e+09  1.94e+03     271s
+      24   1.30e-03 5.50e-04   5.49974396e+09  4.19969138e+09  1.93e+03     290s
+      25   1.02e-03 2.93e-04   5.41913926e+09  4.35952658e+09  1.57e+03     303s
+      26   7.07e-04 2.24e-04   5.30762980e+09  4.42283116e+09  1.31e+03     348s
+      27   6.98e-04 2.04e-04   5.30566881e+09  4.42740669e+09  1.30e+03     368s
+      28   5.75e-04 1.34e-04   5.25404059e+09  4.51834486e+09  1.09e+03     384s
+      29   5.11e-04 5.18e-05   5.23316215e+09  4.57449214e+09  9.78e+02     422s
+      30   3.06e-04 3.13e-05   5.11136554e+09  4.65641695e+09  6.75e+02     463s
+      31   2.48e-04 2.50e-05   5.07790558e+09  4.68190888e+09  5.88e+02     556s
+      32   2.26e-04 1.93e-05   5.07281283e+09  4.69130536e+09  5.66e+02     578s
+      33   1.57e-04 9.24e-06   5.02737766e+09  4.76799355e+09  3.85e+02     610s
+      34   9.42e-05 6.90e-06   4.99047612e+09  4.78396507e+09  3.06e+02     633s
+      35   9.03e-05 5.12e-06   4.98763526e+09  4.80106034e+09  2.77e+02     662s
+      36   3.98e-05 2.82e-06   4.94599392e+09  4.83065342e+09  1.71e+02     671s
+      37   2.58e-05 2.14e-06   4.93651437e+09  4.83728484e+09  1.47e+02     682s
+      38   2.50e-05 1.47e-06   4.93618595e+09  4.83982232e+09  1.43e+02     693s
+      39   2.07e-05 1.07e-06   4.93270993e+09  4.84546499e+09  1.29e+02     703s
+      40   1.99e-05 9.16e-07   4.93246702e+09  4.84544485e+09  1.29e+02     712s
+      41   1.27e-05 5.14e-07   4.92622155e+09  4.85308727e+09  1.08e+02     749s
+      42   6.18e-06 2.78e-07   4.91448823e+09  4.86733398e+09  6.98e+01     756s
+      43   5.75e-06 2.48e-07   4.91402422e+09  4.86791504e+09  6.83e+01     768s
+      44   3.92e-06 2.41e-07   4.91163152e+09  4.86829302e+09  6.42e+01     772s
+      45   3.73e-06 2.31e-07   4.91112985e+09  4.86905244e+09  6.23e+01     776s
+      46   1.94e-06 1.39e-07   4.90762167e+09  4.87412061e+09  4.96e+01     779s
+      47   1.92e-06 1.37e-07   4.90757967e+09  4.87423812e+09  4.94e+01     783s
+      48   1.25e-06 1.12e-07   4.90638638e+09  4.87546419e+09  4.58e+01     787s
+      49   7.47e-07 8.85e-08   4.90346514e+09  4.87810372e+09  3.75e+01     790s
+      50   6.48e-07 7.92e-08   4.90314008e+09  4.87888853e+09  3.59e+01     794s
+      51   4.98e-07 6.91e-08   4.90218004e+09  4.88024918e+09  3.25e+01     797s
+      52   4.95e-07 6.83e-08   4.90215919e+09  4.88024410e+09  3.24e+01     801s
+      53   4.90e-07 6.80e-08   4.90216775e+09  4.88028007e+09  3.24e+01     804s
+      54   4.36e-07 6.78e-08   4.90188796e+09  4.88043523e+09  3.18e+01     806s
+      55   2.99e-07 6.38e-08   4.90162529e+09  4.88076205e+09  3.09e+01     809s
+      56   1.58e-07 4.39e-08   4.90042224e+09  4.88323839e+09  2.54e+01     811s
+      57   1.34e-07 4.22e-08   4.90059270e+09  4.88334431e+09  2.55e+01     813s
+      58   1.04e-07 3.50e-08   4.90003483e+09  4.88459728e+09  2.29e+01     815s
+      59   9.50e-08 3.37e-08   4.90009649e+09  4.88472024e+09  2.28e+01     818s
+      60   8.60e-08 3.33e-08   4.90001976e+09  4.88479577e+09  2.25e+01     820s
+      61   7.03e-08 2.51e-08   4.89985257e+09  4.88580810e+09  2.08e+01     823s
+      62   5.25e-08 2.49e-08   4.89952348e+09  4.88588451e+09  2.02e+01     825s
+      63   4.41e-08 2.20e-08   4.89936497e+09  4.88640830e+09  1.92e+01     827s
+      64   1.95e-08 2.05e-08   4.89921316e+09  4.88671585e+09  1.85e+01     829s
+      65   1.85e-08 2.04e-08   4.89917163e+09  4.88677402e+09  1.84e+01     831s
+      66   9.08e-09 1.50e-08   4.89794260e+09  4.88826475e+09  1.43e+01     832s
+      67   3.26e-09 1.41e-08   4.89826703e+09  4.88831416e+09  1.47e+01     834s
+      68   2.55e-09 9.27e-09   4.89776323e+09  4.89045614e+09  1.08e+01     836s
+      69   4.15e-10 6.43e-09   4.89731419e+09  4.89088355e+09  9.52e+00     838s
+      70   9.46e-11 6.14e-09   4.89657446e+09  4.89107504e+09  8.14e+00     840s
+      71   7.28e-12 2.97e-09   4.89649409e+09  4.89256997e+09  5.81e+00     842s
+      72   8.60e-12 2.23e-09   4.89597151e+09  4.89267917e+09  4.87e+00     844s
+      73   7.28e-12 1.47e-09   4.89583195e+09  4.89300343e+09  4.19e+00     845s
+      74   7.42e-12 1.28e-09   4.89583188e+09  4.89303732e+09  4.14e+00     847s
+      75   7.03e-12 1.11e-09   4.89582547e+09  4.89341063e+09  3.57e+00     849s
+      76   7.28e-12 6.11e-10   4.89574070e+09  4.89395382e+09  2.65e+00     850s
+      77   5.46e-12 1.48e-09   4.89568118e+09  4.89402122e+09  2.46e+00     852s
+      78   7.28e-12 5.82e-10   4.89555416e+09  4.89441725e+09  1.68e+00     854s
+      79   5.46e-12 9.31e-10   4.89551185e+09  4.89441779e+09  1.62e+00     856s
+      80   7.28e-12 8.00e-10   4.89548266e+09  4.89444930e+09  1.53e+00     858s
+      81   5.46e-12 4.66e-10   4.89540821e+09  4.89457401e+09  1.23e+00     859s
+      82   3.64e-12 4.95e-10   4.89518737e+09  4.89480554e+09  5.65e-01     861s
+      83   7.28e-12 1.03e-09   4.89518640e+09  4.89481077e+09  5.56e-01     864s
+      84   5.46e-12 8.59e-10   4.89516124e+09  4.89485596e+09  4.52e-01     866s
+      85   3.64e-12 5.97e-10   4.89510929e+09  4.89488390e+09  3.34e-01     869s
+      86   3.64e-12 7.28e-10   4.89511555e+09  4.89489396e+09  3.28e-01     870s
+      87   3.64e-12 9.75e-10   4.89509232e+09  4.89493928e+09  2.27e-01     872s
+      88   5.46e-12 1.35e-09   4.89508607e+09  4.89497465e+09  1.65e-01     875s
+      89   5.46e-12 2.34e-09   4.89507160e+09  4.89500290e+09  1.02e-01     877s
+      90   3.64e-12 4.66e-10   4.89506888e+09  4.89501168e+09  8.47e-02     879s
+      91   3.64e-12 8.15e-10   4.89505281e+09  4.89502676e+09  3.86e-02     882s
+      92   7.28e-12 2.90e-09   4.89504804e+09  4.89503126e+09  2.48e-02     884s
+      93   7.28e-12 3.11e-09   4.89504828e+09  4.89503139e+09  2.50e-02     886s
+      94   3.64e-12 3.94e-09   4.89504182e+09  4.89503147e+09  1.53e-02     887s
+      95   7.28e-12 4.55e-09   4.89503900e+09  4.89503404e+09  7.34e-03     889s
+      96   7.28e-12 3.97e-09   4.89503797e+09  4.89503587e+09  3.11e-03     891s
+      97   3.64e-12 7.99e-09   4.89503736e+09  4.89503634e+09  1.50e-03     893s
+      98   3.64e-12 7.41e-09   4.89503673e+09  4.89503642e+09  4.55e-04     895s
+      99   3.64e-12 9.50e-09   4.89503663e+09  4.89503648e+09  2.22e-04     897s
+     100*  3.64e-12 1.32e-08   4.89503652e+09  4.89503651e+09  1.82e-05     899s
+     101*  3.64e-12 1.08e-08   4.89503652e+09  4.89503652e+09  2.25e-06     901s
+     102*  3.64e-12 3.12e-08   4.89503652e+09  4.89503652e+09  1.34e-07     903s
+     103*  3.64e-12 1.51e-08   4.89503652e+09  4.89503652e+09  8.82e-09     905s
+    Running crossover as requested
+        Primal residual before push phase:                  1.27e-04
+        Dual residual before push phase:                    5.11e-05
+        Number of dual pushes required:                     103575
+        Number of primal pushes required:                   6498
+    Summary
+        Runtime:                                            905.57s
+        Status interior point solve:                        optimal
+        Status crossover:                                   optimal
+        objective value:                                    4.89503652e+09
+        interior solution primal residual (abs/rel):        6.64e-11 / 3.97e-15
+        interior solution dual residual (abs/rel):          1.48e-08 / 1.05e-13
+        interior solution objective gap (abs/rel):          1.62e-03 / 3.31e-13
+        basic solution primal infeasibility:                8.04e-10
+        basic solution dual infeasibility:                  2.05e-12
+    Ipx: IPM       optimal
+    Ipx: Crossover optimal
+    Solving the original LP from the solution after postsolve
+    Model   status      : Optimal
+    IPM       iterations: 103
+    Crossover iterations: 12550
+    Objective value     :  4.8950365168e+09
+    LP solved for primal
+    Writing Output
+    Time elapsed for writing costs is
+    1.118419208
+    Time elapsed for writing capacity is
+    0.3498405
+    Time elapsed for writing power is
+    0.784041083
+    Time elapsed for writing charge is
+    0.317718542
+    Time elapsed for writing capacity factor is
+    0.262756083
+    Time elapsed for writing storage is
+    0.144432875
+    Time elapsed for writing curtailment is
+    0.235764667
+    Time elapsed for writing nse is
+    0.673588083
+    Time elapsed for writing power balance is
+    0.533529375
+    Time elapsed for writing transmission flows is
+    0.111765375
+    Time elapsed for writing transmission losses is
+    0.139880458
+    Time elapsed for writing emissions is
+    0.244327417
+    Time elapsed for writing reliability is
+    0.163886125
+    Time elapsed for writing storage duals is
+    0.43827175
+    Time elapsed for writing fuel consumption is
+    0.50627925
+    Time elapsed for writing co2 is
+    0.173650209
+    Time elapsed for writing price is
+    0.07908475
+    Time elapsed for writing energy revenue is
+    0.251686916
+    Time elapsed for writing charging cost is
+    0.174489958
+    Time elapsed for writing subsidy is
+    0.188284041
+    Time elapsed for writing time weights is
+    0.050749417
+    Time elapsed for writing net revenue is
+    0.797592334
+    Wrote outputs to GenX-Tutorials/Tutorials/example_systems/1_three_zones/results
+    Time elapsed for writing is
+    8.276978416

The function Run.jl will build and then solve the model according to the specified parameters. These results will then be output into a results folder in the same directory. Note that the results folders are not overwritten with each run.

using CSV
+using DataFrames
+results = CSV.read(open("example_systems/1_three_zones/Results/capacity.csv"),DataFrame)
11×15 DataFrame
RowResourceZoneStartCapRetCapNewCapEndCapCapacityConstraintDualStartEnergyCapRetEnergyCapNewEnergyCapEndEnergyCapStartChargeCapRetChargeCapNewChargeCapEndChargeCap
String31String3Float64Float64Float64Float64String3Float64Float64Float64Float64Float64Float64Float64Float64
1MA_natural_gas_combined_cycle10.00.07394.757394.750.00.00.00.00.00.00.00.00.0
2CT_natural_gas_combined_cycle20.00.02305.822305.820.00.00.00.00.00.00.00.00.0
3ME_natural_gas_combined_cycle30.00.0716.666716.6660.00.00.00.00.00.00.00.00.0
4MA_solar_pv10.00.021186.521186.50.00.00.00.00.00.00.00.00.0
5CT_onshore_wind20.00.011905.511905.50.00.00.00.00.00.00.00.00.0
6CT_solar_pv20.00.016578.816578.80.00.00.00.00.00.00.00.00.0
7ME_onshore_wind30.00.012767.312767.30.00.00.00.00.00.00.00.00.0
8MA_battery10.00.03362.33362.30.00.00.019427.719427.70.00.00.00.0
9CT_battery20.00.05318.365318.360.00.00.027274.127274.10.00.00.00.0
10ME_battery30.00.02095.32095.30.00.00.07096.277096.270.00.00.00.0
11Totaln/a0.00.083631.383631.3n/a0.00.053798.153798.10.00.00.00.0

As you can see, this runs without a problem! To try with your own parameters, edit the new_params dictionary with whatever parameters you'd like to try and run the cells again.Note: to output the results, you'll have to either delete the previous results folder, or input the name of the new results folder (e.g. results_1) when calling CSV.read as above.

Finally, let's rewite genx_settings.yml to put the original settings in the example back:

YAML.write_file("example_systems/1_three_zones/settings/genx_settings.yml", genx_settings_TZ)
diff --git a/previews/PR670/Tutorials/Tutorial_2_network_visualization/index.html b/previews/PR670/Tutorials/Tutorial_2_network_visualization/index.html new file mode 100644 index 0000000000..7f655407ad --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_2_network_visualization/index.html @@ -0,0 +1,3 @@ + +Tutorial 2: Network Visualization · GenX.jl

Tutorial 2: Network Visualization

Interactive Notebook of the tutorial

To run GenX, there are five mandatory input files: Fuels_data.csv, Network.csv, Load_data.csv, Generators_variability.csv, and Generators_data.csv. Detailed descriptions of these files can be found in the GenX Inputs page of the documentation. This tutorial helps visualize the file Network.csv using the example system example_systems/1_three_zones.

using CSV
+using DataFrames

The input file Network.csv contains the nodes of your network, how they connect to each other, and some important features about them. Below is the network file for example_systems/1_three_zones:

network = CSV.read("example_systems/1_three_zones/system/Network.csv",DataFrame,missingstring="NA")
3×14 DataFrame
RowColumn1Network_zonesNetwork_LinesStart_ZoneEnd_ZoneLine_Max_Flow_MWtransmission_path_namedistance_mileLine_Loss_PercentageLine_Max_Reinforcement_MWLine_Reinforcement_Cost_per_MWyrDerateCapRes_1CapRes_1CapRes_Excl_1
String3String3String3String3String3String7String15String15String15String7String7String7String3Int64?
1MAz11122950MA_to_CT123.05840.0123058372950120600.9500
2CTz22132000MA_to_ME196.53850.0196538472000192610.9500
3MEz3missing

MA, CT, and ME are the abbreviations for states Massachusetts, Connecticut, and Maine. However, since the US region of New England contains other states as well, MA in this case is also used to refer to those states.

Columns Start_Zone and End_Zone specify the network of the three regions. In this case, there are only two network lines, specified in the Network_Lines columns. The Start_Zone column indicates that the first node, MA, is the source of both lines as both rows have value 1. Rows z1 and z2 have values of 2 and 3 in End_Zone, which means both nodes CT and ME recieve energy from node MA. This is also indicated in the column `transmissionpathname'.

Below is a visualization of the network:

diff --git a/previews/PR670/Tutorials/Tutorial_3_K-means_time_domain_reduction/index.html b/previews/PR670/Tutorials/Tutorial_3_K-means_time_domain_reduction/index.html new file mode 100644 index 0000000000..23c7de1296 --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_3_K-means_time_domain_reduction/index.html @@ -0,0 +1,244 @@ + +Tutorial 3: K-Means and Time Domain Reduction · GenX.jl

Tutorial 3: K-Means and Time Domain Reduction

Interactive Notebook of the tutorial

A good tool to reduce computation time of GenX is to use Time-domain reduction. Time Domain Reduction is a method that selects a smaller set of time steps from the data in a way that reduces computation time while still capturing the main information of the model. In this tutorial, we go over how TDR works in GenX and how it uses K-means clustering to choose the optimal time steps. For more information on TDR in capacity expansion models, see Mallapragada et al.

Table of Contents

Time Domain Reduction

To see how Time Domain Reduction works, let's look at the Doad_data in example_systems/1_three_zones:

# First, load all packages needed
+using DataFrames
+using CSV
+using VegaLite
+using YAML
+using PlotlyJS
+using Plots
+using Clustering
+using ScikitLearn
+@sk_import datasets: (make_blobs)
WARNING: redefinition of constant make_blobs. This may fail, cause incorrect answers, or produce other errors.
+PyObject <function make_blobs at 0x29b50d6c0>
case = joinpath("example_systems/1_three_zones");
loads =  CSV.read(joinpath(case,"system/Demand_data.csv"),DataFrame,missingstring="NA")
8760×12 DataFrame
8735 rows omitted
RowVollDemand_SegmentCost_of_Demand_Curtailment_per_MWMax_Demand_Curtailment$/MWhRep_PeriodsTimesteps_per_Rep_PeriodSub_WeightsTime_IndexDemand_MW_z1Demand_MW_z2Demand_MW_z3
String7String3String7String7String7String3String7String7Int64Int64Int64Int64
15000011120001876087601785022421070
220.90.0418002742421201012
330.550.0241100371072029969
440.20.003400469471984947
5569221977944
6670452012960
7773072087996
88754421541029
99794622691083
1010834023821137
1111857824491169
1212866624741181
1313870724871187
874987491073030641463
875087501055030131439
875187511043829811423
875287521046929901427
875387531122832061531
875487541190834011624
875587551156233021576
87568756992337971339
87578757946136211277
87588758901834521217
87598759855132811154
87608760808931061092

The columns to note in this file are Rep_Periods, TimeSteps_Per_Rep_Period, Time_Index, and Demand_MW_. This file shows the number of time steps used in GenX before applying TDR, i.e. every hour in a year, totaling 8,760 hours. This means that there is only one representative period, as seen in Rep_Periods.

TDR performs a "reduction" into a specified number of "representative periods", paring down the input data into a smaller set. The representative periods are then used in the GenX algorithm in place of the entire input data to reduce computation time. The TDR algorithm selects the representative periods to be the set of data whose results in GenX best match the results of the entire input data. To do this, TDR using k-means clustering, described in the next section.

When TDR is used, the file time_domain_reduction_settings.yml is called with a variety of specified settings, shown below:

time_domain_reduction_settings = YAML.load(open(joinpath(case,"settings/time_domain_reduction_settings.yml")))
    Dict{Any, Any} with 15 entries:
+      "IterativelyAddPeriods" => 1
+      "ExtremePeriods"        => Dict{Any, Any}("Wind"=>Dict{Any, Any}("System"=>Di…
+      "UseExtremePeriods"     => 1
+      "MinPeriods"            => 8
+      "MaxPeriods"            => 11
+      "DemandWeight"          => 1
+      "ClusterFuelPrices"     => 1
+      "nReps"                 => 100
+      "MultiStageConcatenate" => 0
+      "Threshold"             => 0.05
+      "TimestepsPerRepPeriod" => 168
+      "IterateMethod"         => "cluster"
+      "ScalingMethod"         => "S"
+      "ClusterMethod"         => "kmeans"
+      "WeightTotal"           => 8760

Important here to note are MinPeriods and MaxPeriods. As TDR is performed, it is required to keep the number of representative periods to be between the min and max specified in the settings. This is to ensure that computation time is actually decreased and that the k-means algorithm doesn't just form one large cluster of all points. Additionally, TimestepsPerRepPeriod is set to 168, the number of hours in a week (WeightTotal includes all 8,760 timesteps, the number of hours in a year.) By specifying the number of timesteps in each representative period to be a week, we form 52 clusters from which the algorithm will choose 8-11.

For descriptions of all settings, see cluster_inputs in the documentation.

Now back to pre-TDR. Below shows the load per timestep in megawatts for the entire dataset, i.e. with only one representative period of 8760 hours. This is done for Zone 1:

loads |>
+@vlplot(:line, 
+    x=:Time_Index, y=:Demand_MW_z1, title="MW Load per hour, No TDR",
+    width=600,height=400,linewidth=.01)

svg

As in Tutorial 1: Configuring Settings, we can open the genx_settings.yml file for 1_three_zones to see how TimeDomainReduction is set. If it's set to 1, this means TDR is being used.

genx_settings_TZ = YAML.load(open((joinpath(case,"settings/genx_settings.yml"))))
    Dict{Any, Any} with 19 entries:
+      "NetworkExpansion"                        => 1
+      "ModelingToGenerateAlternativeIterations" => 3
+      "ParameterScale"                          => 1
+      "EnergyShareRequirement"                  => 0
+      "PrintModel"                              => 0
+      "TimeDomainReduction"                     => 1
+      "Trans_Loss_Segments"                     => 1
+      "CapacityReserveMargin"                   => 0
+      "ModelingtoGenerateAlternativeSlack"      => 0.1
+      "MethodofMorris"                          => 0
+      "StorageLosses"                           => 1
+      "MultiStage"                              => 0
+      "OverwriteResults"                        => 0
+      "UCommit"                                 => 2
+      "ModelingToGenerateAlternatives"          => 0
+      "MaxCapReq"                               => 0
+      "MinCapReq"                               => 1
+      "CO2Cap"                                  => 2
+      "WriteShadowPrices"                       => 1

To visualize how TDR decreases computation time, let's start by running SmallNewEngland/OneZone without TDR. In the third section of this tutorial, we'll run the example again using TDR.

To run GenX without TDR, we start by editing the settings to set TimeDomainReduction to 0:

genx_settings_TZ["TimeDomainReduction"] = 0
+genx_settings_TZ ## Output settings
    Dict{Any, Any} with 13 entries:
+    "NetworkExpansion"       => 1
+    "ParameterScale"         => 1
+    "EnergyShareRequirement" => 0
+    "TimeDomainReduction"    => 0
+    "Trans_Loss_Segments"    => 1
+    "CapacityReserveMargin"  => 0
+    "StorageLosses"          => 1
+    "ComputeConflicts"       => 1
+    "UCommit"                => 2
+    "MaxCapReq"              => 0
+    "MinCapReq"              => 1
+    "CO2Cap"                 => 2
+    "WriteShadowPrices"      => 1

Then we write the edited settings to the file path:

YAML.write_file((joinpath(case,"settings/genx_settings.yml")), genx_settings_TZ)

And run it using include. (Note: this process will take a few minutes):

@time include("example_systems/1_three_zones/Run.jl")

This took a little while to run, and would take even longer for larger systems. Let's see how we can get the run time down using Time Domain Reduction. The next sections go over how K-means clustering is used to perform TDR, and how to interpret the resulting files in GenX.

K-means clustering

Let's go over how TDR works. To perform TDR, GenX uses K-means clustering. K-means is an optimization method that clusters data into several groups based on their proximity to "centers" determined by the algorithm.

K-means finds a set number of groups such that the variance between the distance of each point in the group to the mean of the group is minimized.

\[\begin{align*} +\mathop{\arg \min}\limits_{\mathbf{S}} & \sum_{i = 1}^k \sum_{x \in S_i} ||x - \mu_i||^2 \\ +\end{align*}\]

Where $\mathbf{S} = \{S_1, ... , S_k\}$ are the clusters, with $x$ denoting the elements of the clusters, and $\mu_i$ the mean of each cluster, i.e. the mean of the distances from each point to the center of the cluster. By taking the argmin over $\mathbf{S}$, the points $x$ are clustered into groups where their distance to the center is the smallest. For more information on how k-means works, see the Wikipedia.

GenX uses the package Clustering.jl, with documentation here. As an example, using the package ScikitLearn.jl, let's generate data that can cluster easily.

centers = 5
+X, y = make_blobs(n_samples=50,centers=centers); # From scikit-learn
+b = DataFrame(X,:auto)

Note that clustering works for data without obvious groupings, but using blobs as an example makes k-means easier to visualize.

plotly()
+Plots.scatter(b[!,"x1"],b[!,"x2"],legend=false,title="Before K-means Clustering")

Now we use the function kmeans, which is also used in src/time_domain_reduction in GenX.

R = kmeans(transpose(Matrix(b)),centers)

kmeans returns three outputs: assignments, centers, and counts. Assignments shows to which cluster each points belongs, centers shows where the center coordinates of each cluster are, and counts shows how many points belong to each cluster.

println("Assignments = ",R.assignments)
+println("")
+println("Counts = ",R.counts)
+println("")
+println("Centers:")
+R.centers
plotly()
+Plots.scatter(b[!,"x1"],b[!,"x2"],legend=false,marker_z=R.assignments,c=:lightrainbow,title="After K-means Clustering")

In GenX, the representative periods are the centers of the clusters, each representing one week of the year. In the above example that would mean there are 52 data points gathered into 11 clusters (to see this for yourself, change make_blobs to have 52 data points and 11 clusters.)

Results of Time Domain Reduction

To visualize the results of TDR, we'll set TDR = 1 back in the genx_settings.yml file in Example_Systems_Tutorials/SmallNewEngland/OneZone/:

genx_settings_TZ["TimeDomainReduction"] = 1;
genx_settings_TZ
YAML.write_file((joinpath(case,"settings/genx_settings.yml")), genx_settings_TZ)

And run GenX again with TDR:

@time include("example_systems/1_three_zones/Run.jl")

Csv files with the results of TDR are generated automatically in a folder called TDR_results found within the same folder containing the input csv files, in this case Example_Systems_Tutorials/SmallNewEngland/OneZone. The csv files in this folder show the files used in Run.jl that have been pared down from the initial input files.

As an example, consider the input file Fuels_data.csv:

Fuels_original = CSV.read(joinpath(case,"system/Fuels_data.csv"),DataFrame,missingstring="NA")

Compared to TDR_Results/Fuels_data.csv:

Fuels_TDR = CSV.read(joinpath(case,"TDR_Results/Fuels_data.csv"),DataFrame,missingstring="NA")

As you can see, the original has all 8,760 hours, while the TDR version only has 1,848 hours.

loads_TDR = CSV.read(joinpath(case,"TDR_Results/Demand_data.csv"),DataFrame,missingstring="NA")

The 1,848 hours are divided into 11 sections of 168 hours, with each section representing one week of the original data. The number of hours per representative period is set in time_domain_reduction_settings.yml. Also specified in the file are the minimum and maximum number of clusters we would like to have (in this case 8 and 11). The k-means algorithm will then select the number of clusters that should be sufficient to capture the GenX model in fewer time steps (in this case 11).

Gen_TDR = CSV.read(joinpath(case,"TDR_Results/Generators_variability.csv"),DataFrame,missingstring="NA")
generators = CSV.read(joinpath(case,"system/Generators_variability.csv"),DataFrame,missingstring="NA")

Below, we create arrays out of the representative weeks and plot them on the same plot used in the beginning of this tutorial. The file Period_map shows which periods (weeks) are used in TDR and which time step corresponds to each period:

Period_map = CSV.read(joinpath(case,"TDR_Results/Period_map.csv"),DataFrame,missingstring="NA")
# Find array of unique representative periods
+rep_periods = unique(Period_map[!,"Rep_Period"])
+
+# Create an array of the time steps and MW values of each representative period
+weeks_load = []
+for i in rep_periods
+    week_temp_loads = [repeat([i],168) loads[(168*i-167):168*i,"Time_Index"] loads[(168*i-167):168*i,"Demand_MW_z1"]]
+    weeks_load = [weeks_load; week_temp_loads]
+end
+
+# Combine with Total (pre TDR)
+loads_plot = [repeat(["Total"],8760) loads[!,"Time_Index"] loads[!,"Demand_MW_z1"]];
+
+# Add column names and convert column type
+loads_with_TDR = [loads_plot; weeks_load]
+loads_with_TDR = DataFrame(loads_with_TDR ,["Week","hour", "MW"])
+loads_with_TDR[!,:hour] = convert.(Int64,loads_with_TDR[!,:hour]);
+loads_with_TDR[!,:MW] = convert.(Float64,loads_with_TDR[!,:MW]);
loads_with_TDR  |>
+@vlplot(mark={:line},
+    x={:hour,title="Time Step (hours)",labels="Week:n"}, y={:MW,title="Load (MW)"},
+    color={"Week:n", scale={scheme="paired"},sort="decsending"}, title="MW Load per hour with TDR Representative Weeks",
+    width=845,height=400)

svg

TDR is performed for four total data sets: demand (found in Demand.csv), wind and solar (found in Generators_variability.csv), and fuel prices (found in Fuels.csv). Above is just the demand load for one of the three total nodes in the example system, which is why the data may not appear to "represent" all 52 weeks (notice there are fewer representative periods in the fall). Instead, the periods more accurately represent all the data time series combined, including some other parts of the data not seen in this particular plot.

Extreme Periods Off

GenX has a feature called ExtremePeriods, which forces kmeans to include the highest and lowest points in the algorithm. This is done to ensure outliers are used, which is needed in planning energy capacity as the system needs to be able to account for outliers in energy needs. In the above graph, we can see that energy needs peak during the summer, and that the week with the highest load demand is included as a representative week. Let's try turning extreme periods off, and see what happens. move this up

time_domain_reduction_settings["ExtremePeriods"] = 0;
+YAML.write_file(joinpath(case,"settings/genx_settings.yml"), genx_settings_TZ);
+rm(joinpath(case,"TDR_results"), recursive=true) 
include("example_systems/1_three_zones/Run.jl")
Loads_TDR2 = CSV.read(joinpath(case,"TDR_Results/Load_data.csv"),DataFrame,missingstring="NA");
+Period_map2 = CSV.read(joinpath(case,"TDR_Results/Period_map.csv"),DataFrame,missingstring="NA");
rep_periods2 = unique(Period_map2[!,"Rep_Period"])
+
+weeks2 = []
+for i in rep_periods2
+    week_temp = [repeat([i],168) loads[(168*i-167):168*i,"Time_Index"] loads[(168*i-167):168*i,"Demand_MW_z1"]]
+    weeks2 = [weeks2; week_temp]
+end
+
+weeks2 = [weeks2 repeat(["Off"],1848)];
loads_plotOff = [repeat(["Total"],8760) loads[!,"Time_Index"] loads[!,"Demand_MW_z1"] repeat(["Off"],8760) ];
+loads_with_TDR[!,"Extreme_Periods"] = repeat(["On"],length(loads_with_TDR[!,1]));
+loads_with_TDR2 = [loads_plotOff; weeks2]
loads_with_TDR2 = DataFrame(loads_with_TDR2 ,["Week","hour","MW","Extreme_Periods"])
+loads_with_TDR2[!,:hour] = convert.(Int64,loads_with_TDR2[!,:hour]);
+loads_with_TDR2[!,:MW] = convert.(Float64,loads_with_TDR2[!,:MW]);
# Define a new color scheme to accomodate more periods
+myscheme = ["#a6cee3","#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00",
+    "#cab2d6","#6a3d9a","#ffff99","#b15928","#b1ff00","#095768","#ce7e00","#b4a7d6"];
+[loads_with_TDR; loads_with_TDR2] |>
+@vlplot(mark={:line}, row="Extreme_Periods:n",
+    x={:hour,title="Time Step (hours)",labels="Week:n"}, y={:MW,title="Load (MW)"},
+    color={"Week:n", scale={scheme="paired"},sort="decsending"}, 
+    title="MW Load per hour with TDR Representative Weeks, Extreme Periods Off",
+    width=845,height=300)

svg

The first plot (with Extreme Periods off) may not have the week with the highest peak highlighted. If the week with the highest demand is highlighted, try re-running the cell with Extreme Periods Off plotting the results.

Turn Extreme Periods back on:

time_domain_reduction_settings["ExtremePeriods"] = 1;
+YAML.write_file(joinpath(case,"settings/time_domain_reduction_settings.yml"), time_domain_reduction_settings);
+rm(joinpath(case,"TDR_results"), recursive=true) 

Reconstruction

Below is a plot of a reconstruction of the data using only the weeks isolated as representative periods. This is what GenX reads when it runs the solver with TDR on.

recon = []
+recon_noex = []
+for i in range(1,52)
+    index = Period_map[i,"Rep_Period"]
+    recon_temp = [repeat([index],168) collect((168*i-167):168*i) loads[(168*index-167):168*index,"Demand_MW_z1"]]
+    recon = [recon; recon_temp]
+    
+    index2 = Period_map2[i,"Rep_Period"]
+    recon_noex_temp = [repeat([index2],168) collect((168*i-167):168*i) loads[(168*index2-167):168*index2,"Demand_MW_z1"]]
+    recon_noex = [recon_noex; recon_noex_temp]
+end
+
+recon = DataFrame(recon,["Week","hour", "MW"])
+recon[!,:hour] = convert.(Int64,recon[!,:hour]);
+recon[!,:MW] = convert.(Float64,recon[!,:MW]);
+recon[!,"Extreme_Periods"] = repeat(["On"],length(recon[!,1]));
+
+recon_noex = [recon_noex repeat(["Off"],8736)];
+recon_noex = DataFrame(recon_noex,["Week","hour", "MW", "Extreme_Periods"])
+recon_noex[!,:hour] = convert.(Int64,recon_noex[!,:hour]);
+recon_noex[!,:MW] = convert.(Float64,recon_noex[!,:MW]);
plotlyjs()
+G1 = Plots.plot(recon_noex[!,:hour], recon_noex[!,:MW], linewidth=1.7,
+    xticks=0:500:9000,xlabelfontsize=8,
+    yticks=0:2000:18000,yformatter =:plain,ylims=(0,18000), ylabel="Demand (MW)",
+    legend=false, title="Reconstruction, Extreme Periods Off", hover=recon_noex[!,:Week],
+    color=recon_noex[!,:Week],size=(845,800),palette=:Paired_12)
+
+G2 = Plots.plot(recon[!,:hour], recon[!,:MW], linewidth=1.7,
+    xticks=0:500:9000,xlabel="Time Step (Hours)",xlabelfontsize=8,
+    ylims=(0,18000),yticks=0:2000:18000,yformatter =:plain,ylabel="Demand (MW)",
+    legend=false, title="Reconstruction, Extreme Periods On",hover=recon[!,:Week],
+    color=recon[!,:Week],size=(845,800),palette=:Paired_12)
+
+Plots.plot(G1,G2,layout=(2,1))

Each color represents one of the representative weeks.

The range of 8-11 representative periods was chosen by the developers because it was deemed to be the smallest set that still matches the optimal value of the data well. The next section of this Tutorial goes over how the optimal values of the data change as the number of representative periods changes.

Objective Values and Representative Periods

Each time Run.jl is run, a results folder is produced. This folder contains numerous .csv files with output variable from the GenX model. For more information on all outputs, see the documentation GenX Outputs.

This section focuses on the objective value of the model. In optimization problems, the objective value is the main value minimized or maximized within the constraints of the model, according to the objective function specified in the problem formulation. In the case of GenX, the objective function is the total annual electricity system cost. A detailed description of the optimization problem is Objective Function in the documentation.

For the purpose of this tutorial, we focus on the objective value as a way to evaluate how well the representative periods actually "represent" the entire model. To see how well the objective value of representative periods aligns with that of the total period, we can run example_systems/1_three_zone with a variety of minimum and maximum total periods.

time_domain_reduction_settings
    Dict{Any, Any} with 15 entries:
+    "IterativelyAddPeriods" => 1
+    "ExtremePeriods"        => 1
+    "UseExtremePeriods"     => 1
+    "MinPeriods"            => 8
+    "MaxPeriods"            => 11
+    "DemandWeight"          => 1
+    "ClusterFuelPrices"     => 1
+    "nReps"                 => 100
+    "MultiStageConcatenate" => 0
+    "Threshold"             => 0.05
+    "TimestepsPerRepPeriod" => 168
+    "IterateMethod"         => "cluster"
+    "ScalingMethod"         => "S"
+    "ClusterMethod"         => "kmeans"
+    "WeightTotal"           => 8760

Each time Run.jl is run, a new results folder appears in the model folder. These folders are not overwritten. So far, we've run the model three times, so we should have three results folders in the 1threezones folder, but you may have more if you've run the model more. To ensure that the following code works, we'll delete any results folders beyond the original.

folders = cd(readdir,case)
    10-element Vector{String}:
+    ".DS_Store"
+    "README.md"
+    "Run.jl"
+    "policies"
+    "resources"
+    "results"
+    "results_1"
+    "results_2"
+    "settings"
+    "system"
for folder in folders
+    if length(folder) >= 8 && folder[1:8] == "results_"
+        rm("example_systems/1_three_zones/" * folder,recursive=true) 
+    end
+end
+     
+cd(readdir,case) ## Make sure they were deleted
    8-element Vector{String}:
+    ".DS_Store"
+    "README.md"
+    "Run.jl"
+    "policies"
+    "resources"
+    "results"
+    "settings"
+    "system"

To demonstrate how the objective value changes as the number of representative periods does, we'll run GenX ten times, each with a different number of periods, and plot the objective values.

# Change MinPeriods and Max Periods, run `Run.jl`
+# For simplicity, keep min and max periods the same
+rep_periods = [4, 8, 12, 24, 36, 48]
+times = [0.0,0.0,0.0,0.0,0.0,0.0]
+
+for i in range(1,6)
+    println(" ")
+    println("----------------------------------------------------")
+    println("Iteration ",i)
+    println("Periods = ",rep_periods[i])
+    println("----------------------------------------------------")
+    time_domain_reduction_settings["MinPeriods"] = rep_periods[i]
+    time_domain_reduction_settings["MaxPeriods"] = rep_periods[i]
+    if "TDR_Results" in cd(readdir,case)
+        rm(joinpath(case,"TDR_results", recursive=true)) 
+    end
+    println(" ")
+    YAML.write_file(joinpath(case,"settings/time_domain_reduction_settings.yml"), time_domain_reduction_settings)
+    time = @elapsed include("example_systmes/1_three_zones/Run.jl")
+    times[i] = time
+end

Note that as the number of periods increases, so does the time it takes to run.

Now, let's check that we have the correct Results folders and process the objecive values to plot. There should be seven results folders, including the original results.

cd(readdir,case)

The objective value is found in the files costs.csv and status.csv.

status = CSV.read(joinpath(case,"results/status.csv"),DataFrame,missingstring="NA")
 1×3 DataFrame
+    Row  Status     Solve       Objval
+         String     Float64     String
+    ─────────────────────────────────────
+     1	 OPTIMAL	53.9481	    9762.44
# Find objective values from each results folder:
+
+OV_noTDR = CSV.read(joinpath(case,"Results/status.csv"),DataFrame);
+OV_RP4 = CSV.read(joinpath(case,"Results_1/status.csv"),DataFrame);
+OV_RP8 = CSV.read(joinpath(case,"Results_2/status.csv"),DataFrame);
+OV_RP12 = CSV.read(joinpath(case,"Results_3/status.csv"),DataFrame);
+OV_RP24 = CSV.read(joinpath(case,"Results_4/status.csv"),DataFrame);
+OV_RP36 = CSV.read(joinpath(case,"Results_5/status.csv"),DataFrame);
+OV_RP48 = CSV.read(joinpath(case,"Results_6/status.csv"),DataFrame);
# Isolate the objective values from the data frame
+obj_val_tot = [1 OV_noTDR[!,3]; 
+               4 OV_RP4[!,3];
+               8 OV_RP8[!,3];
+               12 OV_RP12[!,3]; 
+               24 OV_RP24[!,3]; 
+               36 OV_RP36[!,3]; 
+               48 OV_RP48[!,3]]
# Take the absolute difference between the original objective value and the new ones
+obj_val_plot = [4 abs(OV_noTDR[!,3][1]-OV_RP4[!,3][1]); 
+                8 abs(OV_noTDR[!,3][1]-OV_RP8[!,3][1]); 
+                12 abs(OV_noTDR[!,3][1]-OV_RP12[!,3][1]);
+                24 abs(OV_noTDR[!,3][1]-OV_RP24[!,3][1]); 
+                36 abs(OV_noTDR[!,3][1]-OV_RP36[!,3][1]); 
+                48 abs(OV_noTDR[!,3][1]-OV_RP48[!,3][1])]
# Plot the differences as a function of number of representative periods
+plotlyjs()
+Plots.scatter(obj_val_plot[:,1],obj_val_plot[:,2],hover=obj_val_plot[:,2],legend=false,xticks=obj_val_plot[:,1],
+                ylabel="|ObjValue[1] - ObjValue[x]|",xlabel="Rep Periods",title="Difference in Objective Values")
+scatter!(twinx(),obj_val_plot[:,1],times,color=:red,markeralpha=.5,label=:"Time",legend=:topleft,
+    yaxis=(label="Time"))
+ygrid!(:on, :dashdot, 0.1)

Here, we can see that while having very few representative periods produces an objective value that differs greatly from the orignal, once we reach around 12 representative periods the difference begins to taper out. Therefore, the original choice of 11 maximum periods in 1_three_zones decreases the run time of GenX significantly while while maintaining an objective value close to the original.

It's important to note, however, that the difference does not always taper out, and for some systems you'll find that the error in objective value continues to decrease as the number of representative periods increases. There also is no way to know apriori what number of periods works.

Finally, let's set TDR to have 8 and 11 min/max periods again, and delete the TDR Results folder.

time_domain_reduction_settings["MinPeriods"] = 8;
+time_domain_reduction_settings["MaxPeriods"] = 11;
time_domain_reduction_settings
rm(joinpath(case,"TDR_results", recursive=true))

julia rm(joinpath(case,"TDR_results"), recursive=true) YAML.write_file(joinpath(case,"settings/time_domain_reduction_settings.yml"), time_domain_reduction_settings) folders = cd(readdir,case) for folder in folders if length(folder) >= 7 && folder[1:7] == "results" rm("example_systems/1_three_zones/" * folder,recursive=true) end end

diff --git a/previews/PR670/Tutorials/Tutorial_4_model_generation/index.html b/previews/PR670/Tutorials/Tutorial_4_model_generation/index.html new file mode 100644 index 0000000000..e01f755916 --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_4_model_generation/index.html @@ -0,0 +1,327 @@ + +Tutorial 4: Model Generation · GenX.jl

Tutorial 4: Model Generation

Interactive Notebook of the tutorial

To run GenX, we use the file Run.jl. This file will solve the optimization problem and generate the output files as described in the documentation and previous tutorial. It does so by first generating the model, then solving the model, both according to settings described in genx_settings.yml. However, Run.jl only contains one commmand, run_genx_case!(dirname(@__FILE__)). This can be confusing for users viewing the files for the first time. In reality, this function signals many more functions to run, generating and solving the model. This tutorial explains how the model in GenX is generated. The next tutorial will then describe how it is solved.

We'll start by explaining JuMP, the optimization package that GenX uses to generate and solve the model.

Table of Contents

JuMP is a modeling language for Julia. It allows users to create models for optimization problems, define variables and constraints, and apply a variety of solvers for the model.

GenX is a Linear Program (LP), which is a form of optimization problem in which a linear objective is minimized (or maximized) according to a set of linear constraints. For more information on LPs, see Wikipedia.

using JuMP
+using HiGHS

Let's say we want to build a power grid consisting of and coal and wind plants. We want to decrease the cost of producing energy while still meeting a certain emissions threshold and full grid demand. Coal plants are cheaper to build and run but have higher emissions than wind farms. To find the minimum cost of a power grid meeting these constraints, we construct an LP using JuMP.

\[\begin{aligned} +& \min 10 x + 15 y &\text{Objective function (cost)}\\ +& \text{s.t.} & \\ +& x + y \geq 10 &\text{Grid Demand}\\ +& 55x + 70y \leq \ 1000 &\text{Construction constraint}\\ +& 40 x + 5 y \leq 200 &\text{Emissions constraint} \\ +& x, y \geq 0 &\text{Non-negativity constraints}\\ +\end{aligned}\]

The core of the JuMP model is the function Model(), which creates the structure of our LP. Model() takes an optimizer as its input.

power = Model(HiGHS.Optimizer)
A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS

The model needs variables, defined using the JuMP function @variable:

@variable(power,x) # Coal
+@variable(power,y) # Wind

Using the JuMP function @constraint, we can add the constraints of the model:

@constraint(power, non_neg_x, x >= 0) # Non-negativity constraint (can't have negative power plants!)
+@constraint(power, non_neg_y, y >= 0) # Non-negativity constraint
+
+@constraint(power, emissions, 40x + 5y <= 200) # Emisisons constraint
+@constraint(power, construction_costs, 55x + 70y <= 1000) # Cost of constructing a new plant
+
+@constraint(power, demand, x + y >= 10) # Grid demand

$x + y \geq 10$

Next, the function @expression defines an expression that can be used in either a constraint or objective function. In GenX, expressions are defined throughout the model generation and put into constraints and the objective function later.

@expression(power,objective,10x+15y)

$ 10 x + 15 y $

Finally, we define the objective function itself:

@objective(power, Min, objective)

$10 x + 15 y$

Our model is now set up!

print(power)

\[\begin{aligned} +\min\quad & 10 x + 15 y\\ +\text{Subject to} \quad & x \geq 0\\ + & y \geq 0\\ + & x + y \geq 10\\ + & 40 x + 5 y \leq 200\\ + & 55 x + 70 y \leq 1000\\ +\end{aligned} \]

In the next Tutorial, we go over how to use JuMP to solve the model we've constructed.

When Run.jl is called, the model for GenX is constructed in a similar way, but with many more factors to consider. The next section goes over how the GenX model is constructed before it is solved.

Generate Model

The basic structure of the way Run.jl generates and solves the model is as follows:

The function run_genx_case(case) takes the "case" as its input. The case is all of the input files and settings found in the same folder as Run.jl. For example, in example_systems/1_three_zones, the case is:

Run_genx_case defines the setup, which are the settings in genx_settings.yml. From there, either run_genx_case_simple(case, mysetup) orrun_genx_case_multistage(case, mysetup) is called. Both of these define the inputs and optimizer. The optimizer is the solver as specified in genx_settings.yml, and the inputs are a variety of parameters specified by the settings and csv files found in the folder. Both of these functions then call generate_model(mysetup, myinputs, OPTIMIZER), which is the main subject of this tutorial.

As in the above example, generate_model utilizes the JuMP functions Model(), @expression, @variable, and @constraints to form a model. This section goes through generate_model and explains how the expressions are formed to create the model.

Arguments

Generate_model takes three arguments: setup, inputs, and optimizer:

To generate the arguments, we have to set a case path (this is set automatically when Run.jl is called):

using GenX
case = joinpath("example_systems/1_three_zones") 
    "example_systems/1_three_zones"

Setup includes the settings from genx_settings.yml along with the default settings found in configure_settings.jl. The function configure_settings combines the two.

genx_settings = GenX.get_settings_path(case, "genx_settings.yml") # Settings YAML file path
+writeoutput_settings = GenX.get_settings_path(case, "output_settings.yml") # Set output path
+setup = GenX.configure_settings(genx_settings,writeoutput_settings) # Combines genx_settings with defaults
    Configuring Settings
+    Dict{Any, Any} with 24 entries:
+      "NetworkExpansion"                        => 0
+      "TimeDomainReductionFolder"               => "TDR_Results"
+      "EnableJuMPStringNames"                   => false
+      "Trans_Loss_Segments"                     => 1
+      "ModelingtoGenerateAlternativeSlack"      => 0.1
+      "Solver"                                  => "HiGHS"
+      "Reserves"                                => 0
+      "MultiStage"                              => 0
+      "OverwriteResults"                        => 0
+      "ModelingToGenerateAlternatives"          => 0
+      "MaxCapReq"                               => 1
+      "MinCapReq"                               => 1
+      "CO2Cap"                                  => 2
+      "WriteShadowPrices"                       => 1
+      "ModelingToGenerateAlternativeIterations" => 3
+      "ParameterScale"                          => 1
+      "EnergyShareRequirement"                  => 1
+      "PrintModel"                              => 0
+      "TimeDomainReduction"                     => 1
+      "CapacityReserveMargin"                   => 1
+      "MethodofMorris"                          => 0
+      "StorageLosses"                           => 1
+      "IncludeLossesInESR"                      => 0
+      "UCommit"                                 => 2

It's here that we create the folder TDR_results before generating the model. This occurs if TimeDomainReduction is set to 1 in the setup. As a reminder, TDR_results is not overwritten when called again. The cell below will delete a preexisting TDR_results folder if it is there.

TDRpath = joinpath(case, setup["TimeDomainReductionFolder"])
+system_path = joinpath(case, setup["SystemFolder"])
+
+settings_path = GenX.get_settings_path(case)
+
+if "TDR_results" in cd(readdir,case)
+    rm(joinpath(case,"TDR_results"), recursive=true) 
+end
+
+if setup["TimeDomainReduction"] == 1
+    GenX.prevent_doubled_timedomainreduction(system_path)
+    if !GenX.time_domain_reduced_files_exist(TDRpath)
+        println("Clustering Time Series Data (Grouped)...")
+        GenX.cluster_inputs(case, settings_path, setup)
+    else
+        println("Time Series Data Already Clustered.")
+    end
+end
    Clustering Time Series Data (Grouped)...
+    Reading Input CSV Files
+    Network.csv Successfully Read!
+    Load_data.csv Successfully Read!
+    Fuels_data.csv Successfully Read!
+    Generators_data.csv Successfully Read!
+    Generators_variability.csv Successfully Read!
+    Validating time basis
+    Capacity_reserve_margin.csv Successfully Read!
+    Minimum_capacity_requirement.csv Successfully Read!
+    Maximum_capacity_requirement.csv Successfully Read!
+    Energy_share_requirement.csv Successfully Read!
+    CO2_cap.csv Successfully Read!
+    CSV Files Successfully Read In From Example_Systems_Tutorials/SmallNewEngland/OneZone
+
+    Dict{String, Any} with 9 entries:
+      "RMSE"          => Dict("Load_MW_z1"=>1100.54, "NG"=>0.312319, "onshore_wind_…
+      "OutputDF"      => DataFrame…
+      "ColToZoneMap"  => Dict("Load_MW_z1"=>1, "battery_z1"=>1, "natural_gas_combin…
+      "ClusterObject" => KmeansResult{Matrix{Float64}, Float64, Int64}([-1.38728 -1…
+      "TDRsetup"      => Dict{Any, Any}("IterativelyAddPeriods"=>1, "ExtremePeriods…
+      "Assignments"   => [1, 1, 1, 1, 2, 2, 2, 2, 2, 3  …  6, 4, 3, 5, 5, 9, 10, 10…
+      "InputDF"       => [672×49 DataFrame
+      "Weights"       => [673.846, 1010.77, 673.846, 842.308, 842.308, 1853.08, 185…
+      "Centers"       => Any[1, 7, 12, 15, 23, 24, 28, 29, 48, 50, 51]

The optimizer argument is taken from setup:

OPTIMIZER =  GenX.configure_solver(settings_path,HiGHS.Optimizer)

The function configure_solver converts the string from "Solver" to a MathOptInterface optimizer so it can be used in the JuMP model as the optimizer. It also goes into the settings file for the specified solver (in this case HiGHS, so 1_three_zones/settings/highs_settings.yml) and uses the settings to configure the solver to be used later.

typeof(OPTIMIZER)
    MathOptInterface.OptimizerWithAttributes

The "inputs" argument is generated by the function load_inputs from the case in run_genx_case_simple (or multistage). If TDR is set to 1 in the settings file, then load_inputs will draw some of the files from the TDR_Results folder. TDR_Results is produced when the case is run.

inputs = GenX.load_inputs(setup, case)
    Reading Input CSV Files
+    Network.csv Successfully Read!
+    Load_data.csv Successfully Read!
+    Fuels_data.csv Successfully Read!
+    Generators_data.csv Successfully Read!
+    Generators_variability.csv Successfully Read!
+    Validating time basis
+    Capacity_reserve_margin.csv Successfully Read!
+    Minimum_capacity_requirement.csv Successfully Read!
+    Maximum_capacity_requirement.csv Successfully Read!
+    Energy_share_requirement.csv Successfully Read!
+    CO2_cap.csv Successfully Read!
+    CSV Files Successfully Read In From Example_Systems_Tutorials/SmallNewEngland/OneZone
+
+    Dict{Any, Any} with 66 entries:
+      "Z"                   => 1
+      "LOSS_LINES"          => [1]
+      "RET_CAP_CHARGE"      => Int64[]
+      "pC_D_Curtail"        => [50.0]
+      "dfGen"               => [4×68 DataFram
+      "pTrans_Max_Possible" => [2.95]
+      "pNet_Map"            => [1.0;;]
+      "omega"               => [4.01099, 4.01099, 4.01099, 4.01099, 4.01099, 4.0109…
+      "RET_CAP_ENERGY"      => [4]
+      "RESOURCES"           => String31["natural_gas_combined_cycle", "solar_pv", "…
+      "COMMIT"              => [1]
+      "pMax_D_Curtail"      => [1]
+      "STOR_ALL"            => [4]
+      "THERM_ALL"           => [1]
+      "dfCO2CapZones"       => [1;;]
+      "REP_PERIOD"          => 11
+      "MinCapReq"           => [5.0, 10.0, 6.0]
+      "STOR_LONG_DURATION"  => Int64[]
+      "dfCapRes"            => [0.156;;]
+      "STOR_SYMMETRIC"      => [4]
+      "VRE"                 => [2, 3]
+      "RETRO"               => Int64[]
+      "THERM_COMMIT"        => [1]
+      "TRANS_LOSS_SEGS"     => 1
+      "H"                   => 168
+      ⋮                     => ⋮

Now that we have our arguments, we're ready to generate the model itself.

Run generate_model

This subsection replicates the arguments in the function generate_model. Note: Running some of these cells for a second time will throw an error as the code will attempt to define a new expression with the name of an existing expression. To run the Tutorial again, clear and restart the kernel.

First, we initialize a model and define the time step and zone variables

EP = Model(OPTIMIZER)  # From JuMP
A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS
T = inputs["T"];   # Number of time steps (hours)
+Z = inputs["Z"];   # Number of zones

Next, the dummy variable vZERO, the objective function, the power balance expression, and zone generation expression are all initialized to zero:

# Introduce dummy variable fixed to zero to ensure that expressions like eTotalCap,
+# eTotalCapCharge, eTotalCapEnergy and eAvail_Trans_Cap all have a JuMP variable
+
+GenX.set_string_names_on_creation(EP, Bool(setup["EnableJuMPStringNames"]))
+@variable(EP, vZERO == 0);
+
+# Initialize Power Balance Expression
+# Expression for "baseline" power balance constraint
+GenX.create_empty_expression!(EP, :ePowerBalance, (T, Z))
+
+# Initialize Objective Function Expression
+EP[:eObj] = AffExpr(0.0)
+
+GenX.create_empty_expression!(EP, :eGenerationByZone, (Z, T))
+
+# Energy losses related to technologies
+GenX.create_empty_expression!(EP, :eELOSSByZone, Z)
    1×1848 Matrix{Int64}:
+     0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0

Next, we go through some of the settings in setup and, if they've been set to be utilized (i.e. have a nonzero value), define expressions from their corresponding input files:

# Initialize Capacity Reserve Margin Expression
+if setup["CapacityReserveMargin"] > 0
+    GenX.create_empty_expression!(EP, :eCapResMarBalance, (inputs["NCapacityReserveMargin"], T))
+end
+
+# Energy Share Requirement
+if setup["EnergyShareRequirement"] >= 1
+    GenX.create_empty_expression!(EP, :eESR, inputs["nESR"])
+end
+
+if setup["MinCapReq"] == 1
+    GenX.create_empty_expression!(EP, :eMinCapRes, inputs["NumberOfMinCapReqs"])
+end
+
+if setup["MaxCapReq"] == 1
+    GenX.create_empty_expression!(EP, :eMaxCapRes, inputs["NumberOfMaxCapReqs"])
+end

The other settings will be used later on.

Next, we define the model infrastructure using functions found in src/core. These take entries from inputs and setup to create more expressions in our model (EP). To see what the functions do in more detail, see the source code and core documentation.

# Infrastructure
+GenX.discharge!(EP, inputs, setup)
+
+GenX.non_served_energy!(EP, inputs, setup)
+
+GenX.investment_discharge!(EP, inputs, setup)
+
+if setup["UCommit"] > 0
+    GenX.ucommit!(EP, inputs, setup)
+end
+
+GenX.fuel!(EP, inputs, setup)
+
+GenX.co2!(EP, inputs) 
+
+if setup["OperationalReserves"] > 0
+    GenX.operational_reserves!(EP, inputs, setup)
+end
+
+if Z > 1
+    GenX.investment_transmission!(EP, inputs, setup)
+    GenX.transmission!(EP, inputs, setup)
+end
+
+if Z > 1 && setup["DC_OPF"] != 0
+    GenX.dcopf_transmission!(EP, inputs, setup)
+end
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Fuel Module
+    CO2 Module
+    Investment Transmission Module
+    Transmission Module

We then define variables and expressions based on the resources in the inputs and setup arguments. The details of these can be found in the src/resources folder and the "resources" folder under Model Function Reference in the documentation:

# Technologies
+# Model constraints, variables, expression related to dispatchable renewable resources
+
+if !isempty(inputs["VRE"])
+    GenX.curtailable_variable_renewable!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expression related to non-dispatchable renewable resources
+if !isempty(inputs["MUST_RUN"])
+    GenX/must_run!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expression related to energy storage modeling
+if !isempty(inputs["STOR_ALL"])
+    GenX.storage!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expression related to reservoir hydropower resources
+if !isempty(inputs["HYDRO_RES"])
+    GenX.hydro_res!(EP, inputs, setup)
+end
+
+if !isempty(inputs["ELECTROLYZER"])
+    GenX.electrolyzer!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expression related to reservoir hydropower resources with long duration storage
+if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"])
+    GenX.hydro_inter_period_linkage!(EP, inputs)
+end
+
+# Model constraints, variables, expression related to demand flexibility resources
+if !isempty(inputs["FLEX"])
+    GenX.flexible_demand!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expression related to thermal resource technologies
+if !isempty(inputs["THERM_ALL"])
+    GenX.thermal!(EP, inputs, setup)
+end
+
+# Model constraints, variables, expressions related to the co-located VRE-storage resources
+if !isempty(inputs["VRE_STOR"])
+    GenX.vre_stor!(EP, inputs, setup)
+end
+
+

Finally, we define expressions and variables using policies outlined in the inputs. These functions can be found in src/policies and in the Emission mitigation policies section of the documentation:

# Policies
+
+if setup["OperationalReserves"] > 0
+    GenX.operational_reserves_constraints!(EP, inputs)
+end
+
+# CO2 emissions limits
+if setup["CO2Cap"] > 0
+    GenX.co2_cap!(EP, inputs, setup)
+end
+
+# Endogenous Retirements
+if setup["MultiStage"] > 0
+    GenX.endogenous_retirement!(EP, inputs, setup)
+end
+
+# Energy Share Requirement
+if setup["EnergyShareRequirement"] >= 1
+    GenX.energy_share_requirement!(EP, inputs, setup)
+end
+
+#Capacity Reserve Margin
+if setup["CapacityReserveMargin"] > 0
+    GenX.cap_reserve_margin!(EP, inputs, setup)
+end
+
+if (setup["MinCapReq"] == 1)
+    GenX.minimum_capacity_requirement!(EP, inputs, setup)
+end
+
+if setup["MaxCapReq"] == 1
+    GenX.maximum_capacity_requirement!(EP, inputs, setup)
+end
+
    Energy Share Requirement Policies Module
+    Capacity Reserve Margin Policies Module
+    Minimum Capacity Requirement Module
+    Maximum Capacity Requirement Module
+
+    3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+     cZoneMaxCapReq[1] : -vRETCAP[2] + vCAP[2] ≤ 50
+     cZoneMaxCapReq[2] : -vRETCAP[3] + vCAP[3] ≤ 100
+     cZoneMaxCapReq[3] : -vRETCAP[4] + vCAP[4] ≤ 60
+

The expressions and variables for the model have all been defined! All that's left to do is define the constraints and objective function.

The Objective Function here is to minimize

@objective(EP,Min,EP[:eObj])
0.17159171428571432 vP_{1,1} + 0.0004010989010989012 vP_{3,1} + 0.0006016483516483517 vP_{4,1} + 0.17159171428571432 vP_{1,2} + 0.0004010989010989012 vP_{3,2} + 0.0006016483516483517 vP_{4,2} + 0.17159171428571432 vP_{1,3} + 0.0004010989010989012 vP_{3,3} + 0.0006016483516483517 vP_{4,3} + 0.17159171428571432 vP_{1,4} + 0.0004010989010989012 vP_{3,4} + 0.0006016483516483517 vP_{4,4} + 0.17159171428571432 vP_{1,5} + 0.0004010989010989012 vP_{3,5} + 0.0006016483516483517 vP_{4,5} + 0.17159171428571432 vP_{1,6} + 0.0004010989010989012 vP_{3,6} + 0.0006016483516483517 vP_{4,6} + 0.17159171428571432 vP_{1,7} + 0.0004010989010989012 vP_{3,7} + 0.0006016483516483517 vP_{4,7} + 0.17159171428571432 vP_{1,8} + 0.0004010989010989012 vP_{3,8} + 0.0006016483516483517 vP_{4,8} + 0.17159171428571432 vP_{1,9} + 0.0004010989010989012 vP_{3,9} + 0.0006016483516483517 vP_{4,9} + 0.17159171428571432 vP_{1,10} + 0.0004010989010989012 vP_{3,10} + 0.0006016483516483517 vP_{4,10} + [[\ldots\text{11038 terms omitted}\ldots]] + 0.00015041208791208792 vCHARGE_{4,1819} + 0.00015041208791208792 vCHARGE_{4,1820} + 0.00015041208791208792 vCHARGE_{4,1821} + 0.00015041208791208792 vCHARGE_{4,1822} + 0.00015041208791208792 vCHARGE_{4,1823} + 0.00015041208791208792 vCHARGE_{4,1824} + 0.00015041208791208792 vCHARGE_{4,1825} + 0.00015041208791208792 vCHARGE_{4,1826} + 0.00015041208791208792 vCHARGE_{4,1827} + 0.00015041208791208792 vCHARGE_{4,1828} + 0.00015041208791208792 vCHARGE_{4,1829} + 0.00015041208791208792 vCHARGE_{4,1830} + 0.00015041208791208792 vCHARGE_{4,1831} + 0.00015041208791208792 vCHARGE_{4,1832} + 0.00015041208791208792 vCHARGE_{4,1833} + 0.00015041208791208792 vCHARGE_{4,1834} + 0.00015041208791208792 vCHARGE_{4,1835} + 0.00015041208791208792 vCHARGE_{4,1836} + 0.00015041208791208792 vCHARGE_{4,1837} + 0.00015041208791208792 vCHARGE_{4,1838} + 0.00015041208791208792 vCHARGE_{4,1839} + 0.00015041208791208792 vCHARGE_{4,1840} + 0.00015041208791208792 vCHARGE_{4,1841} + 0.00015041208791208792 vCHARGE_{4,1842} + 0.00015041208791208792 vCHARGE_{4,1843} + 0.00015041208791208792 vCHARGE_{4,1844} + 0.00015041208791208792 vCHARGE_{4,1845} + 0.00015041208791208792 vCHARGE_{4,1846} + 0.00015041208791208792 vCHARGE_{4,1847} + 0.00015041208791208792 vCHARGE_{4,1848} $

Our constraint is the Power Balance, which is set here to have to meet the demand of the network. The demand is outlined in the last columns of Load_data.csv, and is set to inputs in from the load_load_data function within load_inputs, used in run_genx_case.

## Power balance constraints
+# demand = generation + storage discharge - storage charge - demand deferral + deferred demand satisfaction - demand curtailment (NSE)
+#          + incoming power flows - outgoing power flows - flow losses - charge of heat storage + generation from NACC
+@constraint(EP, cPowerBalance[t=1:T, z=1:Z], EP[:ePowerBalance][t,z] == inputs["pD"][t,z])
+
    1848×1 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
+     cPowerBalance[1,1] : vP[2,1] + vP[3,1] + vP[4,1] + vNSE[1,1,1] - vCHARGE[4,1] = 11.162
+     cPowerBalance[2,1] : vP[2,2] + vP[3,2] + vP[4,2] + vNSE[1,2,1] - vCHARGE[4,2] = 10.556
+     cPowerBalance[3,1] : vP[2,3] + vP[3,3] + vP[4,3] + vNSE[1,3,1] - vCHARGE[4,3] = 10.105
+     cPowerBalance[4,1] : vP[2,4] + vP[3,4] + vP[4,4] + vNSE[1,4,1] - vCHARGE[4,4] = 9.878
+     cPowerBalance[5,1] : vP[2,5] + vP[3,5] + vP[4,5] + vNSE[1,5,1] - vCHARGE[4,5] = 9.843
+     cPowerBalance[6,1] : vP[2,6] + vP[3,6] + vP[4,6] + vNSE[1,6,1] - vCHARGE[4,6] = 10.017
+     cPowerBalance[7,1] : vP[2,7] + vP[3,7] + vP[4,7] + vNSE[1,7,1] - vCHARGE[4,7] = 10.39
+     cPowerBalance[8,1] : vP[2,8] + vP[3,8] + vP[4,8] + vNSE[1,8,1] - vCHARGE[4,8] = 10.727
+     cPowerBalance[9,1] : vP[2,9] + vP[3,9] + vP[4,9] + vNSE[1,9,1] - vCHARGE[4,9] = 11.298
+     cPowerBalance[10,1] : vP[2,10] + vP[3,10] + vP[4,10] + vNSE[1,10,1] - vCHARGE[4,10] = 11.859
+     cPowerBalance[11,1] : vP[2,11] + vP[3,11] + vP[4,11] + vNSE[1,11,1] - vCHARGE[4,11] = 12.196
+     cPowerBalance[12,1] : vP[2,12] + vP[3,12] + vP[4,12] + vNSE[1,12,1] - vCHARGE[4,12] = 12.321
+     cPowerBalance[13,1] : vP[2,13] + vP[3,13] + vP[4,13] + vNSE[1,13,1] - vCHARGE[4,13] = 12.381
+     ⋮
+     cPowerBalance[1837,1] : vP[2,1837] + vP[3,1837] + vP[4,1837] + vNSE[1,1837,1] - vCHARGE[4,1837] = 13.911
+     cPowerBalance[1838,1] : vP[2,1838] + vP[3,1838] + vP[4,1838] + vNSE[1,1838,1] - vCHARGE[4,1838] = 13.818
+     cPowerBalance[1839,1] : vP[2,1839] + vP[3,1839] + vP[4,1839] + vNSE[1,1839,1] - vCHARGE[4,1839] = 13.71
+     cPowerBalance[1840,1] : vP[2,1840] + vP[3,1840] + vP[4,1840] + vNSE[1,1840,1] - vCHARGE[4,1840] = 13.796
+     cPowerBalance[1841,1] : vP[2,1841] + vP[3,1841] + vP[4,1841] + vNSE[1,1841,1] - vCHARGE[4,1841] = 15.038
+     cPowerBalance[1842,1] : vP[2,1842] + vP[3,1842] + vP[4,1842] + vNSE[1,1842,1] - vCHARGE[4,1842] = 16.088
+     cPowerBalance[1843,1] : vP[2,1843] + vP[3,1843] + vP[4,1843] + vNSE[1,1843,1] - vCHARGE[4,1843] = 16.076
+     cPowerBalance[1844,1] : vP[2,1844] + vP[3,1844] + vP[4,1844] + vNSE[1,1844,1] - vCHARGE[4,1844] = 15.782
+     cPowerBalance[1845,1] : vP[2,1845] + vP[3,1845] + vP[4,1845] + vNSE[1,1845,1] - vCHARGE[4,1845] = 15.392
+     cPowerBalance[1846,1] : vP[2,1846] + vP[3,1846] + vP[4,1846] + vNSE[1,1846,1] - vCHARGE[4,1846] = 14.663
+     cPowerBalance[1847,1] : vP[2,1847] + vP[3,1847] + vP[4,1847] + vNSE[1,1847,1] - vCHARGE[4,1847] = 13.62
+     cPowerBalance[1848,1] : vP[2,1848] + vP[3,1848] + vP[4,1848] + vNSE[1,1848,1] - vCHARGE[4,1848] = 12.388

After this final constraint is defined, generate_model finishes compiling the EP, and run_genx_simple (or multistage) uses solve_model to solve the EP. This will be described in Tutorial 5.

diff --git a/previews/PR670/Tutorials/Tutorial_5_solve_model/index.html b/previews/PR670/Tutorials/Tutorial_5_solve_model/index.html new file mode 100644 index 0000000000..00d4bf7eec --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_5_solve_model/index.html @@ -0,0 +1,169 @@ + +Tutorial 5: Solving the Model · GenX.jl

Tutorial 5: Solving the Model

Interactive Notebook of the tutorial

In Tutorial 4, we went over how the model is generated when GenX is run using Run.jl. In the function run_genx_case_simple (or multistage), after generate_model is called, solve_model is called to solve the EP.

In this tutorial, we go over how to use JuMP to solve a model, what it looks like to solve GenX, and how to edit the solver settings.

Table of Contents

A Simple Example

From Tutorial 4, we have the model:

\[\begin{aligned} +& \min 10 x + 15 y &\text{Objective function (cost)}\\ +& \text{s.t.} & \\ +& x + y \geq 10 &\text{Grid Demand}\\ +& 55x + 70y \leq \ 1000 &\text{Construction constraint}\\ +& 40 x + 5 y \leq 200 &\text{Emissions constraint} \\ +& x, y \geq 0 &\text{Non-negativity constraints}\\ +\end{aligned}\]

using JuMP
+using HiGHS
power = Model(HiGHS.Optimizer)
+
+@variable(power,x,Int) # Coal
+@variable(power,y,Int) # Wind
+
+@constraint(power, non_neg_x, x >= 0) # Non-negativity constraint (can't have negative power plants!)
+@constraint(power, non_neg_y, y >= 0) # Non-negativity constraint
+
+@constraint(power, emissions, 40x + 5y <= 200) # Emisisons constraint
+@constraint(power, construction_costs, 55x + 70y <= 1000) # Cost of constructing a new plant
+
+@constraint(power, demand, x + y >= 10) # Grid demand
+
+@expression(power,objective,10x+15y)
+
+@objective(power, Min, objective)

\[10 x + 15 y \]

JuMP uses the function optimize!(model) to solve the LP:

optimize!(power)
    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Presolving model
+    3 rows, 2 cols, 6 nonzeros
+    3 rows, 2 cols, 6 nonzeros
+    Objective function is integral with scale 0.2
+    
+    Solving MIP model with:
+       3 rows
+       2 cols (0 binary, 2 integer, 0 implied int., 0 continuous)
+       6 nonzeros
+    
+            Nodes      |    B&B Tree     |            Objective Bounds              |  Dynamic Constraints |       Work      
+         Proc. InQueue |  Leaves   Expl. | BestBound       BestSol              Gap |   Cuts   InLp Confl. | LpIters     Time
+    
+             0       0         0   0.00%   90              inf                  inf        0      0      0         0     0.0s
+    
+    Solving report
+      Status            Optimal
+      Primal bound      130
+      Dual bound        130
+      Gap               0% (tolerance: 0.01%)
+      Solution status   feasible
+                        130 (objective)
+                        0 (bound viol.)
+                        0 (int. viol.)
+                        0 (row viol.)
+      Timing            0.00 (total)
+                        0.00 (presolve)
+                        0.00 (postsolve)
+      Nodes             1
+      LP iterations     1 (total)
+                        0 (strong br.)
+                        0 (separation)
+                        0 (heuristics)

We can use the function value.() to get the value of each variable, and objective_value() to get the total objective value.

println("# Coal Plants: ", value.(x))
+println("# Wind Farms: ", value.(y))
+println("Cost: ", objective_value(power))
    # Coal Plants: 4.0
+    # Wind Farms: 6.0
+    Cost: 130.0

We can also use the JuMP function solution_summary to see more details of the solution:

solution_summary(power)
    * Solver : HiGHS
+    
+    * Status
+      Result count       : 1
+      Termination status : OPTIMAL
+      Message from the solver:
+      "kHighsModelStatusOptimal"
+    
+    * Candidate solution (result #1)
+      Primal status      : FEASIBLE_POINT
+      Dual status        : NO_SOLUTION
+      Objective value    : 1.30000e+02
+      Objective bound    : 1.30000e+02
+      Relative gap       : 0.00000e+00
+    
+    * Work counters
+      Solve time (sec)   : 3.36621e-03
+      Simplex iterations : 1
+      Barrier iterations : -1
+      Node count         : 1

GenX

Let's optimize the GenX model created in the last Tutorial. To do so, we'll create the inputs for generate_model and run it.

using GenX
case = joinpath("example_systems/1_three_zones") 
+
+genx_settings = GenX.get_settings_path(case, "genx_settings.yml");
+writeoutput_settings = GenX.get_settings_path(case, "output_settings.yml")
+setup = GenX.configure_settings(genx_settings,writeoutput_settings)
+
+settings_path = GenX.get_settings_path(case)
+
+### Create TDR_Results
+TDRpath = joinpath(case, setup["TimeDomainReductionFolder"])
+system_path = joinpath(case, setup["SystemFolder"])
+
+if setup["TimeDomainReduction"] == 1
+    GenX.prevent_doubled_timedomainreduction(system_path)
+    if !GenX.time_domain_reduced_files_exist(TDRpath)
+        println("Clustering Time Series Data (Grouped)...")
+        GenX.cluster_inputs(case, settings_path, setup)
+    else
+        println("Time Series Data Already Clustered.")
+    end
+end
+
+OPTIMIZER =  GenX.configure_solver(settings_path,HiGHS.Optimizer);
+
+inputs = GenX.load_inputs(setup, case)
EP = GenX.generate_model(setup,inputs,OPTIMIZER)
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Emissions Module (for CO2 Policy modularization
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (Unit Commitment) Resources Module
+    C02 Policies Module
+    Energy Share Requirement Policies Module
+    Capacity Reserve Margin Policies Module
+    Minimum Capacity Requirement Module
+    Maximum Capacity Requirement Module
+
+    A JuMP Model
+    Minimization problem with:
+    Variables: 18492
+    Objective function type: AffExpr
+    `AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 5544 constraints
+    `AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 7398 constraints
+    `AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 27730 constraints
+    `VariableRef`-in-`MathOptInterface.EqualTo{Float64}`: 2 constraints
+    `VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 18490 constraints
+    Model mode: AUTOMATIC
+    CachingOptimizer state: EMPTY_OPTIMIZER
+    Solver name: HiGHS
+    Names registered in the model: cCO2Emissions_systemwide, cCapacityResMargin, cESRShare, cMaxCap, cMaxCapEnergy, cMaxCapEnergyDuration, cMaxNSE, cMaxRetCommit, cMaxRetEnergy, cMaxRetNoCommit, cMinCap, cMinCapEnergy, cMinCapEnergyDuration, cNSEPerSeg, cPowerBalance, cSoCBalInterior, cSoCBalStart, cZoneMaxCapReq, cZoneMinCapReq, eCFix, eCFixEnergy, eCNSE, eCStart, eCVar_in, eCVar_out, eCapResMarBalance, eCapResMarBalanceStor, eCapResMarBalanceThermal, eCapResMarBalanceVRE, eELOSS, eELOSSByZone, eESR, eESRDischarge, eEmissionsByPlant, eEmissionsByZone, eExistingCap, eExistingCapEnergy, eGenerationByThermAll, eGenerationByVRE, eGenerationByZone, eMaxCapRes, eMaxCapResInvest, eMinCapRes, eMinCapResInvest, eObj, ePowerBalance, ePowerBalanceDisp, ePowerBalanceNse, ePowerBalanceStor, ePowerBalanceThermCommit, eTotalCFix, eTotalCFixEnergy, eTotalCNSE, eTotalCNSET, eTotalCNSETS, eTotalCStart, eTotalCStartT, eTotalCVarIn, eTotalCVarInT, eTotalCVarOut, eTotalCVarOutT, eTotalCap, eTotalCapEnergy, vCAP, vCAPENERGY, vCHARGE, vCO2Cap_slack, vCOMMIT, vNSE, vP, vRETCAP, vRETCAPENERGY, vS, vSHUT, vSTART, vZERO

The function solve_model(model, setup) uses optimize to optimize the model:

solution = optimize!(EP) # GenX.solve_model(EP,setup)
objective_value(EP)
    9776.57688838726

Infeasibility

In some cases, your model may not be able to return a value. This happens when no value can be found that satisfies all constraints. To see this, let's go back to our simple example and change one of the parameters to break the model.

\[\begin{aligned} +& \min 10 x + 15 y &\text{Objective function (cost)}\\ +& \text{s.t.} & \\ +& x + y \geq 10 &\text{Grid Demand}\\ +& 55x + 70y \leq \ 1000 &\text{Construction constraint}\\ +& 40 x + 5 y \leq 200 &\text{Emissions constraint} \\ +& 7 x + 30 y \geq 500 &\textbf{New Constraint} \\ +& x, y \geq 0 &\text{Non-negativity constraints}\\ +\end{aligned}\]

@constraint(power, new, 7x + 30y >= 500)
+

\[7 x + 30 y \geq 500 \]

print(power)

\[\begin{aligned} +\min\quad & 10 x + 15 y\\ +\text{Subject to} \quad & x \geq 0\\ + & y \geq 0\\ + & x + y \geq 10\\ + & 7 x + 30 y \geq 500\\ + & 40 x + 5 y \leq 200\\ + & 55 x + 70 y \leq 1000\\ + & x \in \mathbb{Z}\\ + & y \in \mathbb{Z}\\ +\end{aligned} \]

optimize!(power)
    Presolving model
+    Presolve: Infeasible
+    
+    Solving report
+      Status            Infeasible
+      Primal bound      inf
+      Dual bound        -inf
+      Gap               inf
+      Solution status   -
+      Timing            0.00 (total)
+                        0.00 (presolve)
+                        0.00 (postsolve)
+      Nodes             0
+      LP iterations     0 (total)
+                        0 (strong br.)
+                        0 (separation)
+                        0 (heuristics)

In this case, the infeasibility was detected on the presovle since it's clear no solution would fit within all constraints. For information on how to debug an infeasible solution, see the JuMP documentaion. Some solvers, such as Gurobi, will compute what is causing the conflict, e.g. which constraints are infeasible with one another (HiGHS does not do this).

GenX version 0.4 has the feature ComputeConflict in settings. If the model does not work, try setting ComputeConflict = 1, and the conflicting constraints will be returned.

Tutorial 6 describes the solver settings, how to change them, and the effects of PreSolve, Crossover, and Feasibility Tolerance.

diff --git a/previews/PR670/Tutorials/Tutorial_6_solver_settings/index.html b/previews/PR670/Tutorials/Tutorial_6_solver_settings/index.html new file mode 100644 index 0000000000..f5c698896b --- /dev/null +++ b/previews/PR670/Tutorials/Tutorial_6_solver_settings/index.html @@ -0,0 +1,330 @@ + +Tutorial 6: Post Processing · GenX.jl

Tutorial 6: Solver Settings

Interactive Notebook of the tutorial

Though solving the model relies only on optimize, there are a number of ways to change the way in which the model is optimized. This tutorial goes over solver parameters and how they affect the model solution.

Table of Contents

using YAML
+using GenX
+using JuMP
+using HiGHS
+using DataFrames
+using Plots
+using Plotly
case = joinpath("example_systems/1_three_zones") 
+
+genx_settings = GenX.get_settings_path(case, "genx_settings.yml");
+writeoutput_settings = GenX.get_settings_path(case, "output_settings.yml")
+setup = GenX.configure_settings(genx_settings,writeoutput_settings) 
+settings_path = GenX.get_settings_path(case)
    Configuring Settings
+
+    "example_systems/1_three_zones/settings"
### Create TDR_Results
+if "TDR_results" in cd(readdir,case)
+    rm(joinpath(case,"TDR_results"), recursive=true) 
+end
+
+TDRpath = joinpath(case, setup["TimeDomainReductionFolder"])
+system_path = joinpath(case, setup["SystemFolder"])
+
+if setup["TimeDomainReduction"] == 1
+    GenX.prevent_doubled_timedomainreduction(system_path)
+    if !GenX.time_domain_reduced_files_exist(TDRpath)
+        println("Clustering Time Series Data (Grouped)...")
+        GenX.cluster_inputs(case, settings_path, setup)
+    else
+        println("Time Series Data Already Clustered.")
+    end
+end
+
+inputs = GenX.load_inputs(setup, case)

The HiGHS Solver

In the example files, the solver HiGHS. HiGHS is freely available for all to use. Other solvers, such as Gurobi, are available for free for academics. For the purpose of this tutorial, we will be focusing on HiGHS.

To set the solver preferences, go into the settings folder of your case and select the YAML file of the solver you're using.

settings_folder = cd(readdir,joinpath(case,"Settings")) # Print Settings folder
    7-element Vector{String}:
+    ".DS_Store"
+    "clp_settings.yml"
+    "cplex_settings.yml"
+    "genx_settings.yml"
+    "gurobi_settings.yml"
+    "highs_settings.yml"
+    "time_domain_reduction_settings.yml"
highs_settings = YAML.load(open(joinpath(case,"Settings/highs_settings.yml")))
    Dict{Any, Any} with 6 entries:
+      "Method"        => "choose"
+      "Feasib_Tol"    => 0.1
+      "run_crossover" => "on"
+      "TimeLimit"     => 1.0e23
+      "Optimal_Tol"   => 0.1
+      "Pre_Solve"     => "choose"

The function configure_highs in src/configure_solver contains a list of default settings for the HiGHS solver

There are about 80, so we'll only focus on a few for now. In most cases, you can leave the other settings on default.

The default settings are combined with the settings you specify in highs_settings.yml in configure_highs, which is called from configure_solver in run_genx_case_simple right before the model is generated.

Feasibility Tolerance

The parameters Feasib_Tol and Optimal_Tol represent the feasibility of the primal and dual functions respectively. Without going into too much detail, a dual function is an analagous formulation of the original ("primal") function whose objective value acts as a lower bound to the primal function. The objective value of the primal function is then the upper bound of the dual function. HiGHS will solve the dual and primal at each time step, then terminate when the solutions of the two are within a certain tolerance range. For more information on how this works specifically in HiGHS, see the HiGHS documentaion.

If we decrease the tolerance parameters, the objective value becomes closer to the "true" optimal value.

# Change tolerance, generate and solve model`
+tols = [1e-7,1e-4,1e-2,1e-1]
+OV = zeros(1,4)
+
+for i in range(1,length(tols))
+    println(" ")
+    println("----------------------------------------------------")
+    println("Iteration ",i)
+    println("Tolerance = ",tols[i])
+    println("----------------------------------------------------")
+    highs_settings["Feasib_Tol"] = tols[i]
+    highs_settings["Optimal_Tol"] = tols[i]
+    YAML.write_file(joinpath(case,"settings/highs_settings.yml"), highs_settings)
+    OPTIMIZER1 = GenX.configure_solver(settings_path,HiGHS.Optimizer)
+    EP = GenX.generate_model(setup,inputs,OPTIMIZER1)
+    GenX.solve_model(EP,setup)
+    OV[i] = objective_value(EP)
+end

Using the smallest tolerance as our base, we can see the error as the tolerance increases:

DataFrame([tols[2:end] abs.(OV[2:end] .- OV[1])],["Tolerance", "Error"])
3×2 DataFrame
+    Row	Tolerance	Error
+        Float64	    Float64
+    ───────────────────────────
+    1	0.0001	    0.0
+    2	0.01	    1.81899e-12
+    3	0.1	        3.45608e-11
using Plots
+using Plotly
# Plot the error as a function of the tolerance
+plotlyjs()
+Plots.scatter(tols[2:end], abs.(OV[2:end] .- OV[1]),legend=:topleft,
+                ylabel="Error", xlabel="Tolerance",size=(920,400),label=:"Error",title="Tolerance of Solver vs Error")
+ygrid!(:on, :dashdot, 0.1)

PreSolve

In optimization, presolve is a stage at the beginning of the solver in which the problem is simplified to remove redunant constraints and otherwise simplify the problem before the optimization itself begins. The default for presolve in GenX is "choose", allowing the solver to use presolve only if it will reduce computation time.

Let's try setting presolve to off and on, then compare computation times.

# First, set tolerances back to original
+highs_settings["Feasib_Tol"] = 1e-5
+highs_settings["Optimal_Tol"] = 1e-5
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)    
highs_settings["Pre_Solve"] = "off"
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)
+OPTIMIZER2 = GenX.configure_solver(setup["Solver"], settings_path);
+EP2 = GenX.generate_model(setup,inputs,OPTIMIZER2)
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Emissions Module (for CO2 Policy modularization
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (Unit Commitment) Resources Module
+    C02 Policies Module
+    Energy Share Requirement Policies Module
+    Capacity Reserve Margin Policies Module
+    Minimum Capacity Requirement Module
+    Maximum Capacity Requirement Module
+
+    A JuMP Model
+    Minimization problem with:
+    Variables: 18492
+    Objective function type: AffExpr
+    `AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 5544 constraints
+    `AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 7398 constraints
+    `AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 27730 constraints
+    `VariableRef`-in-`MathOptInterface.EqualTo{Float64}`: 2 constraints
+    `VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 18490 constraints
+    Model mode: AUTOMATIC
+    CachingOptimizer state: EMPTY_OPTIMIZER
+    Solver name: HiGHS
+    Names registered in the model: cCO2Emissions_systemwide, cCapacityResMargin, cESRShare, cMaxCap, cMaxCapEnergy, cMaxCapEnergyDuration, cMaxNSE, cMaxRetCommit, cMaxRetEnergy, cMaxRetNoCommit, cMinCap, cMinCapEnergy, cMinCapEnergyDuration, cNSEPerSeg, cPowerBalance, cSoCBalInterior, cSoCBalStart, cZoneMaxCapReq, cZoneMinCapReq, eCFix, eCFixEnergy, eCNSE, eCStart, eCVar_in, eCVar_out, eCapResMarBalance, eCapResMarBalanceStor, eCapResMarBalanceThermal, eCapResMarBalanceVRE, eELOSS, eELOSSByZone, eESR, eESRDischarge, eEmissionsByPlant, eEmissionsByZone, eExistingCap, eExistingCapEnergy, eGenerationByThermAll, eGenerationByVRE, eGenerationByZone, eMaxCapRes, eMaxCapResInvest, eMinCapRes, eMinCapResInvest, eObj, ePowerBalance, ePowerBalanceDisp, ePowerBalanceNse, ePowerBalanceStor, ePowerBalanceThermCommit, eTotalCFix, eTotalCFixEnergy, eTotalCNSE, eTotalCNSET, eTotalCNSETS, eTotalCStart, eTotalCStartT, eTotalCVarIn, eTotalCVarInT, eTotalCVarOut, eTotalCVarOutT, eTotalCap, eTotalCapEnergy, vCAP, vCAPENERGY, vCHARGE, vCO2Cap_slack, vCOMMIT, vNSE, vP, vRETCAP, vRETCAPENERGY, vS, vSHUT, vSTART, vZERO
solution2 = @elapsed GenX.solve_model(EP2,setup)
    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Solving LP without presolve or with basis
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+              0    -4.3842305368e+02 Ph1: 19318(12391.6); Du: 5(438.423) 0s
+          18662     9.5083526285e+03 Pr: 3142(5018.36); Du: 0(0.000872182) 5s
+          23359     9.8583752055e+03 Pr: 0(0); Du: 0(1.27565e-13) 6s
+    Model   status      : Optimal
+    Simplex   iterations: 23359
+    Objective value     :  9.8583752055e+03
+    HiGHS run time      :          6.77
+    LP solved for primal
+
+    6.84933975
highs_settings["Pre_Solve"] = "on"
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)
+OPTIMIZER3 = GenX.configure_solver(setup["Solver"], settings_path);
+EP3 = GenX.generate_model(setup,inputs,OPTIMIZER3)
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Emissions Module (for CO2 Policy modularization
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (Unit Commitment) Resources Module
+    C02 Policies Module
+    Energy Share Requirement Policies Module
+    Capacity Reserve Margin Policies Module
+    Minimum Capacity Requirement Module
+    Maximum Capacity Requirement Module
+
+    A JuMP Model
+    Minimization problem with:
+    Variables: 18492
+    Objective function type: AffExpr
+    `AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 5544 constraints
+    `AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 7398 constraints
+    `AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 27730 constraints
+    `VariableRef`-in-`MathOptInterface.EqualTo{Float64}`: 2 constraints
+    `VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 18490 constraints
+    Model mode: AUTOMATIC
+    CachingOptimizer state: EMPTY_OPTIMIZER
+    Solver name: HiGHS
+    Names registered in the model: cCO2Emissions_systemwide, cCapacityResMargin, cESRShare, cMaxCap, cMaxCapEnergy, cMaxCapEnergyDuration, cMaxNSE, cMaxRetCommit, cMaxRetEnergy, cMaxRetNoCommit, cMinCap, cMinCapEnergy, cMinCapEnergyDuration, cNSEPerSeg, cPowerBalance, cSoCBalInterior, cSoCBalStart, cZoneMaxCapReq, cZoneMinCapReq, eCFix, eCFixEnergy, eCNSE, eCStart, eCVar_in, eCVar_out, eCapResMarBalance, eCapResMarBalanceStor, eCapResMarBalanceThermal, eCapResMarBalanceVRE, eELOSS, eELOSSByZone, eESR, eESRDischarge, eEmissionsByPlant, eEmissionsByZone, eExistingCap, eExistingCapEnergy, eGenerationByThermAll, eGenerationByVRE, eGenerationByZone, eMaxCapRes, eMaxCapResInvest, eMinCapRes, eMinCapResInvest, eObj, ePowerBalance, ePowerBalanceDisp, ePowerBalanceNse, ePowerBalanceStor, ePowerBalanceThermCommit, eTotalCFix, eTotalCFixEnergy, eTotalCNSE, eTotalCNSET, eTotalCNSETS, eTotalCStart, eTotalCStartT, eTotalCVarIn, eTotalCVarInT, eTotalCVarOut, eTotalCVarOutT, eTotalCap, eTotalCapEnergy, vCAP, vCAPENERGY, vCHARGE, vCO2Cap_slack, vCOMMIT, vNSE, vP, vRETCAP, vRETCAPENERGY, vS, vSHUT, vSTART, vZERO
solution3 = @elapsed GenX.solve_model(EP3,setup)
    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Presolving model
+    35947 rows, 17464 cols, 136177 nonzeros
+    34470 rows, 15991 cols, 136110 nonzeros
+    Presolve : Reductions: rows 34470(-6202); columns 15991(-2501); elements 136110(-29848)
+    Solving the presolved LP
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+              0    -2.9011432493e+00 Ph1: 118(557.293); Du: 15(2.90114) 0s
+          16445     9.8583752055e+03 Pr: 0(0); Du: 0(1.25316e-13) 4s
+    Solving the original LP from the solution after postsolve
+    Model   status      : Optimal
+    Simplex   iterations: 16445
+    Objective value     :  9.8583752055e+03
+    HiGHS run time      :          4.55
+    LP solved for primal
+
+    4.655439792

As we can see, the runtime with PreSolve is shorter, and would be even shorter for a larger system. However, it could introduce numerical inaccuracies. If you find the model is struggling to converge, try turn PreSolve off.

# Write PreSolve back to choose
+highs_settings["Pre_Solve"] = "choose"
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)

Crossover

Crossover is a method in which, at each step of the optimization algorithm, the solution is pushed to the boundary of the solution space. This allows for a potentially more accurate solution, but can be computationally intensive. Let's try turning crossover on and off and see what solutions we get:

highs_settings["run_crossover"] = "off"
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)
+OPTIMIZER4 = GenX.configure_solver(setup["Solver"], settings_path);
+EP4 = GenX.generate_model(setup,inputs,OPTIMIZER4)
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Emissions Module (for CO2 Policy modularization
+    Transmission Module
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (Unit Commitment) Resources Module
+    C02 Policies Module
+    Minimum Capacity Requirement Module
+
+    A JuMP Model
+    Minimization problem with:
+    Variables: 83192
+    Objective function type: AffExpr
+    `AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 24024 constraints
+    `AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 20334 constraints
+    `AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 103509 constraints
+    `VariableRef`-in-`MathOptInterface.EqualTo{Float64}`: 4 constraints
+    `VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 79492 constraints
+    Model mode: AUTOMATIC
+    CachingOptimizer state: EMPTY_OPTIMIZER
+    Solver name: HiGHS
+    Names registered in the model: cCO2Emissions_systemwide, cMaxCap, cMaxCapEnergy, cMaxCapEnergyDuration, cMaxFlow_in, cMaxFlow_out, cMaxLineReinforcement, cMaxNSE, cMaxRetCommit, cMaxRetEnergy, cMaxRetNoCommit, cMinCap, cMinCapEnergy, cMinCapEnergyDuration, cNSEPerSeg, cPowerBalance, cSoCBalInterior, cSoCBalStart, cTAuxLimit, cTAuxSum, cTLoss, cZoneMinCapReq, eAvail_Trans_Cap, eCFix, eCFixEnergy, eCNSE, eCStart, eCVar_in, eCVar_out, eELOSS, eELOSSByZone, eEmissionsByPlant, eEmissionsByZone, eExistingCap, eExistingCapEnergy, eGenerationByThermAll, eGenerationByVRE, eGenerationByZone, eLosses_By_Zone, eMinCapRes, eMinCapResInvest, eNet_Export_Flows, eObj, ePowerBalance, ePowerBalanceDisp, ePowerBalanceLossesByZone, ePowerBalanceNetExportFlows, ePowerBalanceNse, ePowerBalanceStor, ePowerBalanceThermCommit, eTotalCFix, eTotalCFixEnergy, eTotalCNSE, eTotalCNSET, eTotalCNSETS, eTotalCNetworkExp, eTotalCStart, eTotalCStartT, eTotalCVarIn, eTotalCVarInT, eTotalCVarOut, eTotalCVarOutT, eTotalCap, eTotalCapEnergy, eTransMax, vCAP, vCAPENERGY, vCHARGE, vCO2Cap_slack, vCOMMIT, vFLOW, vNEW_TRANS_CAP, vNSE, vP, vRETCAP, vRETCAPENERGY, vS, vSHUT, vSTART, vTAUX_NEG, vTAUX_POS, vTLOSS, vZERO
solution4 = @elapsed GenX.solve_model(EP4,setup)
    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Presolving model
+    123675 rows, 81174 cols, 478190 nonzeros
+    116575 rows, 74076 cols, 478470 nonzeros
+    Presolve : Reductions: rows 116575(-31292); columns 74076(-9116); elements 478470(-75676)
+    Solving the presolved LP
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+              0     1.9243583242e+03 Pr: 5545(9397.81); Du: 0(3.92542e-09) 0s
+          13530     2.9999821788e+03 Pr: 30131(9.49605e+06); Du: 0(0.0013131) 5s
+          17547     3.5484267823e+03 Pr: 19414(952045); Du: 0(0.00175439) 10s
+          20723     4.7298157079e+03 Pr: 29321(991206); Du: 0(0.00183106) 15s
+          23580     5.7123748112e+03 Pr: 32150(7.12339e+06); Du: 0(0.00148821) 21s
+          26170     6.1864339355e+03 Pr: 23825(7.35638e+06); Du: 0(0.00149712) 26s
+          29149     6.6441899572e+03 Pr: 27775(3.51868e+06); Du: 0(0.00148729) 31s
+          31348     6.8846964690e+03 Pr: 35484(2.8051e+06); Du: 0(0.00139557) 36s
+          33603     6.9920173954e+03 Pr: 24118(2.03558e+06); Du: 0(0.00124593) 42s
+          35671     7.2015870783e+03 Pr: 34108(1.48506e+07); Du: 0(0.00155189) 47s
+          37885     7.4048679609e+03 Pr: 31619(4.35123e+07); Du: 0(0.00179926) 52s
+          40109     7.6356641465e+03 Pr: 25843(2.88592e+06); Du: 0(0.00205301) 58s
+          42353     7.8375259516e+03 Pr: 31443(8.49023e+06); Du: 0(0.00226205) 64s
+          45258     8.1692860958e+03 Pr: 37870(5.26412e+06); Du: 0(0.00255441) 69s
+          48083     8.3152673717e+03 Pr: 40961(1.14875e+07); Du: 0(0.00294817) 75s
+          50545     8.4510258333e+03 Pr: 30790(3.72272e+08); Du: 0(0.00304629) 80s
+          52861     8.5807507297e+03 Pr: 22492(930796); Du: 0(0.00320519) 85s
+          54980     8.6629986832e+03 Pr: 30776(2.08885e+07); Du: 0(0.00337039) 91s
+          57333     8.7549581833e+03 Pr: 26257(1.13046e+06); Du: 0(0.00357615) 96s
+          59389     8.8000584935e+03 Pr: 36678(2.40379e+06); Du: 0(0.00364701) 101s
+          61669     8.8767323679e+03 Pr: 35715(2.44371e+06); Du: 0(0.00390224) 107s
+          63914     8.9782186363e+03 Pr: 43654(3.60725e+06); Du: 0(0.00423026) 113s
+          66084     9.0603325781e+03 Pr: 22220(2.48776e+06); Du: 0(0.00410399) 118s
+          69022     9.1231092369e+03 Pr: 22727(1.78641e+06); Du: 0(0.00444875) 124s
+          71953     9.1538574545e+03 Pr: 22264(5.37124e+06); Du: 0(0.00461629) 129s
+          74846     9.1867207120e+03 Pr: 26970(1.49142e+06); Du: 0(0.00477614) 135s
+          77129     9.2058130738e+03 Pr: 0(0); Du: 0(0.00107487) 140s
+          77129     9.2058130738e+03 Pr: 0(0); Du: 0(0.00107487) 140s
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+          77129     9.2058130738e+03 Pr: 1(0.000235421); Du: 0(0.000836801) 140s
+          77136     9.2058130749e+03 Pr: 0(0); Du: 0(0.000836801) 140s
+          77136     9.2058130749e+03 Pr: 0(0); Du: 0(0.000836801) 140s
+    Solving the original LP from the solution after postsolve
+    Model   status      : Optimal
+    Simplex   iterations: 77136
+    Objective value     :  9.2058130749e+03
+    HiGHS run time      :        140.51
+    LP solved for primal
+
+    140.762363
highs_settings["run_crossover"] = "on"
+YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings)
+OPTIMIZER5 = GenX.configure_solver(setup["Solver"], settings_path);
+EP5 = GenX.generate_model(setup,inputs,OPTIMIZER5)
    Discharge Module
+    Non-served Energy Module
+    Investment Discharge Module
+    Unit Commitment Module
+    Emissions Module (for CO2 Policy modularization
+    Transmission Module
+    Dispatchable Resources Module
+    Storage Resources Module
+    Storage Investment Module
+    Storage Core Resources Module
+    Storage Resources with Symmetric Charge/Discharge Capacity Module
+    Thermal (Unit Commitment) Resources Module
+    C02 Policies Module
+    Minimum Capacity Requirement Module
+
+    A JuMP Model
+    Minimization problem with:
+    Variables: 83192
+    Objective function type: AffExpr
+    `AffExpr`-in-`MathOptInterface.EqualTo{Float64}`: 24024 constraints
+    `AffExpr`-in-`MathOptInterface.GreaterThan{Float64}`: 20334 constraints
+    `AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 103509 constraints
+    `VariableRef`-in-`MathOptInterface.EqualTo{Float64}`: 4 constraints
+    `VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 79492 constraints
+    Model mode: AUTOMATIC
+    CachingOptimizer state: EMPTY_OPTIMIZER
+    Solver name: HiGHS
+    Names registered in the model: cCO2Emissions_systemwide, cMaxCap, cMaxCapEnergy, cMaxCapEnergyDuration, cMaxFlow_in, cMaxFlow_out, cMaxLineReinforcement, cMaxNSE, cMaxRetCommit, cMaxRetEnergy, cMaxRetNoCommit, cMinCap, cMinCapEnergy, cMinCapEnergyDuration, cNSEPerSeg, cPowerBalance, cSoCBalInterior, cSoCBalStart, cTAuxLimit, cTAuxSum, cTLoss, cZoneMinCapReq, eAvail_Trans_Cap, eCFix, eCFixEnergy, eCNSE, eCStart, eCVar_in, eCVar_out, eELOSS, eELOSSByZone, eEmissionsByPlant, eEmissionsByZone, eExistingCap, eExistingCapEnergy, eGenerationByThermAll, eGenerationByVRE, eGenerationByZone, eLosses_By_Zone, eMinCapRes, eMinCapResInvest, eNet_Export_Flows, eObj, ePowerBalance, ePowerBalanceDisp, ePowerBalanceLossesByZone, ePowerBalanceNetExportFlows, ePowerBalanceNse, ePowerBalanceStor, ePowerBalanceThermCommit, eTotalCFix, eTotalCFixEnergy, eTotalCNSE, eTotalCNSET, eTotalCNSETS, eTotalCNetworkExp, eTotalCStart, eTotalCStartT, eTotalCVarIn, eTotalCVarInT, eTotalCVarOut, eTotalCVarOutT, eTotalCap, eTotalCapEnergy, eTransMax, vCAP, vCAPENERGY, vCHARGE, vCO2Cap_slack, vCOMMIT, vFLOW, vNEW_TRANS_CAP, vNSE, vP, vRETCAP, vRETCAPENERGY, vS, vSHUT, vSTART, vTAUX_NEG, vTAUX_POS, vTLOSS, vZERO
solution5 = @elapsed GenX.solve_model(EP5,setup)
    Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
+    Presolving model
+    123675 rows, 81174 cols, 478190 nonzeros
+    116575 rows, 74076 cols, 478470 nonzeros
+    Presolve : Reductions: rows 116575(-31292); columns 74076(-9116); elements 478470(-75676)
+    Solving the presolved LP
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+              0     1.9243583242e+03 Pr: 5545(9397.81); Du: 0(3.92542e-09) 0s
+          13530     2.9999821788e+03 Pr: 30131(9.49605e+06); Du: 0(0.0013131) 5s
+          17547     3.5484267823e+03 Pr: 19414(952045); Du: 0(0.00175439) 10s
+          20723     4.7298157079e+03 Pr: 29321(991206); Du: 0(0.00183106) 15s
+          23580     5.7123748112e+03 Pr: 32150(7.12339e+06); Du: 0(0.00148821) 21s
+          26170     6.1864339355e+03 Pr: 23825(7.35638e+06); Du: 0(0.00149712) 26s
+          29149     6.6441899572e+03 Pr: 27775(3.51868e+06); Du: 0(0.00148729) 31s
+          31348     6.8846964690e+03 Pr: 35484(2.8051e+06); Du: 0(0.00139557) 36s
+          33603     6.9920173954e+03 Pr: 24118(2.03558e+06); Du: 0(0.00124593) 42s
+          35671     7.2015870783e+03 Pr: 34108(1.48506e+07); Du: 0(0.00155189) 47s
+          37885     7.4048679609e+03 Pr: 31619(4.35123e+07); Du: 0(0.00179926) 52s
+          40109     7.6356641465e+03 Pr: 25843(2.88592e+06); Du: 0(0.00205301) 58s
+          42353     7.8375259516e+03 Pr: 31443(8.49023e+06); Du: 0(0.00226205) 63s
+          45258     8.1692860958e+03 Pr: 37870(5.26412e+06); Du: 0(0.00255441) 69s
+          48083     8.3152673717e+03 Pr: 40961(1.14875e+07); Du: 0(0.00294817) 75s
+          50545     8.4510258333e+03 Pr: 30790(3.72272e+08); Du: 0(0.00304629) 80s
+          52861     8.5807507297e+03 Pr: 22492(930796); Du: 0(0.00320519) 85s
+          54980     8.6629986832e+03 Pr: 30776(2.08885e+07); Du: 0(0.00337039) 91s
+          57333     8.7549581833e+03 Pr: 26257(1.13046e+06); Du: 0(0.00357615) 96s
+          59389     8.8000584935e+03 Pr: 36678(2.40379e+06); Du: 0(0.00364701) 101s
+          61669     8.8767323679e+03 Pr: 35715(2.44371e+06); Du: 0(0.00390224) 107s
+          63914     8.9782186363e+03 Pr: 43654(3.60725e+06); Du: 0(0.00423026) 113s
+          66084     9.0603325781e+03 Pr: 22220(2.48776e+06); Du: 0(0.00410399) 118s
+          69022     9.1231092369e+03 Pr: 22727(1.78641e+06); Du: 0(0.00444875) 124s
+          71953     9.1538574545e+03 Pr: 22264(5.37124e+06); Du: 0(0.00461629) 129s
+          74408     9.1814081583e+03 Pr: 29329(5.2193e+06); Du: 0(0.00473215) 134s
+          77012     9.2060247446e+03 Pr: 0(0); Du: 0(0.00509972) 140s
+          77129     9.2058130738e+03 Pr: 0(0); Du: 0(0.00107487) 140s
+          77129     9.2058130738e+03 Pr: 0(0); Du: 0(0.00107487) 140s
+    Using EKK dual simplex solver - serial
+      Iteration        Objective     Infeasibilities num(sum)
+          77129     9.2058130738e+03 Pr: 1(0.000235421); Du: 0(0.000836801) 140s
+          77136     9.2058130749e+03 Pr: 0(0); Du: 0(0.000836801) 140s
+          77136     9.2058130749e+03 Pr: 0(0); Du: 0(0.000836801) 140s
+    Solving the original LP from the solution after postsolve
+    Model   status      : Optimal
+    Simplex   iterations: 77136
+    Objective value     :  9.2058130749e+03
+    HiGHS run time      :        140.44
+    LP solved for primal
+
+    140.74829025
diff --git a/previews/PR670/Tutorials/Tutorials_intro/index.html b/previews/PR670/Tutorials/Tutorials_intro/index.html new file mode 100644 index 0000000000..b0382a23be --- /dev/null +++ b/previews/PR670/Tutorials/Tutorials_intro/index.html @@ -0,0 +1,2 @@ + +Tutorials Overview · GenX.jl
diff --git a/previews/PR670/Tutorials/files/LatexHierarchy.png b/previews/PR670/Tutorials/files/LatexHierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..05981f99c061b62c12eec392a9e3a766270c351a GIT binary patch literal 93747 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