From e9b142dcada04b5e9866d06ce43cdd4a74817b74 Mon Sep 17 00:00:00 2001 From: Luca Bonaldo <39280783+lbonaldo@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:28:42 -0400 Subject: [PATCH 1/7] Fix ret_cap_charge in load_resources (#646) --- src/load_inputs/load_resources_data.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/load_inputs/load_resources_data.jl b/src/load_inputs/load_resources_data.jl index 57bfebd697..58fb7896c9 100644 --- a/src/load_inputs/load_resources_data.jl +++ b/src/load_inputs/load_resources_data.jl @@ -1008,7 +1008,7 @@ function add_resources_to_input_data!(inputs::Dict, setup::Dict, case_path::Abst # Set of asymmetric charge/discharge storage resources eligible for new charge capacity new_cap_charge = intersect(buildable, ids_with(gen, max_charge_cap_mw), inputs["STOR_ASYMMETRIC"]) # Set of asymmetric charge/discharge storage resources eligible for charge capacity retirements - ret_cap_charge = intersect(buildable, ids_with_nonneg(gen, existing_charge_cap_mw), inputs["STOR_ASYMMETRIC"]) + ret_cap_charge = intersect(retirable, ids_with_nonneg(gen, existing_charge_cap_mw), inputs["STOR_ASYMMETRIC"]) end inputs["NEW_CAP_CHARGE"] = new_cap_charge inputs["RET_CAP_CHARGE"] = ret_cap_charge From 692a935a96890b0ee1ceecc5cd0f1c687b015066 Mon Sep 17 00:00:00 2001 From: Luca Bonaldo <39280783+lbonaldo@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:11:53 -0400 Subject: [PATCH 2/7] Fix write_status with UCommit=1 and shadow prices (#645) --- CHANGELOG.md | 1 + src/model/solve_model.jl | 8 -------- src/write_outputs/write_outputs.jl | 10 ++++++++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0673a798..494f1a8083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix cost assignment to virtual storage charge/discharge - issue #604 (#608) - Fix modeling of hydro reservoir with long duration storage (#572). - Fix update of starting transmission capacity in multistage GenX +- Fix write_status with UCommit = WriteShadowPrices = 1 (#645) ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). diff --git a/src/model/solve_model.jl b/src/model/solve_model.jl index 53f385c38f..2693cea662 100644 --- a/src/model/solve_model.jl +++ b/src/model/solve_model.jl @@ -70,14 +70,6 @@ function solve_model(EP::Model, setup::Dict) println("MILP solved for primal") end - if !has_duals(EP) && setup["WriteShadowPrices"] == 1 - # function to fix integers and linearize problem - fix_integers(EP) - # re-solve statement for LP solution - println("Solving LP solution for duals") - optimize!(EP) - end - ## Record solver time solver_time = time() - solver_start_time elseif setup["ComputeConflicts"]==0 diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 46d5838246..8e88ec0dc1 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -47,6 +47,16 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic output_settings_d["WriteStatus"] && write_status(path, inputs, setup, EP) + # linearize and re-solve model if duals are not available but ShadowPrices are requested + if !has_duals(EP) && setup["WriteShadowPrices"] == 1 + # function to fix integers and linearize problem + fix_integers(EP) + # re-solve statement for LP solution + println("Solving LP solution for duals") + set_silent(EP) + optimize!(EP) + end + if output_settings_d["WriteCosts"] elapsed_time_costs = @elapsed write_costs(path, inputs, setup, EP) println("Time elapsed for writing costs is") From 561d1b154125c50c2195803681689338575f5efa Mon Sep 17 00:00:00 2001 From: Luca Bonaldo <39280783+lbonaldo@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:51:38 -0400 Subject: [PATCH 3/7] Update interface and multistage input for vre_stor (#648) --- .../policies/CO2_cap.csv | 4 ++++ .../policies/Minimum_capacity_requirement.csv | 4 ++++ src/load_inputs/load_resources_data.jl | 4 ++-- src/model/resources/resources.jl | 9 +++------ src/model/utility.jl | 2 +- src/multi_stage/configure_multi_stage_inputs.jl | 14 +++++++------- 6 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 example_systems/2_three_zones_w_electrolyzer/policies/CO2_cap.csv create mode 100644 example_systems/2_three_zones_w_electrolyzer/policies/Minimum_capacity_requirement.csv diff --git a/example_systems/2_three_zones_w_electrolyzer/policies/CO2_cap.csv b/example_systems/2_three_zones_w_electrolyzer/policies/CO2_cap.csv new file mode 100644 index 0000000000..fbd59924ee --- /dev/null +++ b/example_systems/2_three_zones_w_electrolyzer/policies/CO2_cap.csv @@ -0,0 +1,4 @@ +,Network_zones,CO_2_Cap_Zone_1,CO_2_Cap_Zone_2,CO_2_Cap_Zone_3,CO_2_Max_tons_MWh_1,CO_2_Max_tons_MWh_2,CO_2_Max_tons_MWh_3,CO_2_Max_Mtons_1,CO_2_Max_Mtons_2,CO_2_Max_Mtons_3 +MA,z1,1,0,0,0.05,0,0,0.018,0,0 +CT,z2,0,1,0,0,0.05,0,0,0.025,0 +ME,z3,0,0,1,0,0,0.05,0,0,0.025 diff --git a/example_systems/2_three_zones_w_electrolyzer/policies/Minimum_capacity_requirement.csv b/example_systems/2_three_zones_w_electrolyzer/policies/Minimum_capacity_requirement.csv new file mode 100644 index 0000000000..bd16edeeb3 --- /dev/null +++ b/example_systems/2_three_zones_w_electrolyzer/policies/Minimum_capacity_requirement.csv @@ -0,0 +1,4 @@ +MinCapReqConstraint,ConstraintDescription,Min_MW +1,MA_PV,5000 +2,CT_Wind,10000 +3,All_Batteries,6000 diff --git a/src/load_inputs/load_resources_data.jl b/src/load_inputs/load_resources_data.jl index 58fb7896c9..a7cfe84aa5 100644 --- a/src/load_inputs/load_resources_data.jl +++ b/src/load_inputs/load_resources_data.jl @@ -801,7 +801,7 @@ function process_piecewisefuelusage!(setup::Dict, gen::Vector{<:AbstractResource inputs["intercept_cols"] = intercept_cols inputs["PWFU_data"] = PWFU_data inputs["PWFU_Num_Segments"] = num_segments - inputs["THERM_COMMIT_PWFU"] = intersect(ids_with_unit_commitment(gen), resource_id.(gen[HAS_PWFU])) + inputs["THERM_COMMIT_PWFU"] = intersect(ids_with_unit_commitment(gen), HAS_PWFU) @info "Piecewise fuel usage data successfully read!" end @@ -898,7 +898,7 @@ function add_resources_to_input_data!(inputs::Dict, setup::Dict, case_path::Abst inputs["HYDRO_RES"] = hydro(gen) # Set of hydro resources modeled with known reservoir energy capacity if !isempty(inputs["HYDRO_RES"]) - inputs["HYDRO_RES_KNOWN_CAP"] = intersect(inputs["HYDRO_RES"], ids_with(gen, hydro_energy_to_power_ratio)) + inputs["HYDRO_RES_KNOWN_CAP"] = intersect(inputs["HYDRO_RES"], ids_with_positive(gen, hydro_energy_to_power_ratio)) end ## STORAGE diff --git a/src/model/resources/resources.jl b/src/model/resources/resources.jl index aed64a9f94..9d7462f95a 100644 --- a/src/model/resources/resources.jl +++ b/src/model/resources/resources.jl @@ -795,11 +795,8 @@ for attr in (:max_cap_solar_mw, :max_cap_charge_dc_mw, :max_cap_charge_ac_mw, :max_cap_discharge_dc_mw, - :max_cap_discharge_ac_mw) - @eval @interface $attr -end - -for attr in (:min_cap_solar_mw, + :max_cap_discharge_ac_mw, + :min_cap_solar_mw, :min_cap_wind_mw, :min_cap_inverter_mw, :min_cap_charge_dc_mw, @@ -808,7 +805,7 @@ for attr in (:min_cap_solar_mw, :min_cap_discharge_ac_mw, :inverter_ratio_solar, :inverter_ratio_wind,) - @eval @interface $attr + @eval @interface $attr default_minmax_cap end for attr in (:etainverter, diff --git a/src/model/utility.jl b/src/model/utility.jl index 8b1d06a7b6..22e7329e9b 100644 --- a/src/model/utility.jl +++ b/src/model/utility.jl @@ -79,7 +79,7 @@ end function by_rid_res(rid::Integer, sym::Symbol, rs::Vector{<:AbstractResource}) r = rs[findfirst(resource_id.(rs) .== rid)] # use getter function for attribute `sym` if exists in GenX, otherwise get the attribute directly - f = isdefined(GenX, sym) ? getfield(GenX, sym) : r -> getproperty(r, sym) + f = isdefined(GenX, sym) ? getfield(GenX, sym) : x -> getproperty(x, sym) return f(r) end diff --git a/src/multi_stage/configure_multi_stage_inputs.jl b/src/multi_stage/configure_multi_stage_inputs.jl index 204094ee9a..870d98cdca 100644 --- a/src/multi_stage/configure_multi_stage_inputs.jl +++ b/src/multi_stage/configure_multi_stage_inputs.jl @@ -105,13 +105,13 @@ function configure_multi_stage_inputs(inputs_d::Dict, settings_d::Dict, NetworkE gen_VRE_STOR.inv_cost_discharge_ac_per_mwyr = compute_overnight_capital_cost(settings_d, inv_cost_discharge_ac_per_mwyr.(gen_VRE_STOR), capital_recovery_period_discharge_ac.(gen_VRE_STOR), tech_wacc_discharge_ac.(gen_VRE_STOR)) gen_VRE_STOR.inv_cost_charge_ac_per_mwyr = compute_overnight_capital_cost(settings_d, inv_cost_charge_ac_per_mwyr.(gen_VRE_STOR), capital_recovery_period_charge_ac.(gen_VRE_STOR), tech_wacc_charge_ac.(gen_VRE_STOR)) - gen_VRE_STOR.fixed_om_inverter_cost_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_solar_cost_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_wind_cost_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_cost_discharge_dc_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_cost_charge_dc_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_cost_discharge_ac_per_mwyr .*= OPEXMULT - gen_VRE_STOR.fixed_om_cost_charge_ac_per_mwyr .*= OPEXMULT + gen_VRE_STOR.fixed_om_inverter_cost_per_mwyr = fixed_om_inverter_cost_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_solar_cost_per_mwyr = fixed_om_solar_cost_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_wind_cost_per_mwyr = fixed_om_wind_cost_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_cost_discharge_dc_per_mwyr = fixed_om_cost_discharge_dc_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_cost_charge_dc_per_mwyr = fixed_om_cost_charge_dc_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_cost_discharge_ac_per_mwyr = fixed_om_cost_discharge_ac_per_mwyr.(gen_VRE_STOR) .* OPEXMULT + gen_VRE_STOR.fixed_om_cost_charge_ac_per_mwyr = fixed_om_cost_charge_ac_per_mwyr.(gen_VRE_STOR) .* OPEXMULT end end From 5462c79e51c501cb4ce25c469e17a8f1a816801f Mon Sep 17 00:00:00 2001 From: Luca Bonaldo <39280783+lbonaldo@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:54:15 -0400 Subject: [PATCH 4/7] Update CI to regularly test examples (#644) --- .github/workflows/test_example.yml | 27 ----------------- .github/workflows/test_examples.yml | 46 +++++++++++++++++++++++++++++ test/test_examples.jl | 28 ++++++++++++++++++ test/utilities.jl | 15 ++++++++++ 4 files changed, 89 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/test_example.yml create mode 100644 .github/workflows/test_examples.yml create mode 100644 test/test_examples.jl diff --git a/.github/workflows/test_example.yml b/.github/workflows/test_example.yml deleted file mode 100644 index 519cebb9d4..0000000000 --- a/.github/workflows/test_example.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Test-example - -on: - push: - schedule: - - cron: 21 4 * * * # Run at 12:21am US Eastern time - -jobs: - test: - strategy: - matrix: - branch: ["main", "develop"] - version: ["1.8", "1.9"] - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ matrix.branch }} - - uses: julia-actions/cache@v1.2.2 - - uses: julia-actions/setup-julia@latest - with: - version: ${{ matrix.version }} - - uses: julia-actions/julia-buildpkg@v1 - - name: Test an example case - run: | - julia --project=. Example_Systems/SmallNewEngland/Simple_Test_Case/Run.jl \ No newline at end of file diff --git a/.github/workflows/test_examples.yml b/.github/workflows/test_examples.yml new file mode 100644 index 0000000000..d0f4b37755 --- /dev/null +++ b/.github/workflows/test_examples.yml @@ -0,0 +1,46 @@ +name: Test-examples + +on: + push: + schedule: + - cron: 21 4 * * * # Run at 12:21am US Eastern time + +jobs: + test: + strategy: + matrix: + branch: ["main", "develop"] + version: ["1.8", "1.9", '1'] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ matrix.branch }} + - uses: julia-actions/cache@v1.2.2 + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.version }} + - uses: julia-actions/julia-buildpkg@v1 + - name: Test examples + shell: julia --project=. --color=yes {0} + run: | + using GenX + using Test + + base_path = Base.dirname(Base.dirname(pathof(GenX))) + examples_path = joinpath(base_path, "example_systems") + + if !isdir(examples_path) + @warn "No example systems found in $examples_path" + exit(0) + end + + @testset "Test examples" begin + for example_dir in readdir(examples_path, join=true) + if isdir(example_dir) && isfile(joinpath(example_dir, "Run.jl")) + @info "Running example in $example_dir" + @test isnothing(run_genx_case!(example_dir)) + end + end + end \ No newline at end of file diff --git a/test/test_examples.jl b/test/test_examples.jl new file mode 100644 index 0000000000..ed060f9ca1 --- /dev/null +++ b/test/test_examples.jl @@ -0,0 +1,28 @@ +module TestExamples + +using Test +using GenX + +include(joinpath(@__DIR__, "utilities.jl")) + + +# Test that the examples in the example_systems directory run without error +function test_examples() + base_path = Base.dirname(Base.dirname(pathof(GenX))) + examples_path = joinpath(base_path, "example_systems") + + examples_dir = readdir(examples_path, join=true) + for example_dir in examples_dir + if isdir(example_dir) && isfile(joinpath(example_dir, "Run.jl")) + @info "Running example in $example_dir" + result = @warn_error_logger run_genx_case!(example_dir) + @test isnothing(result) + end + end +end + +@testset "Test examples" begin + test_examples() +end + +end # module \ No newline at end of file diff --git a/test/utilities.jl b/test/utilities.jl index c8e573d533..43417f5462 100644 --- a/test/utilities.jl +++ b/test/utilities.jl @@ -241,4 +241,19 @@ function isapprox_col(col1, col2) return isapprox_col end return false +end + + +macro warn_error_logger(block) + quote + result = nothing + redirect_stdout(devnull) do + # Create a ConsoleLogger that prints any log messages with level >= Warn to stderr + warnerror_logger = ConsoleLogger(stderr, Logging.Warn) + with_logger(warnerror_logger) do + result = $(esc(block)) + end + end + result + end end \ No newline at end of file From e043a389aa72d455f93e8a566d8419feff4d410a Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Sun, 17 Mar 2024 20:53:16 -0400 Subject: [PATCH 5/7] Update tutorials, fix readme.md --- README.md | 176 +------------ docs/make.jl | 6 +- .../Tutorial_1_configuring_settings.md | 223 +++++++++++++++- .../Tutorial_2_network_visualization.md | 6 +- ...utorial_3_K-means_time_domain_reduction.md | 243 ++++++++++-------- .../Tutorials/Tutorial_4_model_generation.md | 103 +++++--- docs/src/Tutorials/Tutorial_5_solve_model.md | 83 +----- .../Tutorials/Tutorial_6_solver_settings.md | 140 +++++----- docs/src/Tutorials/files/output_58_0.svg | 1 + docs/src/Tutorials/files/output_65_0.svg | 1 + docs/src/User_Guide/TDR_input.md | 2 +- docs/src/User_Guide/running_TDR.md | 12 +- docs/src/index.md | 18 +- 13 files changed, 518 insertions(+), 496 deletions(-) create mode 100644 docs/src/Tutorials/files/output_58_0.svg create mode 100644 docs/src/Tutorials/files/output_65_0.svg diff --git a/README.md b/README.md index a2eb361fcd..a37a100c1c 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,7 @@ The 'main' branch is the current master branch of GenX. The various subdirectori 1. `src/` Contains the core GenX model code for reading inputs, model generation, solving and writing model outputs. -2. `Example_Systems/` Contains fully specified examples that users can use to test GenX and get familiar with its various features. Within this folder, we have two main sets of examples: - - `SmallNewEngland/` , a simplified system consisting of 4 different resources per zone. - - `RealSystemExample/`, a detailed system representation based on ISO New England and including many different resources (up to 58) +2. `example_systems/` Contains fully specified examples that users can use to test GenX and get familiar with its various features. 3. `docs/` Contains source files for documentation pertaining to the model. @@ -54,10 +52,8 @@ We also provide the option to use one of these two commercial solvers: (D) [Gurobi](https://www.gurobi.com), or (E) [CPLEX](https://www.ibm.com/analytics/cplex-optimizer). Note that using Gurobi and CPLEX requires a valid license on the host machine. -There are two ways to run GenX with either type of solver options (open-source free or, licensed commercial) as detailed in the section, `Running an Instance of GenX`. +There are two ways to run GenX with either type of solver options (open-source free or, licensed commercial) as detailed in the section, `Getting Started`. -The file `Project.toml` in the parent directory lists all of the packages and their versions needed to run GenX. -You can see all of the packages installed in your Julia environment and their version numbers by running `pkg> status` on the package manager command line in the Jula REPL. ## Documentation @@ -65,161 +61,8 @@ Detailed documentation for GenX can be found [here](https://genxproject.github.i It includes details of each of GenX's methods, required and optional input files, and outputs. Interested users may also want to browse through [prior publications](https://energy.mit.edu/genx/#publications) that have used GenX to understand the various features of the tool. -## Running an Instance of GenX -1. Download or clone the GenX repository on your machine. -For this tutorial it will be assumed to be within your home directory: `/home/youruser/GenX`. -### Creating the Julia environment and installing dependencies -You could either start from a default terminal or a Julia REPL terminal. -#### For a default terminal: -2. Start a terminal and navigate into the `GenX` folder. -3. Type `julia --project=.` to start an instance of the `julia` kernel with the `project` set to the current folder. -The `.` indicates the current folder. On Windows the location of Julia can also be specified as e.g., 'C:\julia-1.6.0\bin\julia.exe --project=.' - If it's your first time running GenX (or, if you have pulled after some major upgrades/release/version) execute steps 3-6. - -4. Type `]` to bring up the package system `(GenX) pkg >` prompt. This indicates that the GenX project was detected. If you see `(@v1.6) pkg>` as the prompt, then the `project` was not successfully set. -5. Type `instantiate` from the `(GenX) pkg` prompt. - On Windows there is an issue with the prepopulated MUMPS_seq_jll v5.5.1 that prevents compilation of the solvers. To avoid this issue type 'add MUMPS_seq_jll@5.4.1' after running instantiate. -6. Type `st` to check that the dependecies have been installed. If there is no error, it has been successful. -7. Type the back key to come back to the `julia>` prompt. - - These steps can be skipped on subsequent runs. - - Steps 2-5 are shown in Figure 1 and Steps 6-8 are shown in Figure 2. - - ![Creating the Julia environment and installing dependencies: Steps 2-7](docs/src/assets/GenX_setup_tutorial_part_1.png) - *Figure 1. Creating the Julia environment and installing dependencies from Project.toml file from inside the GenX folder: Steps 2-5* - -8. Since we have already started Julia, we can run a case by executing the command `julia> include(“/Run.jl”)`. - -For example, in order to run the OneZone case within the `Example_Systems/SmallNewEngland` folder, -type `include("Example_Systems/SmallNewEngland/OneZone/Run.jl")` from the `julia>` prompt. - -![Creating the Julia environment and installing dependencies: Steps 6-8](docs/src/assets/GenX_setup_tutorial_part_2.png) -*Figure 2. Creating the Julia environment and installing dependencies from Project.toml file from inside the GenX folder: Steps 6-8* - -After the script runs to completion, results will be written to a folder called “Results”, located in the same directory as `Run.jl`. - -#### For a Julia REPL terminal: -2. Open your desired version of Julia -3. In the Julia terminal, enter pkg manager mode by typing ] -Activate the project by typing activate /path/to/GenX -4. Type `instantiate` from the `(GenX) pkg` prompt. - On Windows there is an issue with the prepopulated MUMPS_seq_jll v5.5.1 that prevents compilation of the solvers. To avoid this issue type 'add MUMPS_seq_jll@5.4.1' after running instantiate. -5. Type `st` to check that the dependecies have been installed. If there is no error, it has been successful. -6. Type the back key to come back to the `julia>` prompt. -7. Since we have already started Julia, we can run a case by executing the command `julia> include(“/Run.jl”)`. - -For example, in order to run the OneZone case within the `Example_Systems/SmallNewEngland` folder, -type `include("Example_Systems/SmallNewEngland/OneZone/Run.jl")` from the `julia>` prompt. - - -### Running a case - -Once Steps 1-6 have been performed, a case can be run from the terminal in a single line. -There's no need to be in a certain folder to run a case, but it is required to point `julia` to the project that you created. - -For example, from inside the `GenX` folder: -`/home/youruser/GenX > julia --project=. /home/youruser/GenX/Example_Systems/SmallNewEngland/OneZone/Run.jl` - -Or from another folder - -`/arbitrary/location > julia --project="/home/youruser/GenX" /home/youruser/GenX/Example_Systems/SmallNewEngland/OneZone/Run.jl` - -In fact, a best practice is to place your cases outside of the GenX repository: - -`/arbitrary/location > julia --project="/home/youruser/GenX" /your/custom/case/Run.jl` - -### What happens when you run a case - -The Run.jl file in each of the example systems calls a function `run_genx_case!("path/to/case")` which is suitable for capacity expansion modeling of several varieties. -The following are the main steps performed in that 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. - -If your needs are more complex, it is possible to use a customized run script in place of simply calling `run_genx_case!`; the contents of that function could be a starting point. - -### Using commercial solvers: Gurobi or CPLEX -If you want to use the commercial solvers Gurobi or CPLEX: - -1. Make sure you have a valid license and the actual solvers for either of Gurobi or CPLEX installed on your machine -2. Add Gurobi or CPLEX to your Julia installation - -``` -> julia - -julia> -(@v1.9) pkg> add Gurobi --or- -(@v1.9) pkg> add CPLEX -``` - -3. Set the appropriate solver in the `genx_settings.yml` file of your case -4. In the `Run.jl` file for your case, add `using Gurobi` or `using CPLEX` to the dependencies at the top, and specify the optimizer object as the second argument to `run_genx_case!`. Below is an example `Run.jl` file for a case using the Gurobi optimizer: - -``` -using GenX, Gurobi - -optimizer = Gurobi.Optimizer - -run_genx_case!(dirname(@__FILE__),optimizer) -``` - -A full example case with Gurobi as an optimizer is given in `Example_Systems/SmallNewEngland/OneZone_Gurobi`. Note that if you have not already installed the required Julia packages or you do not have a valid Gurobi/CPLEX license on your host machine, you will receive an error message and Run.jl will not run to completion. - - -## Running Modeling to Generate Alternatives with GenX -GenX includes a modeling to generate alternatives (MGA) package that can be used to automatically enumerate a diverse set of near cost-optimal solutions to electricity system planning problems. To use the MGA algorithm, user will need to perform the following tasks: - -1. Add a `Resource_Type` column in the resource `.csv` files (included in the `resources` folder) denoting the type of each technology. -2. Add a `MGA` column in the resource `.csv` files (included in the `resources` folder) denoting the availability of the technology. -3. Set the `ModelingToGenerateAlternatives` flag in the `GenX_Settings.yml` file to 1. -4. Set the `ModelingtoGenerateAlternativeSlack` flag in the `GenX_Settings.yml` file to the desirable level of slack. -5. Create a `Rand_mga_objective_coefficients.csv` file to provide random objective function coefficients for each MGA iteration. - For each iteration, number of rows in the `Rand_mga_objective_coefficients.csv` file represents the number of distinct technology types while number of columns represent the number of model zones. -6. Solve the model using `Run.jl` file. - -Results from the MGA algorithm would be saved in `MGA_max` and `MGA_min` folders in the `Example_Systems/` folder. - -# Limitations of the GenX Model - -While the benefits of an openly available generation and transmission expansion model are high, many approximations have been made due to missing data or to manage computational tractability. -The assumptions of the GenX model are listed below. -It serves as a caveat to the user and as an encouragement to improve the approximations. - -## Time period -GenX makes the simplifying assumption that each time period contains n copies of a single, representative year. -GenX optimizes generation and transmission capacity for just this characteristic year within each time period, assuming the results for different years in the same time period are identical. -However, the GenX objective function accounts only for the cost of the final model time period. - -## Cost -The GenX objective function assumes that the cost of powerplants is specified in the unit of currency per unit of capacity. -GenX also assumes that the capital cost of technologies is paid through loans. - -## Market -GenX is a bottom-up (technology-explicit), partial equilibrium model that assumes perfect markets for commodities. -In other words, each commodity is produced such that the sum of producer and consumer surplus is maximized. - -## Technology -Behavioral response and acceptance of new technology are often modeled simplistically as a discount rate or by externally fixing the technology capacity. -A higher, technology-specific discount rate represents consumer reluctance to accept newer technologies. - -## Uncertainty -Because each model realization assumes a particular state of the world based on the input values drawn, the parameter uncertainty is propagated through the model in the case of myopic model runs - -## Decision-making -GenX assumes rational decision making, with perfect information and perfect foresight, and simultaneously optimizes all decisions over the user-specified time horizon. - -## Demand -GenX assumes price-elastic demand segments that are represented using piece-wise approximation rather than an inverse demand curve to keep the model linear. - -# How to cite GenX +## How to cite GenX We request that users of GenX to cite it in their academic publications and patent filings. @@ -227,19 +70,6 @@ We request that users of GenX to cite it in their academic publications and pate MIT Energy Initiative and Princeton University ZERO lab. GenX: a configurable power system capacity expansion model for studying low-carbon energy futures n.d. https://github.com/GenXProject/GenX ``` -# pygenx: Python interface for GenX - -Python users can now run GenX from a thin-python-wrapper interface, developed by [Daniel Olsen](https://github.com/danielolsen). -This tool is called `pygenx` and can be cloned from the github page: [pygenx](https://github.com/danielolsen/pygenx). -It needs installation of Julia 1.3 and a clone of GenX repo along with your python installation. - -## Simple GenX Case Runner: For automated sequential batch run for GenX - -It is now possible to run a list of GenX cases as separate batch jobs. -Alternatively, they can also be run locally in sequence, as one job. -It has been developed by [Jacob Schwartz](https://github.com/cfe316). -This tool is called `SimpleGenXCaseRunner` and can be cloned from the github page: [SimpleGenXCaseRunner](https://github.com/cfe316/SimpleGenXCaseRunner) - ## Bug and feature requests and contact info If you would like to report a bug in the code or request a feature, please use our [Issue Tracker](https://github.com/GenXProject/GenX/issues). If you're unsure or have questions on how to use GenX that are not addressed by the above documentation, please reach out to Sambuddha Chakrabarti (sc87@princeton.edu), Jesse Jenkins (jdj2@princeton.edu) or Dharik Mallapragada (dharik@mit.edu). diff --git a/docs/make.jl b/docs/make.jl index 3ae1ee6452..0cb38a76a9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -29,8 +29,8 @@ pages = OrderedDict( "Model Configuration" => "User_Guide/model_configuration.md", "Solver Configuration" => "User_Guide/solver_configuration.md", "Model Inputs" => "User_Guide/model_input.md", - "TDR Inputs" => "User_Guide/TDR_input.md", - "Running the TDR" => "User_Guide/running_TDR.md", + "Time-domain Reduction Inputs" => "User_Guide/TDR_input.md", + "Running the Time-domain Reduction" => "User_Guide/running_TDR.md", "MGA package" => "User_Guide/generate_alternatives.md", "Multi-stage Model" => "User_Guide/multi_stage_input.md", "Slack Variables for Policies" => "User_Guide/slack_variables_overview.md", @@ -80,7 +80,7 @@ pages = OrderedDict( "Inputs Functions" => "Model_Reference/load_inputs.md", "Generate the Model" => "Model_Reference/generate_model.md", "Solving the Model" => "Model_Reference/solve_model.md", - "TDR" => "Model_Reference/TDR.md", + "Time-domain Reduction" => "Model_Reference/TDR.md", "Outputs Functions" => "Model_Reference/write_outputs.md", "Modeling to Generate Alternatives" => "Model_Reference/mga.md", "Multi-stage" => [ diff --git a/docs/src/Tutorials/Tutorial_1_configuring_settings.md b/docs/src/Tutorials/Tutorial_1_configuring_settings.md index 4267a92308..8ba1968665 100644 --- a/docs/src/Tutorials/Tutorial_1_configuring_settings.md +++ b/docs/src/Tutorials/Tutorial_1_configuring_settings.md @@ -1,12 +1,13 @@ # Tutorial 1: Configuring Settings -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_1_Configuring_Settings.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_1_Configuring_Settings.ipynb) 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 here in 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. +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 +](@ref) 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. @@ -58,7 +59,7 @@ genx_settings_SNE = YAML.load(open("example_systems/1_three_zones/settings/genx_ ``` -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 `SmallNewEngland/OneZone` to contain no parameters: +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: ```julia @@ -106,7 +107,7 @@ include("example_systems/1_three_zones/Run.jl") ------------------------------------------------------- Generators_variability.csv Successfully Read! Validating time basis - CSV Files Successfully Read In From /Users/lb9239/Documents/ZERO_lab/GenX/GenX-Tutorials/Tutorials/example_systems/1_three_zones + CSV Files Successfully Read In From GenX-Tutorials/Tutorials/example_systems/1_three_zones Generating the Optimization Model Discharge Module Non-served Energy Module @@ -121,8 +122,211 @@ include("example_systems/1_three_zones/Run.jl") 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. ```julia using CSV @@ -130,11 +334,16 @@ using DataFrames results = CSV.read(open("example_systems/1_three_zones/Results/capacity.csv"),DataFrame) ``` -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. +```@raw html +
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: ```julia -YAML.write_file("example_systems/1_three_zones/settings/genx_settings.yml", genx_settings_SNE) +YAML.write_file("example_systems/1_three_zones/settings/genx_settings.yml", genx_settings_TZ) ``` diff --git a/docs/src/Tutorials/Tutorial_2_network_visualization.md b/docs/src/Tutorials/Tutorial_2_network_visualization.md index eaef940275..67624d285f 100644 --- a/docs/src/Tutorials/Tutorial_2_network_visualization.md +++ b/docs/src/Tutorials/Tutorial_2_network_visualization.md @@ -1,8 +1,8 @@ # Tutorial 2: Network Visualization -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_2_Network_Visualization.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_2_Network_Visualization.ipynb) -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 here in the documentation under "Inputs". This tutorial helps visualize the file `Network.csv` using the example system `SmallNewEngland/ThreeZones`. +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](@ref) page of the documentation. This tutorial helps visualize the file `Network.csv` using the example system `example_systems/1_three_zones`. ```julia @@ -10,7 +10,7 @@ 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 `SmallNewEngland/ThreeZones`: +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`: ```julia diff --git a/docs/src/Tutorials/Tutorial_3_K-means_time_domain_reduction.md b/docs/src/Tutorials/Tutorial_3_K-means_time_domain_reduction.md index b742a0e244..4e9a49a6e6 100644 --- a/docs/src/Tutorials/Tutorial_3_K-means_time_domain_reduction.md +++ b/docs/src/Tutorials/Tutorial_3_K-means_time_domain_reduction.md @@ -1,9 +1,9 @@ # Tutorial 3: K-Means and Time Domain Reduction -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_3_K-means_Time_Domain_Reduction.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_3_K-means_Time_Domain_Reduction.ipynb) -A good tool to reduce computation time of GenX is to use [Time Domain Reduction (TDR)](https://genxproject.github.io/GenX/dev/methods/#Time-Domain-Reduction-(TDR)). 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](https://www.sciencedirect.com/science/article/pii/S0360544218315238). +A good tool to reduce computation time of GenX is to use [Time-domain reduction](@ref). 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](https://www.sciencedirect.com/science/article/pii/S0360544218315238). ### Table of Contents * [Time Domain Reduction](#TDR) @@ -128,6 +128,23 @@ 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: @@ -274,26 +291,17 @@ Period_map = CSV.read(joinpath(case,"TDR_Results/Period_map.csv"),DataFrame,miss ```julia # 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 = [] -weeks_wind = [] 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"]] - week_temp_wind = [repeat([i],168) generators[(168*i-167):168*i,"Time_Index"] generators[(168*i-167):168*i,"CT_onshore_Wind"]] weeks_load = [weeks_load; week_temp_loads] - weeks_wind = [weeks_wind; week_temp_wind] end -``` - -```julia # Combine with Total (pre TDR) loads_plot = [repeat(["Total"],8760) loads[!,"Time_Index"] loads[!,"Demand_MW_z1"]]; -wind_plot = [repeat(["Total"],8760) generators[!,"Time_Index"] generators[!,"CT_onshore_Wind"]] -``` - -```julia # Add column names and convert column type loads_with_TDR = [loads_plot; weeks_load] loads_with_TDR = DataFrame(loads_with_TDR ,["Week","hour", "MW"]) @@ -302,17 +310,6 @@ loads_with_TDR[!,:MW] = convert.(Float64,loads_with_TDR[!,:MW]); ``` -```julia -wind_with_TDR = [wind_plot; weeks_wind] -wind_with_TDR = DataFrame(wind_with_TDR ,["Week","hour", "MW"]) -wind_with_TDR[!,:hour] = convert.(Int64,wind_with_TDR[!,:hour]); -wind_with_TDR[!,:MW] = convert.(Float64,wind_with_TDR[!,:MW]); -wind_with_TDR -``` - -Here's the same plot from before but with representative weeks highlighted: - - ```julia loads_with_TDR |> @vlplot(mark={:line}, @@ -320,55 +317,10 @@ loads_with_TDR |> color={"Week:n", scale={scheme="paired"},sort="decsending"}, title="MW Load per hour with TDR Representative Weeks", width=845,height=400) ``` +![svg](./files/output_58_0.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. -```julia -# F: what units? -wind_with_TDR |> -@vlplot(mark={:line}, - x={:hour,title="Time Step (hours)",labels="Week:n"}, y={:MW,title="Variability",scale={domain=[-.1,1.2]}}, - color={"Week:n", scale={scheme="paired"},sort="decsending"},title="Onshore Wind Variability per hour with TDR Representative Weeks", - width=845,height=400) -``` - - -```julia -# TO DO: Plot other loads? -# May be better to show wind instead of load -- generators variability -# Plot representative periods -- reconstructed time series -``` - -#### Reconstruction - -Below is a plot of a reconstruction of the data using only the weeks isolated as representative periods. - - -```julia -recon = [] -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] -end -``` - - -```julia -recon = DataFrame(recon,["Week","hour", "MW"]) -recon[!,:hour] = convert.(Int64,recon[!,:hour]); -recon[!,:MW] = convert.(Float64,recon[!,:MW]); -``` - - -```julia -recon |> -@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 Reconstructed", - width=845,height=400) -``` - -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 [last section](#ObjVals) of this Tutorial goes over how the optimal values of the data change as the number of representative periods changes. ### Extreme Periods Off @@ -421,6 +373,9 @@ loads_with_TDR2[!,:MW] = convert.(Float64,loads_with_TDR2[!,:MW]); ```julia +# 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)"}, @@ -429,74 +384,141 @@ loads_with_TDR2[!,:MW] = convert.(Float64,loads_with_TDR2[!,:MW]); width=845,height=300) ``` +![svg](./files/output_65_0.svg) -```julia -recon[!,"Extreme_Periods"] = repeat(["On"],length(recon[!,1])); -``` +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: ```julia -length(recon_noex) +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. + ```julia +recon = [] recon_noex = [] for i in range(1,52) - index = Period_map2[i,"Rep_Period"] - recon_noex_temp = [repeat([index],168) collect((168*i-167):168*i) loads[(168*index-167):168*index,"Demand_MW_z1"]] + 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_noex = [recon_noex repeat(["Off"],8736)]; +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]); +``` -[recon_noex; recon] |> -@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 Reconstructed", - width=845,height=400) + +```julia +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 [here](https://genxproject.github.io/GenX/dev/data_documentation/#Outputs). +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](@ref). -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 [here](https://genxproject.github.io/GenX/dev/objective_function/) in the documentation. +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](@ref) 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 `SmallNewEngland/OneZone` with a variety of minimum and maximum total periods. +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. -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 twice, so we should have two results folders, `Results` and `Results_1` in the `OneZone` folder, but you may have more if you've run the model more than twice. To ensure that the following code works, we'll delete any Results folders beyond the original. +```julia +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 1_three_zones 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. ```julia folders = cd(readdir,case) ``` +``` + 10-element Vector{String}: + ".DS_Store" + "README.md" + "Run.jl" + "policies" + "resources" + "results" + "results_1" + "results_2" + "settings" + "system" +``` ```julia for folder in folders - if length(folder) >= 8 && folder[1:8] == "Results_" + if length(folder) >= 8 && folder[1:8] == "results_" rm("example_systems/1_three_zones/" * folder,recursive=true) end end -``` - - -```julia + cd(readdir,case) ## Make sure they were deleted ``` -Now, we're going to change the min and max periods in the TDR settings. As a reminder, here are the time domain reduction settings we've been using: - - -```julia -time_domain_reduction_settings ``` - + 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. @@ -526,7 +548,7 @@ 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`. +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`. ```julia @@ -537,7 +559,15 @@ The objective value is found in the files `costs.csv` and `status.csv`. ```julia -status = CSV.read(joinpath(case,"Results/status.csv"),DataFrame,missingstring="NA") +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 ``` @@ -577,11 +607,6 @@ obj_val_plot = [4 abs(OV_noTDR[!,3][1]-OV_RP4[!,3][1]); ``` -```julia -# Add axis and dots for run time -``` - - ```julia # Plot the differences as a function of number of representative periods plotlyjs() @@ -592,7 +617,7 @@ scatter!(twinx(),obj_val_plot[:,1],times,color=:red,markeralpha=.5,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 `SmallNewEngland/OneZone` decreases the run time of GenX significantly while while maintaining an objective value close to the original. +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. @@ -617,5 +642,11 @@ 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/docs/src/Tutorials/Tutorial_4_model_generation.md b/docs/src/Tutorials/Tutorial_4_model_generation.md index e9af453d39..116b76fe2d 100644 --- a/docs/src/Tutorials/Tutorial_4_model_generation.md +++ b/docs/src/Tutorials/Tutorial_4_model_generation.md @@ -1,6 +1,6 @@ # Tutorial 4: Model Generation -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_4_Model_Generation.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_4_Model_Generation.ipynb) 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. @@ -19,7 +19,7 @@ We'll start by explaining JuMP, the optimization package that GenX uses to gener 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 the Wikipedia. +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](https://en.wikipedia.org/wiki/Linear_programming). ```julia @@ -126,7 +126,7 @@ The basic structure of the way `Run.jl` generates and solves the model is as fol ``` -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 `SmallNewEngland/OneZone`, the case is: +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: ```@raw html @@ -149,16 +149,19 @@ using GenX ```julia -case = joinpath("Example_Systems_Tutorials/SmallNewEngland/OneZone") +case = joinpath("example_systems/1_three_zones") ``` -"Example_Systems_Tutorials/SmallNewEngland/OneZone" +``` + "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. ```julia genx_settings = GenX.get_settings_path(case, "genx_settings.yml") # Settings YAML file path -setup = GenX.configure_settings(genx_settings) # Combines genx_settings with defaults not specified in the file +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 @@ -188,15 +191,21 @@ setup = GenX.configure_settings(genx_settings) # Combines genx_settings with def "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. +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. ```julia 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(case) + 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) @@ -238,17 +247,18 @@ The optimizer argument is taken from setup: ```julia -OPTIMIZER = GenX.configure_solver(setup["Solver"], settings_path); +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 `OneZone/Settings/highs_settings.yml`) and uses the settings to configure the solver to be used later. +The function `configure_solver` converts the string from "Solver" to a [MathOptInterface](https://jump.dev/MathOptInterface.jl/stable) 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. ```julia 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. @@ -331,17 +341,21 @@ Next, the dummy variable vZERO, the objective function, the power balance expres ```julia # 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 -@expression(EP, ePowerBalance[t=1:T, z=1:Z], 0) +GenX.create_empty_expression!(EP, :ePowerBalance, (T, Z)) # Initialize Objective Function Expression -@expression(EP, eObj, 0) +EP[:eObj] = AffExpr(0.0) -# Initialize Total Generation per Zone -@expression(EP, eGenerationByZone[z=1:Z, t=1:T], 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}: @@ -351,28 +365,25 @@ Next, we go through some of the settings in setup and, if they've been set to be ```julia +# Initialize Capacity Reserve Margin Expression if setup["CapacityReserveMargin"] > 0 - @expression(EP, eCapResMarBalance[res=1:inputs["NCapacityReserveMargin"], t=1:T], 0) + GenX.create_empty_expression!(EP, :eCapResMarBalance, (inputs["NCapacityReserveMargin"], T)) end +# Energy Share Requirement if setup["EnergyShareRequirement"] >= 1 - @expression(EP, eESR[ESR=1:inputs["nESR"]], 0) + GenX.create_empty_expression!(EP, :eESR, inputs["nESR"]) end if setup["MinCapReq"] == 1 - @expression(EP, eMinCapRes[mincap = 1:inputs["NumberOfMinCapReqs"]], 0) + GenX.create_empty_expression!(EP, :eMinCapRes, inputs["NumberOfMinCapReqs"]) end if setup["MaxCapReq"] == 1 - @expression(EP, eMaxCapRes[maxcap = 1:inputs["NumberOfMaxCapReqs"]], 0) + GenX.create_empty_expression!(EP, :eMaxCapRes, inputs["NumberOfMaxCapReqs"]) end ``` -``` - 3-element Vector{Int64}: - 0 - 0 - 0 -``` + The other settings will be used later on. @@ -391,22 +402,32 @@ if setup["UCommit"] > 0 GenX.ucommit!(EP, inputs, setup) end -GenX.emissions!(EP, inputs) +GenX.fuel!(EP, inputs, setup) + +GenX.co2!(EP, inputs) -if setup["Reserves"] > 0 - GenX.reserves!(EP, inputs, setup) +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 - Emissions Module (for CO2 Policy modularization + 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: @@ -435,6 +456,10 @@ 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) @@ -450,22 +475,28 @@ if !isempty(inputs["THERM_ALL"]) GenX.thermal!(EP, inputs, setup) end -# Model constraints, variables, expression related to retrofit technologies -if !isempty(inputs["RETRO"]) - EP = GenX.retrofit(EP, inputs) +# 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 [policies documentation](https://genxproject.github.io/GenX/dev/policies/): +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](@ref) section of the documentation: ```julia # 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 +if setup["CO2Cap"] > 0 + GenX.co2_cap!(EP, inputs, setup) +end # Endogenous Retirements if setup["MultiStage"] > 0 diff --git a/docs/src/Tutorials/Tutorial_5_solve_model.md b/docs/src/Tutorials/Tutorial_5_solve_model.md index 5d5d8f0417..8a4ab67147 100644 --- a/docs/src/Tutorials/Tutorial_5_solve_model.md +++ b/docs/src/Tutorials/Tutorial_5_solve_model.md @@ -1,6 +1,6 @@ # Tutorial 5: Solving the Model -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_5_Solve_Model.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_5_Solve_Model.ipynb) 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. @@ -159,17 +159,20 @@ using GenX ```julia -case = joinpath("Example_Systems_Tutorials/SmallNewEngland/OneZone") +case = joinpath("example_systems/1_three_zones") genx_settings = GenX.get_settings_path(case, "genx_settings.yml"); -setup = GenX.configure_settings(genx_settings); +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(case) + 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) @@ -178,57 +181,11 @@ if setup["TimeDomainReduction"] == 1 end end -OPTIMIZER = GenX.configure_solver(setup["Solver"], settings_path); +OPTIMIZER = GenX.configure_solver(settings_path,HiGHS.Optimizer); inputs = GenX.load_inputs(setup, case) ``` -``` - Configuring Settings - Time Series Data Already Clustered. - 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 DataFrame… - "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 - ⋮ => ⋮ -``` - ```julia EP = GenX.generate_model(setup,inputs,OPTIMIZER) ``` @@ -274,24 +231,6 @@ The function `solve_model(model, setup)` uses `optimize` to optimize the model: solution = optimize!(EP) # GenX.solve_model(EP,setup) ``` -``` - Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms - Presolving model - 36002 rows, 17519 cols, 136507 nonzeros - 34537 rows, 16058 cols, 136301 nonzeros - Presolve : Reductions: rows 34537(-6135); columns 16058(-2434); elements 136301(-29877) - Solving the presolved LP - Using EKK dual simplex solver - serial - Iteration Objective Infeasibilities num(sum) - 0 -6.1414103898e-01 Ph1: 31(126.489); Du: 3(0.614141) 0s - 17289 1.0127828422e+04 Pr: 0(0); Du: 0(3.1797e-13) 4s - Solving the original LP from the solution after postsolve - Model status : Optimal - Simplex iterations: 17289 - Objective value : 1.0127828422e+04 - HiGHS run time : 4.56 -``` - ```julia objective_value(EP) @@ -301,9 +240,6 @@ objective_value(EP) 9776.57688838726 ``` - -Note that regardless of solver settings, the folder `TDR_Results` stays the same, so we won't change it throughout the tutorial. - ## 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. @@ -373,4 +309,7 @@ optimize!(power) 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](https://jump.dev/JuMP.jl/stable/manual/solutions/#Conflicts). 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/docs/src/Tutorials/Tutorial_6_solver_settings.md b/docs/src/Tutorials/Tutorial_6_solver_settings.md index 67ff761297..17e9b8f5fd 100644 --- a/docs/src/Tutorials/Tutorial_6_solver_settings.md +++ b/docs/src/Tutorials/Tutorial_6_solver_settings.md @@ -1,6 +1,6 @@ # Tutorial 6: Solver Settings -[Jupyter Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_6_Solver_Settings.ipynb) +[Interactive Notebook of the tutorial](https://github.com/GenXProject/GenX-Tutorials/blob/main/Tutorials/Tutorial_6_Solver_Settings.ipynb) 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. @@ -15,72 +15,46 @@ Though solving the model relies only on `optimize`, there are a number of ways t using YAML using GenX using JuMP +using HiGHS using DataFrames +using Plots +using Plotly ``` ```julia -case = joinpath("Example_Systems_Tutorials/SmallNewEngland/OneZone") +case = joinpath("example_systems/1_three_zones") + genx_settings = GenX.get_settings_path(case, "genx_settings.yml"); -setup = GenX.configure_settings(genx_settings); +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 - 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 - 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 DataFrame… - "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 - ⋮ => ⋮ + + "example_systems/1_three_zones/settings" +``` + +```julia +### 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 @@ -92,6 +66,19 @@ To set the solver preferences, go into the settings folder of your case and sele ```julia 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" +``` + +```julia highs_settings = YAML.load(open(joinpath(case,"Settings/highs_settings.yml"))) ``` ``` @@ -117,18 +104,15 @@ The default settings are combined with the settings you specify in `highs_settin ### 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. +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__](https://en.wikipedia.org/wiki/Duality_(optimization)) 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](https://ergo-code.github.io/HiGHS/dev/terminology). If we decrease the tolerance parameters, the objective value becomes closer to the "true" optimal value. ```julia # Change tolerance, generate and solve model` -tols = [1e-7,1e-5,1e-4,1e-3,1e-2,1e-1] -OV = [0.0,0.0,0.0,0.0,0.0,0.0] -times = [0.0,0.0,0.0,0.0,0.0,0.0] -#highs_settings["Method"] = "ipm" -#YAML.write_file(joinpath(case,"Settings/highs_settings.yml"), highs_settings) +tols = [1e-7,1e-4,1e-2,1e-1] +OV = zeros(1,4) for i in range(1,length(tols)) println(" ") @@ -138,37 +122,39 @@ for i in range(1,length(tols)) 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(setup["Solver"], settings_path); + 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) - time = @elapsed GenX.solve_model(EP,setup) + GenX.solve_model(EP,setup) OV[i] = objective_value(EP) - times[i] = time end - - ``` Using the smallest tolerance as our base, we can see the error as the tolerance increases: ```julia -DataFrame([tols[2:end] abs.(OV[2:end] .- OV[1]) times[2:end]],["Tolerance", "Error", "Time"]) +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 ``` - ```julia using Plots using Plotly -import Pkg; Pkg.add("PyPlot") ``` ```julia -# Plot the run time as a function of the tolerance +# 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") -scatter!(twinx(),tols[2:end],times[2:end],color=:red,markeralpha=.5,label=:"Time",legend=:topleft, - yaxis=(label="Time")) + ylabel="Error", xlabel="Tolerance",size=(920,400),label=:"Error",title="Tolerance of Solver vs Error") ygrid!(:on, :dashdot, 0.1) ``` diff --git a/docs/src/Tutorials/files/output_58_0.svg b/docs/src/Tutorials/files/output_58_0.svg new file mode 100644 index 0000000000..41748d9d21 --- /dev/null +++ b/docs/src/Tutorials/files/output_58_0.svg @@ -0,0 +1 @@ +05001,0001,5002,0002,5003,0003,5004,0004,5005,0005,5006,0006,5007,0007,5008,0008,5009,000Time Step (hours)02,0004,0006,0008,00010,00012,00014,00016,00018,000Load (MW)Total46121720232829344548WeekMW Load per hour with TDR Representative Weeks \ No newline at end of file diff --git a/docs/src/Tutorials/files/output_65_0.svg b/docs/src/Tutorials/files/output_65_0.svg new file mode 100644 index 0000000000..c66dee379a --- /dev/null +++ b/docs/src/Tutorials/files/output_65_0.svg @@ -0,0 +1 @@ +Extreme_Periods02,0004,0006,0008,00010,00012,00014,00016,00018,000Load (MW)Off02,0004,0006,0008,00010,00012,00014,00016,00018,000Load (MW)On05001,0001,5002,0002,5003,0003,5004,0004,5005,0005,5006,0006,5007,0007,5008,0008,5009,000Time Step (hours)Total47121520232534444849617282945WeekMW Load per hour with TDR Representative Weeks, Extreme Periods Off and On \ No newline at end of file diff --git a/docs/src/User_Guide/TDR_input.md b/docs/src/User_Guide/TDR_input.md index 4187ee7810..0b36a9de1f 100644 --- a/docs/src/User_Guide/TDR_input.md +++ b/docs/src/User_Guide/TDR_input.md @@ -2,7 +2,7 @@ Modeling grid operations for each hour of the year can be computationally expensive for models with many zones and resources. Rather than modeling and optimizing power grid operations at a high temporal resolution (e.g., hourly, over a full year) while evaluating new capacity investments, which can be computationally expensive for large-scale studies with several resources, it may be useful to consider a reduced temporal resolution to model annual grid operations. Time-domain reduction is often employed in capacity expansion models as a way to balance model spatial and temporal resolution as well as representation of dispatch, while ensuring reasonable computational times. Such a time-domain reduction is often employed in capacity expansion models as a way to balance model spatial and temporal resolution as well as representation of dispatch, while ensuring reasonable computational times. GenX allows the option of performing time-domain reduction on the user supplied time-series input data to produce a representative time series at the desired level of temporal resolution. The time-domain reduction method provided allows the user to automate these features while specifying the various parameters of the time-domain reduction 'clustering' algorithm to be used in formulating the resulting optimization model. The below table summarizes the list of parameters to be specified by the user to perform the time domain reduction implemented in GenX. These parameters are passed to GenX via the YAML file `time_domain_reduction_settings.yml`. -It's also possible for GenX perform clustering separately from the optimization task. Check out the [Running the TDR](@ref) section for more information. +It's also possible for GenX perform clustering separately from the optimization task. Check out the [Running a case with Time Domain Reduction](@ref) section for more information. **Structure of the time\_domain\_reduction.yml file** diff --git a/docs/src/User_Guide/running_TDR.md b/docs/src/User_Guide/running_TDR.md index c52e819cc5..d21525e174 100644 --- a/docs/src/User_Guide/running_TDR.md +++ b/docs/src/User_Guide/running_TDR.md @@ -1,6 +1,4 @@ -# Running the TDR - -## Running a case with Time Domain Reduction +# Running a case with Time Domain Reduction There are two ways to run a case with a reduced (e.g. less than full-year) temporal resolution. 1. Let GenX perform the time domain reduction before optimizing. @@ -12,7 +10,7 @@ It's also possible for GenX perform clustering separately from the optimization Set `TimeDomainReduction: 1 `in the GenX settings for the case. -When the case is run (but before the optimization model is built), reduced time series data will be output to a folder within the case, (typically) `TDR_Results`. Note that if the data already exists in that folder, it will not be overwritten. If a user wants to change the time domain reduction settings and try again, the folder should be deleted before the case is run. +When the case is run (but before the optimization model is built), reduced time series data will be output to a folder within the case, (typically) `TDR_results`. Note that if the data already exists in that folder, it will not be overwritten. If a user wants to change the time domain reduction settings and try again, the folder should be deleted before the case is run. The clustering is done according to the settings in `time_domain_reduction.yml`. These are described in the Inputs section of data_documentation. @@ -25,8 +23,6 @@ The header of the file `Load_data.csv` in the main case folder will typically lo 1, 8760, 8760, ``` -For an example that uses this method, see `Example_Systems/RealSystemExample/ISONE_Singlezone`. - ## Method 2: Bring your own clustered data The second method is to use an external program to generate the reduced ('clustered') time series data. For instance, PowerGenome has a capability to construct GenX cases with clustered time series data. @@ -50,8 +46,6 @@ For problems involving Long Duration Storage, a file `Period_map.csv` is necessa See also the [Time-domain reduction](@ref). -For an example that uses this method, see `Example_Systems/RealSystemExample/ISONE_Trizone`. - ## Performing time domain reduction (TDR) separately from optimization *Added in 0.3.4* @@ -64,6 +58,6 @@ julia> using GenX julia> run_timedomainreduction!("/path/to/case") ``` -This function will obey the settings in `path/to/case/Settings/time_domain_reduction_settings.yml`. It will output the resulting clustered time series files in the case. +This function will obey the settings in `path/to/case/settings/time_domain_reduction_settings.yml`. It will output the resulting clustered time series files in the case. Running this function will overwrite these files in the case. This is done with the expectation that the user is trying out various settings. \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 6c13826c93..3e53fffb4b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -25,25 +25,25 @@ This section provides a quick guidance as to how to navigate through the differe This page serves as a gentle introduction to what GenX is meant for and what it does. -The next subsection, Installation Guide goes over how to download and install GenX and also how to download and install the Julia programming language (in which GenX is written) and the different open-source non-commercial freely available solvers, as well as the commercial solvers and the respective JuMP interfaces. This subsection also goes over installing the environment dependencies and instantiating a virtual environment. +The next subsection, [Installation Guide](@ref) goes over how to download and install GenX and also how to download and install the Julia programming language (in which GenX is written) and the different open-source non-commercial freely available solvers, as well as the commercial solvers and the respective JuMP interfaces. This subsection also goes over installing the environment dependencies and instantiating a virtual environment. We also mention the shortcomings of GenX and some third party extentions in the next couple subsections -The next section is Getting Started, that has two subsections. The first subsection, Example Cases, gives a walkthrough through some predefined example systems and how to run GenX for those and interpret the results. It also tells how to run GenX for a user-defined case. The second subsection talks specifically about how to run GenX with commercial solvers like Gurobi and CPLEX that are absolutely indispensable for solving large cases. +The next section is **Getting Started**, that has two subsections. The first subsection, Example Cases, gives a walkthrough through some predefined example systems and how to run GenX for those and interpret the results. It also tells how to run GenX for a user-defined case. The second subsection talks specifically about how to run GenX with commercial solvers like Gurobi and CPLEX that are absolutely indispensable for solving large cases. -The third section, Tutorial, gives a comprehensive tour of the different steps that are involved when a GenX capacity expansion simulation is run. It consists of 6 tutorial sections, each of which highlights the different important aspects of model construction and run of GenX. The different sections are configuring the GenX settings, visualizing the network, time domain reduction, generating the model, solving the model, and adjusting the different solver settings. +The third section, **Tutorial**, gives a comprehensive tour of the different steps that are involved when a GenX capacity expansion simulation is run. It consists of 6 tutorial sections, each of which highlights the different important aspects of model construction and run of GenX. The different sections are configuring the GenX settings, visualizing the network, time domain reduction, generating the model, solving the model, and adjusting the different solver settings. -The User Guide, which the fourth section, goes into the depths and details of the different steps and the settings and input parameters from the previous Tutorial section. The sections starts off with an overview of the workflow in GenX, briefing about the different steps (some of which we encountered in the Tutorials) of running GenX model. It then explains the different parameters of settings, policy, time-domain reduction, model structure, and output. Following this, the next subsection explains the different solver settings parameters of the different solvers. The next subsection goes over the different input CSV files and the different fields that are used there. The following two subsections are devoted to Time Domain Reduction (TDR). The first one walks through and explains the different settings parameters for TDR and the second one explains the couple different ways to run TDR for GenX and what exactly happens when we run TDR. The next four subsections, respectively, explains the different parameters, inputs, and outputs, and what happens when Modeling to Generate Alternatives (MGA), Multi-stage model, slack variables for policies (when we want to satisfy policy constraints in a soft manner by adding penalty of violation in the objective function), and Method of Morris. Finally, the last two sections are about the different steps involved while solving the model and the explanation of different output fields for both the default settings and user-specific settings. +The **User Guide**, which the fourth section, goes into the depths and details of the different steps and the settings and input parameters from the previous Tutorial section. The sections starts off with an overview of the workflow in GenX, briefing about the different steps (some of which we encountered in the Tutorials) of running GenX model. It then explains the different parameters of settings, policy, time-domain reduction, model structure, and output. Following this, the next subsection explains the different solver settings parameters of the different solvers. The next subsection goes over the different input CSV files and the different fields that are used there. The following two subsections are devoted to Time Domain Reduction (TDR). The first one walks through and explains the different settings parameters for TDR and the second one explains the couple different ways to run TDR for GenX and what exactly happens when we run TDR. The next four subsections, respectively, explains the different parameters, inputs, and outputs, and what happens when Modeling to Generate Alternatives (MGA), Multi-stage model, slack variables for policies (when we want to satisfy policy constraints in a soft manner by adding penalty of violation in the objective function), and Method of Morris. Finally, the last two sections are about the different steps involved while solving the model and the explanation of different output fields for both the default settings and user-specific settings. -The Model Concept and Overview section first introduces the GenX model and talks about its scope. It also introduces the notations, the objective function, and the power balance constraints. This is the first section which delves into the theoretical and mathematical details of the model, which is the most important one for model developers. +The **Model Concept and Overview** section first introduces the GenX model and talks about its scope. It also introduces the notations, the objective function, and the power balance constraints. This is the first section which delves into the theoretical and mathematical details of the model, which is the most important one for model developers. -The Model Reference, which is the sixth section delves deep into the GenX model and introduces the mathematical formulation, while discussing the physical interpretations of all the different parts of the GenX model. The different parts of the model consists of the different tyoe of generating resources (thermal, hydro, VRE, storage etc.), transmission network (modeling of flows as well as losses), demand modeling, operating reserves, unit commitment, different policies (such as CO2 constraint, capacity reserve margin, energy share requirement, min and max cap requirement etc.). This section also mentions about the different Julia functions (or methods) used for loading the input files, building the model, solving it, and generating the output files. Also, this is the section that explains the internal details of the Julia functions used for TDR, MGA, Method of Morris, Multi-stage modeling, and the several utility functions used throughout the GenX code-base. +The **Model Reference**, which is the sixth section delves deep into the GenX model and introduces the mathematical formulation, while discussing the physical interpretations of all the different parts of the GenX model. The different parts of the model consists of the different tyoe of generating resources (thermal, hydro, VRE, storage etc.), transmission network (modeling of flows as well as losses), demand modeling, operating reserves, unit commitment, different policies (such as CO2 constraint, capacity reserve margin, energy share requirement, min and max cap requirement etc.). This section also mentions about the different Julia functions (or methods) used for loading the input files, building the model, solving it, and generating the output files. Also, this is the section that explains the internal details of the Julia functions used for TDR, MGA, Method of Morris, Multi-stage modeling, and the several utility functions used throughout the GenX code-base. -The seventh section, Public API Reference is for describing the functions that are directly accessible to an external program from GenX (like loading inputs, generating output, running TDR script etc.) and how an external "client" code can access the GenX features, if the user desires to run his/her own code instead of the Run.jl provided by us. +The seventh section, **Public API Reference** is for describing the functions that are directly accessible to an external program from GenX (like loading inputs, generating output, running TDR script etc.) and how an external "client" code can access the GenX features, if the user desires to run his/her own code instead of the Run.jl provided by us. The eighth section, Third party extension mentions about Pygenx, a Python interface for GenX, that was built by Daniel Olsen and GenX case runner for automated batch running, built by Jacob Schwartz. -Finally, the ninth and last section, Developer Docs talks about the resource organization in GenX, how to add a new user-defined resource, and also several JuMP functions that are used as utility throughout the GenX code-base. +Finally, the ninth and last section, **Developer Docs** talks about the resource organization in GenX, how to add a new user-defined resource, and also several JuMP functions that are used as utility throughout the GenX code-base. @@ -52,7 +52,7 @@ Finally, the ninth and last section, Developer Docs talks about the resource org ## How to cite GenX We recommend users of GenX to cite it in their academic publications and patent filings. Here's the text to put up as the citation for GenX: -`MIT Energy Initiative and Princeton University ZERO lab. [GenX](https://github.com/GenXProject/GenX): a configurable power system capacity expansion model for studying low-carbon energy futures n.d. https://github.com/GenXProject/GenX. +`MIT Energy Initiative and Princeton University ZERO lab. [GenX](https://github.com/GenXProject/GenX): a configurable power system capacity expansion model for studying low-carbon energy futures n.d. https://github.com/GenXProject/GenX`. ## Acknowledgement The GenX team expresses deep gratitude to Laura Zwanziger and Jacob Schwartz for designing the Julia-themed GenX logo and to [Maya Mutic](https://github.com/mmutic) for developing the tutorials along with Filippo Pecci and Luca Bonaldo. From 3876dcf383d91a75c9667c075c22424478e298ba Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Sun, 17 Mar 2024 20:56:36 -0400 Subject: [PATCH 6/7] Add space to equation --- docs/src/Model_Concept_Overview/power_balance.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/Model_Concept_Overview/power_balance.md b/docs/src/Model_Concept_Overview/power_balance.md index 965c802592..d786bc0eef 100644 --- a/docs/src/Model_Concept_Overview/power_balance.md +++ b/docs/src/Model_Concept_Overview/power_balance.md @@ -14,8 +14,8 @@ The losses function $\beta_{l,t}(\cdot)$ will depend on the configuration used t ```math \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} - \forall z\in \mathcal{Z}, t \in \mathcal{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} ``` From d2fbead0fa5d37f07f0d59dec3a4d9f5115aaad4 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Sun, 17 Mar 2024 21:02:20 -0400 Subject: [PATCH 7/7] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a37a100c1c..ad26a8dde7 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,19 @@ The 'main' branch is the current master branch of GenX. The various subdirectori ## Requirements -GenX currently exists in version 0.4 and runs only on Julia v1.6.x, 1.7.x, 1.8.x, and 1.9.x, where x>=0 and a minimum version of JuMP v1.1.1. We recommend the users to either stick to a particular version of Julia to run GenX. If however, the users decide to switch between versions, it's very important to delete the old Manifest.toml file and do a fresh build of GenX when switching between Julia versions. +GenX currently exists in version 0.4 and runs only on Julia v1.6.x, 1.7.x, 1.8.x, and 1.9.x, where x>=0 and a minimum version of JuMP v1.1.1. We recommend the users to either stick to a particular version of Julia to run GenX. If however, the users decide to switch between versions, it's very important to delete the old `Manifest.toml` file and do a fresh build of GenX when switching between Julia versions. + There is also an older version of GenX, which is also currently maintained and runs on Julia 1.3.x and 1.4.x series. For those users who has previously cloned GenX, and has been running it successfully so far, and therefore might be unwilling to run it on the latest version of Julia: please look into the GitHub branch, [old_version](https://github.com/GenXProject/GenX/tree/old_version). It is currently setup to use one of the following open-source freely available solvers: -(A) the default solver: [HiGHS](https://github.com/jump-dev/HiGHS.jl) for linear programming and MILP, -(B) [Clp](https://github.com/jump-dev/Clp.jl) for linear programming (LP) problems, -(C) [Cbc](https://github.com/jump-dev/Cbc.jl) for mixed integer linear programming (MILP) problems +- the default solver: [HiGHS](https://github.com/jump-dev/HiGHS.jl) for linear programming and MILP, +- [Clp](https://github.com/jump-dev/Clp.jl) for linear programming (LP) problems, +- [Cbc](https://github.com/jump-dev/Cbc.jl) for mixed integer linear programming (MILP) problems We also provide the option to use one of these two commercial solvers: -(D) [Gurobi](https://www.gurobi.com), or -(E) [CPLEX](https://www.ibm.com/analytics/cplex-optimizer). +- [Gurobi](https://www.gurobi.com), or +- [CPLEX](https://www.ibm.com/analytics/cplex-optimizer). Note that using Gurobi and CPLEX requires a valid license on the host machine. There are two ways to run GenX with either type of solver options (open-source free or, licensed commercial) as detailed in the section, `Getting Started`.