diff --git a/.github/workflows/PR_checks.yml b/.github/workflows/PR_checks.yml deleted file mode 100644 index 6f02a6690b..0000000000 --- a/.github/workflows/PR_checks.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: PR Check -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] - -jobs: - # Enforces update of changelog file on every pull request - Changelog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: 'CHANGELOG.md' - skipLabels: 'Skip-Changelog' - token: ${{ secrets.GITHUB_TOKEN }} - missingUpdateErrorMessage: > - No update to CHANGELOG.md found! Please add an entry describing - your change and include the pull request tag. Note that we use - the keepachangelog format (https://keepachangelog.com). If your - change doesn’t require a changelog entry, please add the - 'Skip-Changelog' label to the pull request. - - # Check if the version number in the Project.toml file is updated - Version: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check version number - if: github.event.pull_request.base.ref == 'develop' - shell: bash {0} - run: | - git fetch origin develop &> /dev/null - vdiff=$(git diff -U0 origin/develop -- Project.toml | grep -E "^\+" | grep "version =") - if [ -z "$vdiff" ]; - then - echo "::error::Error: version number in Project.toml has not been updated." && exit 1 - - else - echo "Version number in Project.toml has been updated." - echo "New" ${vdiff:1} - fi \ No newline at end of file diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000000..3bb6ebed9c --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,22 @@ +name: Changelog Enforcer +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + # Enforces update of changelog file on every pull request + Changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: 'CHANGELOG.md' + skipLabels: 'Skip-Changelog, skip changelog' + token: ${{ secrets.GITHUB_TOKEN }} + missingUpdateErrorMessage: > + No update to CHANGELOG.md found! Please add an entry describing + your change and include the pull request tag. Note that we use + the keepachangelog format (https://keepachangelog.com). If your + change doesn’t require a changelog entry, please add the + 'Skip-Changelog' or 'skip changelog' label to the pull request. diff --git a/.github/workflows/dev_version_update.yml b/.github/workflows/dev_version_update.yml new file mode 100644 index 0000000000..cadaca0d70 --- /dev/null +++ b/.github/workflows/dev_version_update.yml @@ -0,0 +1,137 @@ +name: Dev Version Check and Update + +on: + pull_request_review: + types: [submitted] + +jobs: + check-version: + if: github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'develop' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: Set up Julia + uses: julia-actions/setup-julia@latest + with: + version: '1.x' + + - uses: julia-actions/cache@v2 + + - name: Configure Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "actions@github.com" + + - name: Check and Update Version + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + run: | + # Get PR base branch and export it + export BASE_BRANCH=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$REPO/pulls/$PR_NUMBER" | \ + jq -r .base.ref) + + julia -e ' + + # Get base branch directly from environment variable + base_branch = ENV["BASE_BRANCH"] + + # First ensure we have the base branch + run(`git fetch origin $(base_branch)`) + + function parse_version(version_str) + # Extract M.m.p and optional dev number + base_pattern = r"^(\d+\.\d+\.\d+)(?:-dev\.(\d+))?$" + m = match(base_pattern, version_str) + if isnothing(m) + error("Invalid version format: $version_str") + end + + version = m.captures[1] + dev_num = isnothing(m.captures[2]) ? nothing : parse(Int, m.captures[2]) + return (version, dev_num) + end + + function should_update_version(base_ver_str, current_ver_str) + base_ver, base_dev = parse_version(base_ver_str) + current_ver, current_dev = parse_version(current_ver_str) + + # If versions differ, no update needed + if base_ver != current_ver + return false + end + + # If base has dev number, we should increment from the base dev number + if !isnothing(base_dev) + return true + end + + # If base has no dev number and current has none, add dev.1 + if isnothing(base_dev) && isnothing(current_dev) + return true + end + + return false + end + + function get_new_version(base_ver_str, current_ver_str) + base_ver, base_dev = parse_version(base_ver_str) + current_ver, current_dev = parse_version(current_ver_str) + + # If base has a dev number, increment from that + if !isnothing(base_dev) + return "$(base_ver)-dev.$(base_dev + 1)" + end + + # If no dev numbers exist, start with dev.1 + return "$(current_ver)-dev.1" + end + + function check_and_update_version() + # Get the base branch version + base_content = read(pipeline(`git show origin/$(base_branch):Project.toml`), String) + # Get current branch version + current_content = read(joinpath(pwd(), "Project.toml"), String) + + # Extract versions using regex + version_pattern = r"version = \"(.*?)\"" + base_version = match(version_pattern, base_content).captures[1] + current_version = match(version_pattern, current_content).captures[1] + + println("Base version: $base_version") + println("Current version: $current_version") + + if should_update_version(base_version, current_version) + println("Version needs updating") + + new_version = get_new_version(base_version, current_version) + + # Update the file + new_content = replace(current_content, + "version = \"$current_version\"" => + "version = \"$new_version\"") + + write(joinpath(pwd(), "Project.toml"), new_content) + + # Commit and push the change + run(`git add $(joinpath(pwd(), "Project.toml"))`) + run(`git commit -m "Bump version to $new_version"`) + run(`git push`) + + println("Version updated to $new_version") + else + println("Version already updated") + end + end + + check_and_update_version()' \ No newline at end of file diff --git a/.github/workflows/format_suggestions.yml b/.github/workflows/format_suggestions.yml deleted file mode 100644 index dbd307846d..0000000000 --- a/.github/workflows/format_suggestions.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Format suggestions -on: - pull_request: - -jobs: - code-style: - runs-on: ubuntu-latest - steps: - - uses: julia-actions/julia-format@v2 - continue-on-error: true - - name: Check on failures - if: steps.julia-format.outcome != 'success' - run: echo "There are formatting errors. Please check the logs above." - shell: bash \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cfb0dcbda..909f8a14cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [0.4.2] - 2024-12-23 + ### Added - Fusion plant optional features for thermal plants (#743). - Support for reusing the same Gurobi environment for multiple solves when number of concurrent Gurobi uses is limited (#783). +- Additional long-duration storage constraints to bound state of charge in +non-representative periods (#781). +- New version of `add_similar_to_expression!` to support arrays of `Number`s. (#798) +- New settings flag `LDSAdditionalConstraints` to provide flexibility in +activating new long-duration storage constraints (#781). Can be set in the GenX +settings file (PR #801). ### Changed - The `charge.csv` and `storage.csv` files now include only resources with charge and storage variables (#760 and #763). - Deduplicated docs on optimized scheduled maintenance for thermal resources (#745). +- Removed the `CapRes_*` columns from `Network.csv` since they were not being used (#784). + +### Fixed +- Add constraint to ensure that electricity charged from the grid cannot exceed +the charging capacity of the storage component in VRE_STOR (#770). +- Update `getproperty` function for vectors of resources to ensure compatibility +with Julia v1.11 (#785). +- Fixed cost calculation in `write_costs.jl` when no resources are present in +a zone. (#796) +- Added `eTotalCMaxCapSlack` to calculation of `cUnmetPolicyPenalty` in +`write_costs.jl` (#806). ## [0.4.1] - 2024-08-20 diff --git a/CITATION.cff b/CITATION.cff index 3519a1b267..a0f0146e67 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -54,7 +54,7 @@ authors: given-names: "Qingyu" orcid: "https://orcid.org/0000-0003-2692-5135" title: "GenX" -version: 0.4.1 +version: 0.4.2 doi: 10.5281/zenodo.10846070 date-released: 2024-04-26 url: "https://github.com/GenXProject/GenX.jl" diff --git a/Project.toml b/Project.toml index 6cbaf9b300..64ae1fc30f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GenX" uuid = "5d317b1e-30ec-4ed6-a8ce-8d2d88d7cfac" authors = ["Bonaldo, Luca", "Chakrabarti, Sambuddha", "Cheng, Fangwei", "Ding, Yifu", "Jenkins, Jesse D.", "Luo, Qian", "Macdonald, Ruaridh", "Mallapragada, Dharik", "Manocha, Aneesha", "Mantegna, Gabe ", "Morris, Jack", "Patankar, Neha", "Pecci, Filippo", "Schwartz, Aaron", "Schwartz, Jacob", "Schivley, Greg", "Sepulveda, Nestor", "Xu, Qingyu", "Zhou, Justin"] -version = "0.4.1-dev.9" +version = "0.4.2-dev.1" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" diff --git a/README.md b/README.md index e6ce3b87af..dbbf8a71d2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ that incorporates several state-of-the-art practices in electricity system plann The model was [originally developed](https://energy.mit.edu/publication/enhanced-decision-support-changing-electricity-landscape/) by [Jesse D. Jenkins](https://mae.princeton.edu/people/faculty/jenkins) and [Nestor A. Sepulveda](https://energy.mit.edu/profile/nestor-sepulveda/) at the Massachusetts Institute of Technology and is now jointly maintained by -[a team of contributors](https://github.com/GenXProject/GenX#genx-team) at the Princeton University ZERO Lab (led by Jenkins), MIT (led by [Ruaridh MacDonald](https://energy.mit.edu/profile/ruaridh-macdonald/)), and NYU (led by [Dharik Mallapragada](https://engineering.nyu.edu/faculty/dharik-mallapragada)). +[a team of contributors](https://github.com/GenXProject/GenX#genx-team) at the Princeton University ZERO Lab (led by Jenkins), MIT (led by [Ruaridh MacDonald](https://energy.mit.edu/profile/ruaridh-macdonald/)), NYU (led by [Dharik Mallapragada](https://engineering.nyu.edu/faculty/dharik-mallapragada)), and Binghamton University (led by [Neha Patankar](https://www.binghamton.edu/ssie/people/profile.html?id=npatankar)). GenX is a constrained linear or mixed integer linear optimization model that determines the portfolio of electricity generation, storage, transmission, and demand-side resource investments and operational decisions to meet electricity demand in one or more future planning years at lowest cost, @@ -40,7 +40,7 @@ The 'main' branch is the current master branch of GenX. The various subdirectori ## Requirements -GenX (v0.4.1) runs on Julia v1.6 through v1.9, with a minimum version of the package JuMP v1.1.1. Julia v1.10 is also supported. However, we recently noticed a decline in performance with Julia v1.10, which is currently under investigation. Therefore, **we recommend using Julia v1.9**, particularly for very large cases. +GenX (v0.4.2) runs on Julia v1.6 through v1.9, with a minimum version of the package JuMP v1.1.1. Julia v1.10 and v1.11 are also supported. However, we recently noticed a decline in performance with Julia v1.10, which is currently under investigation. Therefore, **we recommend using Julia v1.9**, particularly for very large cases. 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. diff --git a/docs/src/Model_Concept_Overview/model_notation.md b/docs/src/Model_Concept_Overview/model_notation.md index 46369433ea..a805fd96cd 100644 --- a/docs/src/Model_Concept_Overview/model_notation.md +++ b/docs/src/Model_Concept_Overview/model_notation.md @@ -135,7 +135,7 @@ $\mathcal{W} \subseteq \mathcal{G}$ | where $\mathcal{W}$ set of hydroelectric g |$r^{ac,cha}_{y,z,t} \in \mathbb{R}_+$ | Upward spinning reserves contribution \[MW\] for the storage AC charge component from technology $y$ in zone $z$ at time $t$ - only applicable for co-located VRE and storage resources with a storage AC charge component, $y \in \mathcal{VS}^{sym,ac} \cup y \in \mathcal{VS}^{asym,ac,cha}$ | |$\alpha^{Contingency,Aux}_{y,z} \in \{0,1\}$ | Binary variable that is set to be 1 if the total installed capacity $\Delta^{\text{total}}_{y,z} > 0$ for any generator $y \in \mathcal{UC}$ and zone $z$, and can be 0 otherwise | |$\Phi_{l,t} \in \mathbb{R}_+$ | Power flow in line $l$ at time step $t$ \[MWh\]| -|$\theta_{z,t} \in \mathbb{R}$ | Volta phase angle in zone $z$ at time step $t$ \[radian\]| +|$\theta_{z,t} \in \mathbb{R}$ | Voltage phase angle in zone $z$ at time step $t$ \[radian\]| |$\nu_{y,z,t}$ | Commitment state of the generation cluster $y$ in zone $z$ at time $t$| |$\chi_{y,z,t}$ | Number of startup decisions, of the generation cluster $y$ in zone $z$ at time $t$| |$\zeta_{y,z,t}$ | Number of shutdown decisions, of the generation cluster $y$ in zone $z$ at time $t$| diff --git a/docs/src/Tutorials/Tutorial_1_configuring_settings.md b/docs/src/Tutorials/Tutorial_1_configuring_settings.md index 37d121f057..c7d8c7f08c 100644 --- a/docs/src/Tutorials/Tutorial_1_configuring_settings.md +++ b/docs/src/Tutorials/Tutorial_1_configuring_settings.md @@ -9,7 +9,7 @@ GenX is easy to customize to fit a variety of problems. In this tutorial, we sho 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 specific 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. +Most settings are set as either 0 or 1, which correspond to whether or not to include a specific feature. For example, to use `TimeDomainReduction`, you would set its parameter to 1 within `genx_settings.yml`. If you would like to run GenX without it, you would set its parameter to 0. Other settings, such as `CO2Cap`, have more options corresponding to integers, while some settings such as `ModelingtoGenerateAlternativeSlack` take a numerical input directly (in this case, the slack value). Two settings, `Solver` and `TimeDomainReductionFolder` take in text as input. To learn more about different solvers, read [here](https://github.com/GenXProject/GenX.jl/blob/main/docs/src/User_Guide/solver_configuration.md). For `TimeDomainReductionFolder`, specify the name of the directory you wish to see the results in. For a more comprehensive description of the input options, see the documentation linked above. diff --git a/docs/src/Tutorials/Tutorial_4_model_generation.md b/docs/src/Tutorials/Tutorial_4_model_generation.md index 4ef5f566a1..0d50bab766 100644 --- a/docs/src/Tutorials/Tutorial_4_model_generation.md +++ b/docs/src/Tutorials/Tutorial_4_model_generation.md @@ -474,7 +474,7 @@ 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) + GenX.hydro_inter_period_linkage!(EP, inputs, setup) end # Model constraints, variables, expression related to demand flexibility resources diff --git a/docs/src/Tutorials/Tutorial_8_outputs.md b/docs/src/Tutorials/Tutorial_8_outputs.md index b344c3aab3..e6b9f130f5 100644 --- a/docs/src/Tutorials/Tutorial_8_outputs.md +++ b/docs/src/Tutorials/Tutorial_8_outputs.md @@ -28,7 +28,6 @@ using StatsPlots case = joinpath("example_systems/1_three_zones"); ``` - ```julia include("example_systems/1_three_zones/Run.jl") ``` @@ -40,15 +39,12 @@ include("example_systems/1_three_zones/Run.jl") Demand (load) data Successfully Read! Fuels_data.csv Successfully Read! - Thermal.csv Successfully Read. Vre.csv Successfully Read. Storage.csv Successfully Read. Resource_energy_share_requirement.csv Successfully Read. Resource_capacity_reserve_margin.csv Successfully Read. Resource_minimum_capacity_requirement.csv Successfully Read. - - Summary of resources loaded into the model: ------------------------------------------------------- @@ -89,8 +85,7 @@ include("example_systems/1_three_zones/Run.jl") CSV Files Successfully Read In From /Users/mayamutic/Desktop/GenX-Tutorials/Tutorials/example_systems/1_three_zones Generating the Optimization Model - - Thermal.csv Successfully Read. + Thermal.csv Successfully Read. Vre.csv Successfully Read. Storage.csv Successfully Read. Resource_energy_share_requirement.csv Successfully Read. @@ -115,6 +110,7 @@ include("example_systems/1_three_zones/Run.jl") Minimum Capacity Requirement Module Time elapsed for model building is 5.887781667 + Solving Model Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms Presolving model @@ -251,6 +247,7 @@ include("example_systems/1_three_zones/Run.jl") Objective value : 9.4121364078e+03 HiGHS run time : 107.89 LP solved for primal + Writing Output Time elapsed for writing costs is 0.8427745 @@ -312,17 +309,12 @@ include("example_systems/1_three_zones/Run.jl") Time elapsed for writing is 6.909353542 - Below are all 33 files output by running GenX: - ```julia results = cd(readdir,joinpath(case,"results")) ``` - - - 33-element Vector{String}: "CO2_prices_and_penalties.csv" "ChargingCost.csv" @@ -351,17 +343,15 @@ results = cd(readdir,joinpath(case,"results")) "time_weights.csv" "tlosses.csv" - - ### Power -The file `power.csv`, shown below, outputs the power in MW discharged by each node at each time step. Note that if TimeDomainReduction is in use the file will be shorter. The first row states which zone each node is part of, and the total power per year is located in the second row. After that, each row represents one time step of the series. - +The file `power.csv`, shown below, contains the power output in MW discharged by each node at each time step. Note that if `TimeDomainReduction` is enabled, the file will have fewer rows compared to the number of time steps in the `system/Demand_data.csv` file. In this case, the corresponding `Demand_data.csv` file that matches the time series in `power.csv` can be found in the `TDR_results` folder. The first row of `power.csv` indicates the zone each node belongs to, while the second row contains the total power per year. Each subsequent row represents one time step in the series. ```julia power = CSV.read(joinpath(case,"results/power.csv"),DataFrame,missingstring="NA") ``` -``` @raw html + +```@raw html
1850×12 DataFrame
1825 rows omitted
RowResourceMA_natural_gas_combined_cycleCT_natural_gas_combined_cycleME_natural_gas_combined_cycleMA_solar_pvCT_onshore_windCT_solar_pvME_onshore_windMA_batteryCT_batteryME_batteryTotal
String15Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64
1Zone1.02.03.01.02.02.03.01.02.03.00.0
2AnnualSum1.04015e73.42459e68.94975e52.47213e72.90683e72.69884e72.625e75.06354e61.45833e74.90368e61.463e8
3t1-0.0-0.0-0.0-0.08510.78-0.05300.610.02537.45673.3417022.2
4t2-0.0-0.0-0.0-0.08420.78-0.06282.040.02537.450.017240.3
5t3-0.0-0.0-0.0-0.08367.78-0.02409.840.02537.451828.2415143.3
6t4-0.0-0.0-0.0-0.08353.78-0.02762.241591.462537.450.015244.9
7t5-0.0-0.0-0.0-0.07482.39-0.00.01617.462980.641384.6213465.1
8t6-0.0-0.0-0.0-0.02429.93-0.02797.241717.965535.370.012480.5
9t7-0.0-0.0-0.0-0.011868.8-0.01374.731320.78871.4431340.6716776.4
10t8-0.0-0.0-0.0-0.02656.93-0.00.02115.965535.371452.6211760.9
11t9-0.0-0.0-0.03061.280.03110.82982.24868.8175389.440.015412.6
12t10-0.0-0.0-0.06100.227597.995543.690.00.00.01521.1220763.0
13t11-0.0-0.0-0.08314.290.06341.983080.240.02458.820.020195.3
1839t1837-0.0-0.0-0.06712.182541.66736.37305.6081410.33763.7261427.8219897.6
1840t1838-0.0-0.0-0.06514.150.06847.243153.240.03464.220.019978.9
1841t1839-0.0-0.0-0.05582.073848.886280.20.0195.4222048.31571.1219526.0
1842t1840-0.0-0.0-0.03688.139349.984892.73490.611006.020.00.022427.4
1843t1841-0.0-0.0-0.0509.228124.991351.083653.061218.52507.81828.2419192.9
1844t1842-0.0-0.0-0.0-0.02918.2-0.06896.822194.615535.37256.86317801.9
1845t1843-0.0-0.0-0.0-0.06800.37-0.07324.661838.113950.1541.947219955.2
1846t1844-0.0-0.0-0.0-0.09505.82-0.05683.661744.782567.93838.07720340.3
1847t1845-0.0-0.0-0.0-0.03491.93-0.05128.561597.615535.371107.4916861.0
1848t1846-0.0-0.0-0.0-0.012135.6-0.05021.751341.111140.561125.920764.9
1849t1847-0.0-0.0-0.0-0.08875.71-0.03605.98974.612665.481783.7917905.6
1850t1848-0.0-0.0-0.0-0.013549.1-0.04098.0541.61205.311478.2719872.3
``` @@ -386,11 +376,16 @@ for i in range(2,4) power_plot = [power_plot; power_plot_temp] end -demands = CSV.read(joinpath(case,"system/Demand_data.csv"),DataFrame,missingstring="NA") +demands = CSV.read(joinpath(case,"TDR_results/Demand_data.csv"),DataFrame,missingstring="NA") demands_tot = demands[!,"Demand_MW_z1"]+demands[!,"Demand_MW_z2"]+demands[!,"Demand_MW_z3"] power_plot[!,"Demand_Total"] = repeat(demands_tot[tstart:tend],4); ``` +Note that since the `power.csv` file is generated by running GenX with `TimeDomainReduction: 1`, the demands time series must be taken from the `Demand_data.csv` file located in the `TDR_results` folder. + +GenX also has the ability to output the reconstructed version of power generation by setting `OutputFullTimeSeries: 1` in `genx_settings.yml`. In this case, a second version of the `power.csv` file will be created inside the `results/Full_TimeSeries` folder. To plot the reconstructed version against the demand, ensure you use the `Demand_data.csv` from the `settings` folder, not the one in the `TDR_results` folder. + +Finally, if `TimeDomainReduction: 0` is set, the `power.csv` file will contain the full time series of power generation, and the `Demand_data.csv` should be taken from the `settings` folder. ```julia power_plot |> @@ -402,11 +397,9 @@ power_plot |> ``` ![svg](./files/t8_cap.svg) - We can separate it by zone in the following plot: - ```julia Zone1 = [power[2,2] power[2,5] 0 power[2,9]] Zone2 = [power[2,3] power[2,7] power[2,6] power[2,10]] @@ -448,7 +441,6 @@ end ``` - ```julia Plots.heatmap(heat,yticks=0:4:24,xticks=([15:30:364;], ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"]), @@ -458,7 +450,6 @@ Plots.heatmap(heat,yticks=0:4:24,xticks=([15:30:364;], ![svg](./files/t8_heatmap.svg) - ### Cost and Revenue The basic cost of each power plant and the revenue it generates can be found in files `costs.csv`, `NetRevenue.csv`,and `EnergyRevenue.csv`. `NetRevenue.csv` breaks down each specific cost per node in each zone, which is useful to visualize what the cost is coming from. @@ -468,14 +459,10 @@ The basic cost of each power plant and the revenue it generates can be found in netrevenue = CSV.read(joinpath(case,"results/NetRevenue.csv"),DataFrame,missingstring="NA") ``` - - ``` @raw html
10×28 DataFrame
RowregionResourcezoneClusterR_IDInv_cost_MWInv_cost_MWhInv_cost_charge_MWFixed_OM_cost_MWFixed_OM_cost_MWhFixed_OM_cost_charge_MWVar_OM_cost_outFuel_costVar_OM_cost_inStartCostCharge_costCO2SequestrationCostEnergyRevenueSubsidyRevenueOperatingReserveRevenueOperatingRegulationRevenueReserveMarginRevenueESRRevenueEmissionsCostRegSubsidyRevenueRevenueCostProfit
String3String31Int64Int64Int64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64
1MAMA_natural_gas_combined_cycle1115.54734e80.00.08.72561e70.00.03.69253e72.10416e80.03.84832e70.00.02.77103e90.00.00.00.00.01.84321e90.02.77103e92.77103e91.43051e-6
2CTCT_natural_gas_combined_cycle2121.42906e80.00.02.11911e70.00.01.22258e74.97792e70.07.75292e60.00.08.4423e80.00.00.00.00.06.10375e80.08.4423e88.4423e81.19209e-7
3MEME_natural_gas_combined_cycle3133.52336e70.00.08.77661e60.00.04.02739e62.26505e70.03.33663e60.00.02.19267e80.00.00.00.00.01.45243e80.02.19267e82.19267e80.0
4MAMA_solar_pv1141.27007e90.00.02.79327e80.00.00.00.00.00.00.00.01.5494e90.00.00.00.00.00.00.01.5494e91.5494e9-2.86102e-6
5CTCT_onshore_wind2151.40748e90.00.06.25617e80.00.02.90683e60.00.00.00.00.02.036e90.00.00.00.00.00.00.02.036e92.036e9-5.00679e-6
6CTCT_solar_pv2161.35108e90.00.02.97142e80.00.00.00.00.00.00.00.01.64822e90.00.00.00.00.00.00.01.64822e91.64822e99.53674e-7
7MEME_onshore_wind3171.03673e90.00.04.60821e80.00.02.625e60.00.00.00.00.01.50017e90.00.00.00.00.00.00.01.50017e91.50017e92.38419e-6
8MAMA_battery1084.29792e72.23673e80.01.07426e75.59033e70.07.59532e50.08.97367e50.01.3432e80.04.48833e80.00.00.00.00.00.00.04.48833e84.69275e8-2.0442e7
9CTCT_battery2091.08405e85.73615e80.02.70957e71.43365e80.02.1875e60.02.58447e60.05.24177e80.01.31941e90.00.00.00.00.00.00.01.31941e91.38143e9-6.20165e7
10MEME_battery30103.58043e71.03994e80.08.94925e62.59915e70.07.35552e50.08.69036e50.03.81057e70.02.03732e80.00.00.00.00.00.00.02.03732e82.14449e8-1.0717e7
``` - - ```julia xnames = netrevenue[!,2] names1 = ["Investment cost" "Fixed OM cost" "Variable OM cost" "Fuel cost" "Start Cost" "Battery charge cost" "CO2 Sequestration Cost" "Revenue"] @@ -491,8 +478,6 @@ StatsPlots.scatter!(xnames,netrevenue[!,"Revenue"],label="Revenue",color="black" ![svg](./files/t8_cost.svg) - - ### Emissions The file `emmissions.csv` gives the total CO2 emmissions per zone for each hour GenX runs. The first three rows give the marginal CO2 abatement cost in $/ton CO2. @@ -502,14 +487,10 @@ The file `emmissions.csv` gives the total CO2 emmissions per zone for each hour emm1 = CSV.read(joinpath(case,"results/emissions.csv"),DataFrame) ``` - - ``` @raw html
1852×5 DataFrame
1827 rows omitted
RowZone123Total
String15Float64Float64Float64Float64
1CO2_Price_1444.9210.00.00.0
2CO2_Price_20.0468.6680.00.0
3CO2_Price_30.00.0240.860.0
4AnnualSum4.14279e61.30236e66.03017e56.04816e6
5t10.00.00.00.0
6t20.00.00.00.0
7t30.00.00.00.0
8t40.00.00.00.0
9t50.00.00.00.0
10t60.00.00.00.0
11t70.00.00.00.0
12t80.00.00.00.0
13t90.00.00.00.0
1841t18370.00.00.00.0
1842t18380.00.00.00.0
1843t18390.00.00.00.0
1844t18400.00.00.00.0
1845t18410.00.00.00.0
1846t18420.00.00.00.0
1847t18430.00.00.00.0
1848t18440.00.00.00.0
1849t18450.00.00.00.0
1850t18460.00.00.00.0
1851t18470.00.00.00.0
1852t18480.00.00.00.0
``` - - ```julia # Pre-processing tstart = 470 @@ -519,7 +500,6 @@ names_emm = ["Zone 1","Zone 2","Zone 3"] emm_tot = DataFrame([emm1[3:end,2] emm1[3:end,3] emm1[3:end,4]], ["Zone 1","Zone 2","Zone 3"]) - emm_plot = DataFrame([collect((tstart-3):(tend-3)) emm_tot[tstart:tend,1] repeat([names_emm[1]],(tend-tstart+1))], ["Hour","MW","Zone"]); @@ -530,7 +510,6 @@ end ``` - ```julia emm_plot |> @vlplot(mark={:line}, @@ -541,11 +520,8 @@ emm_plot |> ![svg](./files/t8_emm1.svg) - - Let's try changing the CO2 cap, as in Tutorial 7, and plotting the resulting emmissions. - ```julia genx_settings_TZ = YAML.load(open((joinpath(case,"settings/genx_settings.yml")))) genx_settings_TZ["CO2Cap"] = 0 @@ -555,7 +531,6 @@ include("example_systems/1_three_zones/Run.jl") # run outside of notebook ``` - Configuring Settings Time Series Data Already Clustered. Configuring Solver @@ -576,15 +551,12 @@ include("example_systems/1_three_zones/Run.jl") Total number of resources: 10 ------------------------------------------------------- - Thermal.csv Successfully Read. Vre.csv Successfully Read. Storage.csv Successfully Read. Resource_energy_share_requirement.csv Successfully Read. Resource_capacity_reserve_margin.csv Successfully Read. Resource_minimum_capacity_requirement.csv Successfully Read. - - Generators_variability.csv Successfully Read! Validating time basis Minimum_capacity_requirement.csv Successfully Read! @@ -607,6 +579,7 @@ include("example_systems/1_three_zones/Run.jl") Minimum Capacity Requirement Module Time elapsed for model building is 0.531860834 + Solving Model Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms Presolving model @@ -740,6 +713,7 @@ include("example_systems/1_three_zones/Run.jl") Objective value : 5.5855435982e+03 HiGHS run time : 66.51 LP solved for primal + Writing Output Time elapsed for writing costs is 0.099885792 @@ -799,20 +773,13 @@ include("example_systems/1_three_zones/Run.jl") Time elapsed for writing is 0.530491792 - - ```julia emm2 = CSV.read(joinpath(case,"results_1/emissions.csv"),DataFrame) ``` - - ``` @raw html
1849×5 DataFrame
1824 rows omitted
RowZone123Total
String15Float64Float64Float64Float64
1AnnualSum1.68155e71.41088e74310.213.09286e7
2t1997.1690.00.0997.169
3t2997.1690.00.0997.169
4t3997.1690.00.0997.169
5t4997.1690.00.0997.169
6t5997.1690.00.0997.169
7t6997.1690.00.0997.169
8t7997.1690.00.0997.169
9t8997.1690.00.0997.169
10t9997.1690.00.0997.169
11t101471.460.00.01471.46
12t11997.1690.00.0997.169
13t121115.810.00.01115.81
1838t18372789.351012.990.03802.34
1839t18382835.211012.990.03848.2
1840t18392520.571012.990.03533.56
1841t18401496.47445.850.01942.32
1842t18412571.261012.990.03584.25
1843t18422835.211012.990.03848.2
1844t18432835.211012.990.03848.2
1845t18442625.42960.1840.03585.6
1846t18452506.32342.3910.02848.71
1847t18462277.59342.3910.02619.98
1848t18471960.08524.5260.02484.6
1849t18481566.77342.3910.01909.16
``` - - - ```julia # Pre-processing tstart = 470 @@ -822,7 +789,6 @@ names_emm = ["Zone 1","Zone 2","Zone 3"] emm_tot2 = DataFrame([emm2[3:end,2] emm2[3:end,3] emm2[3:end,4]], ["Zone 1","Zone 2","Zone 3"]) - emm_plot2 = DataFrame([collect((tstart-3):(tend-3)) emm_tot2[tstart:tend,1] repeat([names_emm[1]],(tend-tstart+1))], ["Hour","MW","Zone"]); @@ -858,12 +824,9 @@ Plots.plot(collect((tstart-3):(tend-3)),emm1sum[tstart:tend],size=(800,400),labe Plots.plot!(collect((tstart-3):(tend-3)),emm2sum[tstart:tend],label="No CO2 Cap",linewidth = 1.5) ``` ![svg](./files/t8_emm_comp.svg) - - Finally, set the CO2 Cap back to 2: - ```julia genx_settings_TZ["CO2Cap"] = 2 YAML.write_file((joinpath(case,"settings/genx_settings.yml")), genx_settings_TZ) diff --git a/docs/src/Tutorials/files/t8_cap.svg b/docs/src/Tutorials/files/t8_cap.svg index c40ef607c6..a8747901c7 100644 --- a/docs/src/Tutorials/files/t8_cap.svg +++ b/docs/src/Tutorials/files/t8_cap.svg @@ -1 +1 @@ -1224364860728496108120132144156168Time Step (hours)02,0004,0006,0008,00010,00012,00014,00016,00018,00020,00022,00024,00026,000Demand (MW)WindSolarNatural_GasBatteryDemandResource_TypeResource Capacity per Hour with Demand Curve, all Zones \ No newline at end of file +1224364860728496108120132144156168Time Step (hours)02,0004,0006,0008,00010,00012,00014,00016,00018,00020,00022,00024,00026,000Load (MW)WindSolarNatural_GasBatteryDemandResource_TypeResource Capacity per Hour with Load Demand Curve, all Zones \ No newline at end of file diff --git a/docs/src/User_Guide/generate_alternatives.md b/docs/src/User_Guide/generate_alternatives.md index f0fe836362..4278c4ffe7 100644 --- a/docs/src/User_Guide/generate_alternatives.md +++ b/docs/src/User_Guide/generate_alternatives.md @@ -10,4 +10,4 @@ GenX includes a modeling to generate alternatives (MGA) package that can be used 6. Set the `MGAAnnualGeneration` flag in the `genx_settings.yml` file to the desired MGA formulation. 7. Solve the model using `Run.jl` file. -Results from the MGA algorithm would be saved in MGA_max and MGA_min folders in the case folder. \ No newline at end of file +Results from the MGA algorithm would be saved in MGA\_max and MGA\_min folders in the case folder. \ No newline at end of file diff --git a/docs/src/User_Guide/model_configuration.md b/docs/src/User_Guide/model_configuration.md index 0bb8b1dc13..2c8ccdcded 100644 --- a/docs/src/User_Guide/model_configuration.md +++ b/docs/src/User_Guide/model_configuration.md @@ -31,6 +31,9 @@ The following tables summarize the model settings parameters and their default/p |StorageVirtualDischarge | Flag to enable contributions that a storage device makes to the capacity reserve margin without generating power.| ||1 = activate the virtual discharge of storage resources.| ||0 = do not activate the virtual discharge of storage resources.| +|LDSAdditionalConstraints | Flag to activate additional constraints for long duration storage resources to prevent violation of SoC limits in non-representative periods.| +||1 = activate additional constraints.| +||0 = do not activate additional constraints.| |HourlyMatching| Constraint to match generation from clean sources with hourly consumption.| ||1 = Constraint is active.| ||0 = Constraint is not active.| @@ -49,7 +52,7 @@ The following tables summarize the model settings parameters and their default/p |MultiStage | Model multiple planning stages | ||1 = Model multiple planning stages as specified in `multi_stage_settings.yml` | ||0 = Model single planning stage | -|ModelingToGenerateAlternatives | Modeling to Generate Alternative Algorithm. For details, see [here](https://genxproject.github.io/GenX/dev/additional_features/#Modeling-to-Generate-Alternatives)| +|ModelingToGenerateAlternatives | Modeling to Generate Alternatives Algorithm. For more details, see the [Modeling to Generate Alternatives](@ref) section in the **Model Reference**.| ||1 = Use the algorithm. | ||0 = Do not use the algorithm. | |ModelingtoGenerateAlternativeSlack | value used to define the maximum deviation from the least-cost solution as a part of Modeling to Generate Alternative Algorithm. Can take any real value between 0 and 1. | diff --git a/docs/src/User_Guide/model_input.md b/docs/src/User_Guide/model_input.md index 84a0da97ad..19f64dbf5a 100644 --- a/docs/src/User_Guide/model_input.md +++ b/docs/src/User_Guide/model_input.md @@ -75,11 +75,11 @@ This input file contains input parameters related to: 1) definition of model zon |Ohms | Line resistance in Ohms (used to calculate I^2R losses)| |kV | Line voltage in kV (used to calculate I^2R losses)| |**CapacityReserveMargin > 0**|| -|CapRes\_* | Eligibility of the transmission line for adding firm capacity to the capacity reserve margin constraint. * represents the number of the capacity reserve margin constraint.| -||1 = the transmission line is eligible for adding firm capacity to the region| -||0 = the transmission line is not eligible for adding firm capacity to the region| -|DerateCapRes\_* | (0,1) value represents the derating of the firm transmission capacity for the capacity reserve margin constraint.| -|CapResExcl\_* | (-1,1,0) = -1 if the designated direction of the transmission line is inbound to locational deliverability area (LDA) modeled by the capacity reserve margin constraint. = 1 if the designated direction of the transmission line is outbound from the LDA modeled by the capacity reserve margin constraint. Zero otherwise.| +|CapResExcl\_* | {-1,1,0} Eligibility of the transmission line for adding firm capacity to the capacity reserve margin constraint, as well as whether the line flow is into or out of the constraint region. * represents the number of the capacity reserve margin constraint.| +|| 0 = the transmission line is not eligible/part of any capacity reserve constraint.| +|| -1 = the transmission line is eligible for the capacity reserve margin constraint and the designated direction of the transmission line is **inbound** to locational deliverability area (LDA) modeled by the capacity reserve margin constraint.| +|| 1 = the transmission line is eligible for the capacity reserve margin constraint and the designated direction of the transmission line is **outbound** from the locational deliverability area (LDA) modeled by the capacity reserve margin constraint.| +|DerateCapRes\_* | [0,1] value represents the derating of the firm transmission capacity for the capacity reserve margin constraint. * represents the number of the capacity reserve margin constraint.| |**MultiStage == 1**| |Capital\_Recovery\_Period |Capital recovery period (in years) used for determining overnight capital costs from annualized investment costs for network transmission line expansion. | |Line\_Max\_Flow\_Possible\_MW |Maximum possible line flow in the current model period. Overrides Line\_Max\_Reinforcement\_MW, which is not used when performing multi-stage modeling. | diff --git a/docs/src/index.md b/docs/src/index.md index 3a87b5b5fb..91dfc7bafe 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -9,7 +9,7 @@ GenX is a highly-configurable, [open source](https://github.com/GenXProject/GenX/blob/main/LICENSE) electricity resource capacity expansion model that incorporates several state-of-the-art practices in electricity system planning to offer improved decision support for a changing electricity landscape. -The model was [originally developed](https://energy.mit.edu/publication/enhanced-decision-support-changing-electricity-landscape/) by [Jesse D. Jenkins](https://mae.princeton.edu/people/faculty/jenkins) and [Nestor A. Sepulveda](https://energy.mit.edu/profile/nestor-sepulveda/) at the Massachusetts Institute of Technology and is now jointly maintained by [a team of contributors](https://energy.mit.edu/genx/#team) at the Princeton University ZERO Lab (led by Jenkins), MIT (led by [Ruaridh MacDonald](https://energy.mit.edu/profile/ruaridh-macdonald/)), and NYU (led by [Dharik Mallapragada](https://engineering.nyu.edu/faculty/dharik-mallapragada)). +The model was [originally developed](https://energy.mit.edu/publication/enhanced-decision-support-changing-electricity-landscape/) by [Jesse D. Jenkins](https://mae.princeton.edu/people/faculty/jenkins) and [Nestor A. Sepulveda](https://energy.mit.edu/profile/nestor-sepulveda/) at the Massachusetts Institute of Technology and is now jointly maintained by [a team of contributors](https://energy.mit.edu/genx/#team) at the Princeton University ZERO Lab (led by Jenkins), MIT (led by [Ruaridh MacDonald](https://energy.mit.edu/profile/ruaridh-macdonald/)), NYU (led by [Dharik Mallapragada](https://engineering.nyu.edu/faculty/dharik-mallapragada)), and Binghamton University (led by [Neha Patankar](https://www.binghamton.edu/ssie/people/profile.html?id=npatankar)). GenX is a constrained linear or mixed integer linear optimization model that determines the portfolio of electricity generation, storage, transmission, and demand-side resource investments and operational decisions to meet electricity demand in one or more future planning years at lowest cost, while subject to a variety of power system operational constraints, resource availability limits, and other imposed environmental, market design, and policy constraints. diff --git a/docs/src/installation.md b/docs/src/installation.md index 3fb13ee363..e6801a1b60 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -2,7 +2,7 @@ This guide will walk you through the steps to install Julia, the GenX package, and the required dependencies to run GenX. ## Installing Julia -GenX (v0.4.1) runs on Julia v1.6 through v1.9, with a minimum version of the package [JuMP](https://jump.dev/JuMP.jl/stable/) v1.1.1. Julia v1.10 is also supported. However, we recently noticed a decline in performance with Julia v1.10, which is currently under investigation. Therefore, we recommend using Julia v1.9, particularly for very large cases. To install Julia, please follow the instructions on the [Julia website](https://julialang.org/downloads/). +GenX (v0.4.2) runs on Julia v1.6 through v1.9, with a minimum version of the package [JuMP](https://jump.dev/JuMP.jl/stable/) v1.1.1. Julia v1.10 and v1.11 are also supported. However, we recently noticed a decline in performance with Julia v1.10, which is currently under investigation. Therefore, we recommend using Julia v1.9, particularly for very large cases. To install Julia, please follow the instructions on the [Julia website](https://julialang.org/downloads/). !!! note "Note" We recommend the users to 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. diff --git a/example_systems/10_IEEE_9_bus_DC_OPF/system/Network.csv b/example_systems/10_IEEE_9_bus_DC_OPF/system/Network.csv index b9a66cd006..0677a9450a 100644 --- a/example_systems/10_IEEE_9_bus_DC_OPF/system/Network.csv +++ b/example_systems/10_IEEE_9_bus_DC_OPF/system/Network.csv @@ -1,11 +1,11 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1,Angle_Limit_Rad,Line_Voltage_kV,Line_Resistance_Ohms,Line_Reactance_Ohms -BUS1,z1,1,1,4,250,BUS1_to_BUS4,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,68.5584 -BUS2,z2,2,4,5,250,BUS4_to_BUS5,0.5,0.015,500,12000,0.95,0,0,0.785398,345,20.23425,109.503 -BUS3,z3,3,5,6,150,BUS5_to_BUS6,0.5,0.015,500,12000,0.95,0,0,0.785398,345,46.41975,202.3425 -BUS4,z4,4,3,6,300,BUS3_to_BUS6,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,69.74865 -BUS5,z5,5,6,7,150,BUS6_to_BUS7,0.5,0.015,500,12000,0.95,0,0,0.785398,345,14.163975,119.9772 -BUS6,z6,6,7,8,250,BUS7_to_BUS8,0.5,0.015,500,12000,0.95,0,0,0.785398,345,10.117125,85.698 -BUS7,z7,7,8,2,250,BUS8_to_BUS2,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,74.390625 -BUS8,z8,8,8,9,250,BUS8_to_BUS9,0.5,0.015,500,12000,0.95,0,0,0.785398,345,38.088,191.63025 -BUS9,z9,9,9,4,250,BUS9_to_BUS4,0.5,0.015,500,12000,0.95,0,0,0.785398,345,11.9025,101.17125 -,,10,1,6,250,BUS1_to_BUS6,0.5,0.015,500,12000,0.95,0,0,0.785398,345,11.9025,101.17125 \ No newline at end of file +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1,Angle_Limit_Rad,Line_Voltage_kV,Line_Resistance_Ohms,Line_Reactance_Ohms +BUS1,z1,1,1,4,250,BUS1_to_BUS4,0.5,0.015,500,12000,0.95,0,0.785398,345,0,68.5584 +BUS2,z2,2,4,5,250,BUS4_to_BUS5,0.5,0.015,500,12000,0.95,0,0.785398,345,20.23425,109.503 +BUS3,z3,3,5,6,150,BUS5_to_BUS6,0.5,0.015,500,12000,0.95,0,0.785398,345,46.41975,202.3425 +BUS4,z4,4,3,6,300,BUS3_to_BUS6,0.5,0.015,500,12000,0.95,0,0.785398,345,0,69.74865 +BUS5,z5,5,6,7,150,BUS6_to_BUS7,0.5,0.015,500,12000,0.95,0,0.785398,345,14.163975,119.9772 +BUS6,z6,6,7,8,250,BUS7_to_BUS8,0.5,0.015,500,12000,0.95,0,0.785398,345,10.117125,85.698 +BUS7,z7,7,8,2,250,BUS8_to_BUS2,0.5,0.015,500,12000,0.95,0,0.785398,345,0,74.390625 +BUS8,z8,8,8,9,250,BUS8_to_BUS9,0.5,0.015,500,12000,0.95,0,0.785398,345,38.088,191.63025 +BUS9,z9,9,9,4,250,BUS9_to_BUS4,0.5,0.015,500,12000,0.95,0,0.785398,345,11.9025,101.17125 +,,10,1,6,250,BUS1_to_BUS6,0.5,0.015,500,12000,0.95,0,0.785398,345,11.9025,101.17125 \ No newline at end of file diff --git a/example_systems/1_three_zones/system/Network.csv b/example_systems/1_three_zones/system/Network.csv index 82d03a60e7..c2acbc65a1 100644 --- a/example_systems/1_three_zones/system/Network.csv +++ b/example_systems/1_three_zones/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, \ No newline at end of file +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/2_three_zones_w_electrolyzer/system/Network.csv b/example_systems/2_three_zones_w_electrolyzer/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/2_three_zones_w_electrolyzer/system/Network.csv +++ b/example_systems/2_three_zones_w_electrolyzer/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv b/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv index 238c5acd03..c2fcbd3628 100644 --- a/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv +++ b/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv @@ -1,4 +1,4 @@ -Resource,Zone,Model,New_Build,Can_Retire,Existing_Cap_MW,Existing_Cap_MWh,Max_Cap_MW,Max_Cap_MWh,Min_Cap_MW,Min_Cap_MWh,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster -MA_battery,1,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,MA,0 -CT_battery,2,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,CT,0 -ME_battery,3,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,ME,0 \ No newline at end of file +Resource,Zone,LDS,Model,New_Build,Can_Retire,Existing_Cap_MW,Existing_Cap_MWh,Max_Cap_MW,Max_Cap_MWh,Min_Cap_MW,Min_Cap_MWh,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster +MA_battery,1,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,MA,0 +CT_battery,2,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,CT,0 +ME_battery,3,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,ME,0 \ No newline at end of file diff --git a/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml b/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml index ac456b6fcb..27f3eef52f 100644 --- a/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml +++ b/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml @@ -10,3 +10,4 @@ ParameterScale: 1 # Turn on parameter scaling wherein demand, capacity and power WriteShadowPrices: 1 # Write shadow prices of LP or relaxed MILP; 0 = not active; 1 = active UCommit: 2 # Unit committment of thermal power plants; 0 = not active; 1 = active using integer clestering; 2 = active using linearized clustering TimeDomainReduction: 1 # Time domain reduce (i.e. cluster) inputs based on Demand_data.csv, Generators_variability.csv, and Fuels_data.csv; 0 = not active (use input data as provided); 0 = active (cluster input data, or use data that has already been clustered) +LDSAdditionalConstraints: 1 # Activate additional constraints to prevent violation of SoC limits in non-representative periods; 0 = not active; 1 = active \ No newline at end of file diff --git a/example_systems/3_three_zones_w_co2_capture/system/Network.csv b/example_systems/3_three_zones_w_co2_capture/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/3_three_zones_w_co2_capture/system/Network.csv +++ b/example_systems/3_three_zones_w_co2_capture/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/4_three_zones_w_policies_slack/system/Network.csv b/example_systems/4_three_zones_w_policies_slack/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/4_three_zones_w_policies_slack/system/Network.csv +++ b/example_systems/4_three_zones_w_policies_slack/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/5_three_zones_w_piecewise_fuel/system/Network.csv b/example_systems/5_three_zones_w_piecewise_fuel/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/5_three_zones_w_piecewise_fuel/system/Network.csv +++ b/example_systems/5_three_zones_w_piecewise_fuel/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/6_three_zones_w_multistage/inputs/inputs_p1/system/Network.csv b/example_systems/6_three_zones_w_multistage/inputs/inputs_p1/system/Network.csv index e233912403..b9d254d5a6 100644 --- a/example_systems/6_three_zones_w_multistage/inputs/inputs_p1/system/Network.csv +++ b/example_systems/6_three_zones_w_multistage/inputs/inputs_p1/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0,7000,0.062,30 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0,5000,0.062,30 -ME,z3,,,,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,7000,0.062,30 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,5000,0.062,30 +ME,z3,,,,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/6_three_zones_w_multistage/inputs/inputs_p2/system/Network.csv b/example_systems/6_three_zones_w_multistage/inputs/inputs_p2/system/Network.csv index e233912403..b9d254d5a6 100644 --- a/example_systems/6_three_zones_w_multistage/inputs/inputs_p2/system/Network.csv +++ b/example_systems/6_three_zones_w_multistage/inputs/inputs_p2/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0,7000,0.062,30 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0,5000,0.062,30 -ME,z3,,,,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,7000,0.062,30 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,5000,0.062,30 +ME,z3,,,,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/6_three_zones_w_multistage/inputs/inputs_p3/system/Network.csv b/example_systems/6_three_zones_w_multistage/inputs/inputs_p3/system/Network.csv index e233912403..b9d254d5a6 100644 --- a/example_systems/6_three_zones_w_multistage/inputs/inputs_p3/system/Network.csv +++ b/example_systems/6_three_zones_w_multistage/inputs/inputs_p3/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0,7000,0.062,30 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0,5000,0.062,30 -ME,z3,,,,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1,Line_Max_Flow_Possible_MW,WACC,Capital_Recovery_Period +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,7000,0.062,30 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,5000,0.062,30 +ME,z3,,,,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/7_three_zones_w_colocated_VRE_storage/system/Network.csv b/example_systems/7_three_zones_w_colocated_VRE_storage/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/7_three_zones_w_colocated_VRE_storage/system/Network.csv +++ b/example_systems/7_three_zones_w_colocated_VRE_storage/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/8_three_zones_w_colocated_VRE_storage_electrolyzers/system/Network.csv b/example_systems/8_three_zones_w_colocated_VRE_storage_electrolyzers/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/8_three_zones_w_colocated_VRE_storage_electrolyzers/system/Network.csv +++ b/example_systems/8_three_zones_w_colocated_VRE_storage_electrolyzers/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/example_systems/9_three_zones_w_retrofit/system/Network.csv b/example_systems/9_three_zones_w_retrofit/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/example_systems/9_three_zones_w_retrofit/system/Network.csv +++ b/example_systems/9_three_zones_w_retrofit/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/precompile/case/system/Network.csv b/precompile/case/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/precompile/case/system/Network.csv +++ b/precompile/case/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/src/case_runners/case_runner.jl b/src/case_runners/case_runner.jl index 84e2726e52..afea227f29 100644 --- a/src/case_runners/case_runner.jl +++ b/src/case_runners/case_runner.jl @@ -29,6 +29,7 @@ run_genx_case!("path/to/case", Gurobi.Optimizer) ``` """ function run_genx_case!(case::AbstractString, optimizer::Any = HiGHS.Optimizer) + print_genx_version() # Log the GenX version genx_settings = get_settings_path(case, "genx_settings.yml") # Settings YAML file path writeoutput_settings = get_settings_path(case, "output_settings.yml") # Write-output settings YAML file path mysetup = configure_settings(genx_settings, writeoutput_settings) # mysetup dictionary stores settings and GenX-specific parameters diff --git a/src/configure_settings/configure_settings.jl b/src/configure_settings/configure_settings.jl index 01d4603179..9df418e334 100644 --- a/src/configure_settings/configure_settings.jl +++ b/src/configure_settings/configure_settings.jl @@ -8,6 +8,7 @@ function default_settings() "CapacityReserveMargin" => 0, "CO2Cap" => 0, "StorageLosses" => 1, + "LDSAdditionalConstraints" => 1, "VirtualChargeDischargeCost" => 1, # $/MWh "MinCapReq" => 0, "MaxCapReq" => 0, diff --git a/src/load_inputs/load_resources_data.jl b/src/load_inputs/load_resources_data.jl index a88c48db6e..7966038a0a 100644 --- a/src/load_inputs/load_resources_data.jl +++ b/src/load_inputs/load_resources_data.jl @@ -522,7 +522,9 @@ function check_retrofit_id(rs::Vector{T}) where {T <: AbstractResource} retrofit_options = ids_retrofit_options(rs) # check that all retrofit_ids for resources that can retrofit and retrofit options match - if Set(rs[units_can_retrofit].retrofit_id) != Set(rs[retrofit_options].retrofit_id) + can_retrofit_retro_ids = [r.retrofit_id for r in rs[units_can_retrofit]] # retrofit cluster ids for units that can retrofit + retrofit_option_retro_ids = [r.retrofit_id for r in rs[retrofit_options]] # retrofit cluster ids for retrofit options + if Set(can_retrofit_retro_ids) != Set(retrofit_option_retro_ids) msg = string("Retrofit IDs for resources that \"can retrofit\" and \"retrofit options\" do not match.\n" * "All retrofitting units must be associated with a retrofit option.") push!(warning_strings, msg) diff --git a/src/model/core/operational_reserves.jl b/src/model/core/operational_reserves.jl index 54d6dab073..1790db3bb1 100644 --- a/src/model/core/operational_reserves.jl +++ b/src/model/core/operational_reserves.jl @@ -270,7 +270,7 @@ function operational_reserves_core!(EP::Model, inputs::Dict, setup::Dict) # N-1 contingency requirement is considered only if Unit Commitment is being modeled if UCommit >= 1 && (inputs["pDynamic_Contingency"] >= 1 || inputs["pStatic_Contingency"] > 0) - add_to_expression!(EP[:eRsvReq], EP[:eContingencyReq]) + add_similar_to_expression!(EP[:eRsvReq], EP[:eContingencyReq]) end ## Objective Function Expressions ## diff --git a/src/model/core/transmission/dcopf_transmission.jl b/src/model/core/transmission/dcopf_transmission.jl index de2dcfd5bf..ab2537ca01 100644 --- a/src/model/core/transmission/dcopf_transmission.jl +++ b/src/model/core/transmission/dcopf_transmission.jl @@ -1,5 +1,5 @@ @doc raw""" - function dcopf_transmission!(EP::Model, inputs::Dict, setup::Dict) + dcopf_transmission!(EP::Model, inputs::Dict, setup::Dict) The addtional constraints imposed upon the line flows in the case of DC-OPF are as follows: For the definition of the line flows, in terms of the voltage phase angles: ```math @@ -14,7 +14,7 @@ For imposing the constraint of maximum allowed voltage phase angle difference ac & \sum_{z\in \mathcal{Z}}{(\varphi^{map}_{l,z} \times \theta_{z,t})} \geq -\Delta \theta^{\max}_{l} \quad \forall l \in \mathcal{L}, \forall t \in \mathcal{T}\\ \end{aligned} ``` -Finally, we enforce the reference voltage phase angle constraint: +Finally, we enforce the reference voltage phase angle constraint (for the slack bus/reference bus): ```math \begin{aligned} \theta_{1,t} = 0 \quad \forall t \in \mathcal{T} diff --git a/src/model/core/transmission/investment_transmission.jl b/src/model/core/transmission/investment_transmission.jl index cb67e708eb..f80b898e9a 100644 --- a/src/model/core/transmission/investment_transmission.jl +++ b/src/model/core/transmission/investment_transmission.jl @@ -1,13 +1,15 @@ @doc raw""" - function investment_transmission!(EP::Model, inputs::Dict, setup::Dict) -This function model transmission expansion and adds transmission reinforcement or construction costs to the objective function. Transmission reinforcement costs are equal to the sum across all lines of the product between the transmission reinforcement/construction cost, $pi^{TCAP}_{l}$, times the additional transmission capacity variable, $\bigtriangleup\varphi^{cap}_{l}$. + investment_transmission!(EP::Model, inputs::Dict, setup::Dict) +This function model transmission expansion and adds transmission reinforcement or construction costs to the objective function. Transmission reinforcement costs are equal to the sum across all lines of the product between the transmission reinforcement/construction cost, $\pi^{TCAP}_{l}$, times the additional transmission capacity variable, $\bigtriangleup\varphi^{cap}_{l}$. ```math \begin{aligned} & \sum_{l \in \mathcal{L}}\left(\pi^{TCAP}_{l} \times \bigtriangleup\varphi^{cap}_{l}\right) \end{aligned} ``` Note that fixed O\&M and replacement capital costs (depreciation) for existing transmission capacity is treated as a sunk cost and not included explicitly in the GenX objective function. + **Accounting for Transmission Between Zones** + Available transmission capacity between zones is set equal to the existing line's maximum power transfer capacity, $\overline{\varphi^{cap}_{l}}$, plus any transmission capacity added on that line (for lines eligible for expansion in the set $\mathcal{E}$). ```math \begin{aligned} diff --git a/src/model/core/transmission/transmission.jl b/src/model/core/transmission/transmission.jl index ca5c637159..4020c8ea2e 100644 --- a/src/model/core/transmission/transmission.jl +++ b/src/model/core/transmission/transmission.jl @@ -25,7 +25,7 @@ Transmission losses due to power flows can be accounted for in three different w & \beta_{l,t}(\cdot) = \begin{cases} 0 & \text{if~} \text{losses.~0} \\ \\ \varphi^{loss}_{l}\times \mid \Phi_{l,t} \mid & \text{if~} \text{losses.~1} \\ \\ \ell_{l,t} &\text{if~} \text{losses.~2} \end{cases}, &\quad \forall l \in \mathcal{L},\forall t \in \mathcal{T} \end{aligned} ``` -For the second option, an absolute value approximation is utilized to calculate the magnitude of the power flow on each line (reflecting the fact that negative power flows for a line linking nodes $i$ and $j$ represents flows from node $j$ to $i$ and causes the same magnitude of losses as an equal power flow from $i$ to $j$). This absolute value function is linearized such that the flow in the line must be equal to the subtraction of the auxiliary variable for flow in the positive direction, $\Phi^{+}_{l,t}$, and the auxiliary variable for flow in the negative direction, $\Phi^{+}_{l,t}$, of the line. Then, the magnitude of the flow is calculated as the sum of the two auxiliary variables. The sum of positive and negative directional flows are also constrained by the line flow capacity. +For the second option, an absolute value approximation is utilized to calculate the magnitude of the power flow on each line (reflecting the fact that negative power flows for a line linking nodes $i$ and $j$ represents flows from node $j$ to $i$ and causes the same magnitude of losses as an equal power flow from $i$ to $j$). This absolute value function is linearized such that the flow in the line must be equal to the subtraction of the auxiliary variable for flow in the positive direction, $\Phi^{+}_{l,t}$, and the auxiliary variable for flow in the negative direction, $\Phi^{-}_{l,t}$, of the line. Then, the magnitude of the flow is calculated as the sum of the two auxiliary variables. The sum of positive and negative directional flows are also constrained by the line flow capacity. ```math \begin{aligned} % trasmission losses simple diff --git a/src/model/expression_manipulation.jl b/src/model/expression_manipulation.jl index 33b0b8e8e2..7d0dd4e566 100644 --- a/src/model/expression_manipulation.jl +++ b/src/model/expression_manipulation.jl @@ -138,6 +138,15 @@ function add_similar_to_expression!(expr1::AbstractArray{GenericAffExpr{C, T}, d return nothing end +# If the expressions are vectors of numbers, use the += operator +function add_similar_to_expression!(arr1::AbstractArray{T, dims}, + arr2::AbstractArray{T, dims}) where {T <: Number, dims} + for i in eachindex(arr1) + arr1[i] += arr2[i] + end + return nothing +end + ###### ###### ###### ###### ###### ###### # Element-wise addition of one term into an expression # Both arrays must have the same dimensions diff --git a/src/model/generate_model.jl b/src/model/generate_model.jl index d047fcbe11..0067b0c553 100644 --- a/src/model/generate_model.jl +++ b/src/model/generate_model.jl @@ -171,7 +171,7 @@ function generate_model(setup::Dict, inputs::Dict, OPTIMIZER::MOI.OptimizerWithA # Model constraints, variables, expression related to reservoir hydropower resources with long duration storage if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"]) - hydro_inter_period_linkage!(EP, inputs) + hydro_inter_period_linkage!(EP, inputs, setup) end # Model constraints, variables, expression related to demand flexibility resources diff --git a/src/model/resources/hydro/hydro_inter_period_linkage.jl b/src/model/resources/hydro/hydro_inter_period_linkage.jl index c1812173b4..19761d07ea 100644 --- a/src/model/resources/hydro/hydro_inter_period_linkage.jl +++ b/src/model/resources/hydro/hydro_inter_period_linkage.jl @@ -1,5 +1,5 @@ @doc raw""" - hydro_inter_period_linkage!(EP::Model, inputs::Dict) + hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) This function creates variables and constraints enabling modeling of long duration storage resources when modeling representative time periods. **Storage inventory balance at beginning of each representative period** @@ -42,8 +42,45 @@ Finally, the next constraint enforces that the initial storage level for each in \quad \forall n \in \mathcal{N}, o \in \mathcal{O}^{LDES} \end{aligned} ``` + +**Bound storage inventory in non-representative periods** +We need additional variables and constraints to ensure that the storage content is within zero and the installed energy capacity in non-representative periods. We introduce +the variables $\Delta Q^{max,pos}_{o,z,m}$ and $\Delta Q^{max,neg}_{o,z,m}$ that represent the maximum positive and negative storage content variations within the representative +period $m$, respectively, extracted as: + +```math +\begin{aligned} +& \Delta Q^{max,pos}_{o,z,m} \geq \Gamma_{o,z,(m-1)\times \tau^{period}+t } - \Gamma_{o,z,(m-1)\times \tau^{period}+1 } \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M}, t \in \mathcal{T} +& \end{aligned} +``` + +```math +\begin{aligned} +& \Delta Q^{max,neg}_{o,z,m} \leq \Gamma_{o,z,(m-1)\times \tau^{period}+t } - \Gamma_{o,z,(m-1)\times \tau^{period}+1 } \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M}, t \in \mathcal{T} +& \end{aligned} +``` + +For every input period $n \in \mathcal{N}$, the maximum storage is computed and constrained to be less than or equal to the installed energy capacity as: + +```math +\begin{aligned} +& Q_{o,z,n} \times \left(1-\eta_{o,z}^{loss}\right) - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} + \Delta Q^{max,pos}_{o,z,f(n)} \leq \Delta^{total, energy}_{o,z} \\ +& \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N} +& \end{aligned} +``` + +Similarly, the minimum storage content is imposed to be positive in every period of the time horizon: + +```math +\begin{aligned} +& Q_{o,z,n} \times \left(1-\eta_{o,z}^{loss}\right) - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} + \Delta Q^{max,neg}_{o,z,f(n)} \geq 0 \\ +& \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N} +& \end{aligned} +``` + +Additional details on this approach are available in [Parolin et al., 2024](https://doi.org/10.48550/arXiv.2409.19079). """ -function hydro_inter_period_linkage!(EP::Model, inputs::Dict) +function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) println("Long Duration Storage Module for Hydro Reservoir") gen = inputs["RESOURCES"] @@ -59,6 +96,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) MODELED_PERIODS_INDEX = 1:NPeriods REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX] + NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX) ### Variables ### @@ -71,6 +109,15 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) # Build up inventory can be positive or negative @variable(EP, vdSOC_HYDRO[y in STOR_HYDRO_LONG_DURATION, w = 1:REP_PERIOD]) + # Additional constraints to prevent violation of SoC limits in non-representative periods + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) + # Maximum positive storage inventory change within subperiod + @variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0) + + # Maximum negative storage inventory change within subperiod + @variable(EP, vdSOC_maxNeg_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] <= 0) + end + ### Constraints ### # Links last time step with first time step, ensuring position in hour 1 is within eligible change from final hour position @@ -111,4 +158,28 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) r in REP_PERIODS_INDEX], vSOC_HYDROw[y,r]==EP[:vS_HYDRO][y, hours_per_subperiod * dfPeriodMap[r, :Rep_Period_Index]] - vdSOC_HYDRO[y, dfPeriodMap[r, :Rep_Period_Index]]) + + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) + # Extract maximum storage level variation (positive) within subperiod + @constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + vdSOC_maxPos_HYDRO[y,w] >= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) + + # Extract maximum storage level variation (negative) within subperiod + @constraint(EP, cMaxSoCVarNeg_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + vdSOC_maxNeg_HYDRO[y,w] <= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) + + # Max storage content within each modeled period cannot exceed installed energy capacity + @constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX], + vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] + +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] + +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y]) + + # Min storage content within each modeled period cannot be negative + @constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX], + vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] + +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] + +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) + end end diff --git a/src/model/resources/resources.jl b/src/model/resources/resources.jl index 671f90dd01..7f42700bfd 100644 --- a/src/model/resources/resources.jl +++ b/src/model/resources/resources.jl @@ -66,9 +66,7 @@ Allows to set the attribute `sym` of an `AbstractResource` object using dot synt - `value`: The value to set for the attribute. """ -Base.setproperty!(r::AbstractResource, sym::Symbol, value) = setindex!(parent(r), - value, - sym) +Base.setproperty!(r::AbstractResource, sym::Symbol, value) = setindex!(parent(r), value, sym) """ haskey(r::AbstractResource, sym::Symbol) @@ -106,32 +104,31 @@ end """ Base.getproperty(rs::Vector{<:AbstractResource}, sym::Symbol) -Allows to access attributes of a vector of `AbstractResource` objects using dot syntax. If the `sym` is an element of the `resource_types` constant, it returns all resources of that type. Otherwise, it returns the value of the attribute for each resource in the vector. +Allows to return all resources of a given type of a vector of `AbstractResource` objects using dot syntax. # Arguments: - `rs::Vector{<:AbstractResource}`: The vector of `AbstractResource` objects. -- `sym::Symbol`: The symbol representing the attribute name or a type from `resource_types`. +- `sym::Symbol`: The symbol representing the type from `resource_types`. # Returns: - If `sym` is an element of the `resource_types` constant, it returns a vector containing all resources of that type. -- If `sym` is an attribute name, it returns a vector containing the value of the attribute for each resource. ## Examples ```julia julia> vre_gen = gen.Vre; # gen vector of resources julia> typeof(vre_gen) Vector{Vre} (alias for Array{Vre, 1}) -julia> vre_gen.zone +julia> GenX.zone_id.(vre_gen) ``` """ function Base.getproperty(rs::Vector{<:AbstractResource}, sym::Symbol) - # if sym is Type then return a vector resources of that type + # if sym is one of the resource types then return a vector resources of that type if sym ∈ resource_types res_type = eval(sym) return Vector{res_type}(rs[isa.(rs, res_type)]) + else + return getfield(rs, sym) end - # if sym is a field of the resource then return that field for all resources - return [getproperty(r, sym) for r in rs] end """ @@ -255,8 +252,7 @@ julia> findall(r -> max_cap_mwh(r) != 0, gen.Storage) 50 ``` """ -Base.findall(f::Function, rs::Vector{<:AbstractResource}) = resource_id.(filter(r -> f(r), - rs)) +Base.findall(f::Function, rs::Vector{<:AbstractResource}) = resource_id.(filter(r -> f(r), rs)) """ interface(name, default=default_zero, type=AbstractResource) @@ -531,14 +527,14 @@ const default_zero = 0 # INTERFACE FOR ALL RESOURCES resource_name(r::AbstractResource) = r.resource -resource_name(rs::Vector{T}) where {T <: AbstractResource} = rs.resource +resource_name(rs::Vector{T}) where {T <: AbstractResource} = resource_name.(rs) resource_id(r::AbstractResource)::Int64 = r.id resource_id(rs::Vector{T}) where {T <: AbstractResource} = resource_id.(rs) resource_type_mga(r::AbstractResource) = r.resource_type zone_id(r::AbstractResource) = r.zone -zone_id(rs::Vector{T}) where {T <: AbstractResource} = rs.zone +zone_id(rs::Vector{T}) where {T <: AbstractResource} = zone_id.(rs) # getter for boolean attributes (true or false) with validation function new_build(r::AbstractResource) diff --git a/src/model/resources/storage/long_duration_storage.jl b/src/model/resources/storage/long_duration_storage.jl index 3730d3dff1..189381f895 100644 --- a/src/model/resources/storage/long_duration_storage.jl +++ b/src/model/resources/storage/long_duration_storage.jl @@ -55,7 +55,44 @@ If the capacity reserve margin constraint is enabled, a similar set of constrain & \frac{1}{\eta_{o,z}^{discharge}}\Theta^{CRM}_{o,z,(m-1)\times \tau^{period}+1} - \eta_{o,z}^{charge}\Pi^{CRM}_{o,z,(m-1)\times \tau^{period}+1} \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M} \end{aligned} ``` -All other constraints are identical to those used to track the actual state of charge, except with the new variables $Q^{CRM}_{o,z,n}$ and $\Delta Q^{CRM}_{o,z,n}$ used in place of $Q_{o,z,n}$ and $\Delta Q_{o,z,n}$, respectively. +All other constraints are identical to those used to track the actual state of charge, except with the new variables $Q^{CRM}_{o,z,n}$ and $\Delta Q^{CRM}_{o,z,n}$ used in place of $Q_{o,z,n}$ and $\Delta Q_{o,z,n}$, respectively. \ + +**Bound storage inventory in non-representative periods** +We need additional variables and constraints to ensure that the storage content is within zero and the installed energy capacity in non-representative periods. We introduce +the variables $\Delta Q^{max,pos}_{o,z,m}$ and $\Delta Q^{max,neg}_{o,z,m}$ that represent the maximum positive and negative storage content variations within the representative +period $m$, respectively, extracted as: + +```math +\begin{aligned} +& \Delta Q^{max,pos}_{o,z,m} \geq \Gamma_{o,z,(m-1)\times \tau^{period}+t } - \Gamma_{o,z,(m-1)\times \tau^{period}+1 } \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M}, t \in \mathcal{T} +& \end{aligned} +``` + +```math +\begin{aligned} +& \Delta Q^{max,neg}_{o,z,m} \leq \Gamma_{o,z,(m-1)\times \tau^{period}+t } - \Gamma_{o,z,(m-1)\times \tau^{period}+1 } \quad \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, m \in \mathcal{M}, t \in \mathcal{T} +& \end{aligned} +``` + +For every input period $n \in \mathcal{N}$, the maximum storage is computed and constrained to be less than or equal to the installed energy capacity as: + +```math +\begin{aligned} +& Q_{o,z,n} \times \left(1-\eta_{o,z}^{loss}\right) - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} + \Delta Q^{max,pos}_{o,z,f(n)} \leq \Delta^{total, energy}_{o,z} \\ +& \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N} +& \end{aligned} +``` + +Similarly, the minimum storage content is imposed to be positive in every period of the time horizon: + +```math +\begin{aligned} +& Q_{o,z,n} \times \left(1-\eta_{o,z}^{loss}\right) - \frac{1}{\eta_{o,z}^{discharge}}\Theta_{o,z,(m-1)\times \tau^{period}+1} + \eta_{o,z}^{charge}\Pi_{o,z,(m-1)\times \tau^{period}+1} + \Delta Q^{max,neg}_{o,z,f(n)} \geq 0 \\ +& \forall o \in \mathcal{O}^{LDES}, z \in \mathcal{Z}, n \in \mathcal{N} +& \end{aligned} +``` + +Additional details on this approach are available in [Parolin et al., 2024](https://doi.org/10.48550/arXiv.2409.19079). """ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) println("Long Duration Storage Module") @@ -75,6 +112,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) MODELED_PERIODS_INDEX = 1:NPeriods REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX] + NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX) ### Variables ### @@ -96,6 +134,15 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) @variable(EP, vCAPRES_dsoc[y in STOR_LONG_DURATION, w = 1:REP_PERIOD]) end + # Additional constraints to prevent violation of SoC limits in non-representative periods + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) + # Maximum positive storage inventory change within subperiod + @variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0) + + # Maximum negative storage inventory change within subperiod + @variable(EP, vdSOC_maxNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD] <= 0) + end + ### Constraints ### # Links last time step with first time step, ensuring position in hour 1 is within eligible change from final hour position @@ -181,4 +228,27 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) r in MODELED_PERIODS_INDEX], vSOCw[y, r]>=vCAPRES_socw[y, r]) end + + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) + # Extract maximum storage level variation (positive) within subperiod + @constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + vdSOC_maxPos[y,w] >= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) + + # Extract maximum storage level variation (negative) within subperiod + @constraint(EP, cMaxSoCVarNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + vdSOC_maxNeg[y,w] <= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) + + # Max storage content within each modeled period cannot exceed installed energy capacity + @constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX], + (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y]) + + # Min storage content within each modeled period cannot be negative + @constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX], + (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) + end end + diff --git a/src/model/resources/vre_stor/vre_stor.jl b/src/model/resources/vre_stor/vre_stor.jl index c367199a67..2e93bbc024 100644 --- a/src/model/resources/vre_stor/vre_stor.jl +++ b/src/model/resources/vre_stor/vre_stor.jl @@ -927,12 +927,20 @@ The following two constraints track the state of charge of the storage resources \end{aligned} ``` -The last constraint limits the volume of energy stored at any time, $\Gamma_{y,z,t}$, to be less than the installed energy storage capacity, $\Delta^{total, energy}_{y,z}$. +This constraint limits the volume of energy stored at any time, $\Gamma_{y,z,t}$, to be less than the installed energy storage capacity, $\Delta^{total, energy}_{y,z}$. ```math \begin{aligned} & \Gamma_{y,z,t} \leq \Delta^{total, energy}_{y,z} & \quad \forall y \in \mathcal{VS}^{stor}, z \in \mathcal{Z}, t \in \mathcal{T} \end{aligned} ``` + +The last constraint limits the volume of energy exported from the grid to the storage at any time, $\Pi_{y,z,t}$, to be less than the electricity charged to the energy storage component, $\Pi_{y,z,t}^{ac} + \frac{\Pi^{dc}_{y,z,t}}{\eta^{inverter}_{y,z}}$. +```math +\begin{aligned} + & \Pi_{y,z,t} = \Pi_{y,z,t}^{ac} + \frac{\Pi^{dc}_{y,z,t}}{\eta^{inverter}_{y,z}} +\end{aligned} +``` + The next set of constraints only apply to symmetric storage resources (all $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{sym,ac}$). For storage technologies with symmetric charge and discharge capacity (all $y \in \mathcal{VS}^{sym,dc} \cup y \in \mathcal{VS}^{sym,ac}$), since storage resources generally represent a 'cluster' of multiple similar storage devices of the same type/cost in the same zone, GenX @@ -1131,6 +1139,9 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) CONSTRAINTSET = STOR end + # total charging expressions: total storage charge (including both AC and DC) [MWh] + @expression(EP, eCHARGE_VS_STOR[y in STOR, t = 1:T], JuMP.AffExpr()) + # SoC expressions @expression(EP, eSoCBalStart_VRE_STOR[y in CONSTRAINTSET, t in START_SUBPERIODS], vS_VRE_STOR[y, @@ -1180,6 +1191,7 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) by_rid(y, :etainverter) for t in 1:T) for t in 1:T EP[:eInvACBalance][y, t] -= vP_DC_CHARGE[y, t] / by_rid(y, :etainverter) + EP[:eCHARGE_VS_STOR][y, t] += vP_DC_CHARGE[y, t] / by_rid(y, :etainverter) EP[:eInverterExport][y, t] += vP_DC_CHARGE[y, t] / by_rid(y, :etainverter) end for t in INTERIOR_SUBPERIODS @@ -1204,6 +1216,7 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) EP[:eELOSS_VRE_STOR][y] += sum(inputs["omega"][t] * vP_AC_CHARGE[y, t] for t in 1:T) for t in 1:T EP[:eInvACBalance][y, t] -= vP_AC_CHARGE[y, t] + EP[:eCHARGE_VS_STOR][y, t] += vP_AC_CHARGE[y, t] end for t in INTERIOR_SUBPERIODS eSoCBalInterior_VRE_STOR[y, t] += by_rid(y, :eff_up_ac) * @@ -1286,6 +1299,9 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) if rep_periods > 1 && !isempty(VS_LDS) lds_vre_stor!(EP, inputs) end + + # Constraint 4: electricity charged from the grid cannot exceed the charging capacity of the storage component in VRE_STOR + @constraint(EP, [y in STOR, t = 1:T], vCHARGE_VRE_STOR[y,t] <= eCHARGE_VS_STOR[y,t]) end @doc raw""" diff --git a/src/startup/genx_startup.jl b/src/startup/genx_startup.jl index 58f1e7ba70..76ff8fdbc2 100644 --- a/src/startup/genx_startup.jl +++ b/src/startup/genx_startup.jl @@ -1,7 +1,3 @@ -function __init__() - print_genx_version() -end - function print_genx_version() v = pkgversion(GenX) ascii_art = raw""" @@ -54,6 +50,9 @@ Returns `nothing`. """ function _precompile() @info "Running precompile script for GenX. This may take a few minutes." + @info "If you want to skip this step, please set the environment variable " * + "`GENX_PRECOMPILE` to `false` before running `using GenX`. \n" * + "Example: `ENV[\"GENX_PRECOMPILE\"] = \"false\"`." redirect_stdout(devnull) do warnerror_logger = ConsoleLogger(stderr, Logging.Warn) with_logger(warnerror_logger) do diff --git a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl index 707147556b..418b14eeab 100644 --- a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl @@ -73,29 +73,18 @@ function write_reserve_margin_revenue(path::AbstractString, gen_VRE_STOR = gen.VreStorage tempresrev[VRE_STOR] = derating_factor.(gen_VRE_STOR, tag = i) .* ((value.(EP[:vP][VRE_STOR, :])) * weighted_price) - tempresrev[VRE_STOR_STOR] .-= derating_factor.( - gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0) .| (gen_VRE_STOR.stor_dc_charge .!= 0) .| (gen_VRE_STOR.stor_ac_discharge .!= 0) .| (gen_VRE_STOR.stor_ac_charge .!= 0)], - tag = i) .* (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, - :]).data * weighted_price) - tempresrev[DC_DISCHARGE] .+= derating_factor.( - gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)], - tag = i) .* ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, - :]).data .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)])) * - weighted_price) - tempresrev[AC_DISCHARGE] .+= derating_factor.( - gen_VRE_STOR[(gen_VRE_STOR.stor_ac_discharge .!= 0)], - tag = i) .* ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, - :]).data) * weighted_price) - tempresrev[DC_CHARGE] .-= derating_factor.( - gen_VRE_STOR[(gen_VRE_STOR.stor_dc_charge .!= 0)], - tag = i) .* ((value.(EP[:vCAPRES_DC_CHARGE][DC_CHARGE, :]).data ./ - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_charge .!= 0)])) * - weighted_price) - tempresrev[AC_CHARGE] .-= derating_factor.( - gen_VRE_STOR[(gen_VRE_STOR.stor_ac_charge .!= 0)], - tag = i) .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * - weighted_price) + tempresrev[VRE_STOR_STOR] .-= derating_factor.(gen[VRE_STOR_STOR], tag = i) .* + (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, :]).data * weighted_price) + tempresrev[DC_DISCHARGE] .+= derating_factor.(gen[DC_DISCHARGE], tag = i) .* + ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, :]).data .* + etainverter.(gen[DC_DISCHARGE])) * weighted_price) + tempresrev[AC_DISCHARGE] .+= derating_factor.(gen[AC_DISCHARGE], tag = i) .* + ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, :]).data) * weighted_price) + tempresrev[DC_CHARGE] .-= derating_factor.(gen[DC_CHARGE], tag = i) .* + ((value.(EP[:vCAPRES_DC_CHARGE][DC_CHARGE, :]).data ./ + etainverter.(gen[DC_CHARGE])) * weighted_price) + tempresrev[AC_CHARGE] .-= derating_factor.(gen[AC_CHARGE], tag = i) .* + ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) end tempresrev *= scale_factor annual_sum .+= tempresrev diff --git a/src/write_outputs/energy_share_requirement/write_esr_revenue.jl b/src/write_outputs/energy_share_requirement/write_esr_revenue.jl index a9298663e8..580ee45837 100644 --- a/src/write_outputs/energy_share_requirement/write_esr_revenue.jl +++ b/src/write_outputs/energy_share_requirement/write_esr_revenue.jl @@ -47,39 +47,20 @@ function write_esr_revenue(path::AbstractString, if !isempty(VRE_STOR) if !isempty(SOLAR_ONLY) - solar_resources = ((gen_VRE_STOR.wind .== 0) .& (gen_VRE_STOR.solar .!= 0)) - dfESRRev[SOLAR, esr_col] = (value.(EP[:vP_SOLAR][SOLAR, :]).data - .* - etainverter.(gen_VRE_STOR[solar_resources]) * - weight) .* - esr_vrestor.(gen_VRE_STOR[solar_resources], - tag = i) * price + dfESRRev[SOLAR, esr_col] = (value.(EP[:vP_SOLAR][SOLAR, :]).data .* + etainverter.(gen[SOLAR]) * weight) .* + esr_vrestor.(gen[SOLAR], tag = i) * price end if !isempty(WIND_ONLY) - wind_resources = ((gen_VRE_STOR.wind .!= 0) .& (gen_VRE_STOR.solar .== 0)) - dfESRRev[WIND, esr_col] = (value.(EP[:vP_WIND][WIND, :]).data - * - weight) .* - esr_vrestor.(gen_VRE_STOR[wind_resources], - tag = i) * price + dfESRRev[WIND, esr_col] = (value.(EP[:vP_WIND][WIND, :]).data * weight) .* + esr_vrestor.(gen[WIND], tag = i) * price end if !isempty(SOLAR_WIND) - solar_and_wind_resources = ((gen_VRE_STOR.wind .!= 0) .& - (gen_VRE_STOR.solar .!= 0)) - dfESRRev[SOLAR_WIND, esr_col] = (((value.(EP[:vP_WIND][SOLAR_WIND, - :]).data * weight) - .* - esr_vrestor.( - gen_VRE_STOR[solar_and_wind_resources], - tag = i) * price) + - (value.(EP[:vP_SOLAR][SOLAR_WIND, :]).data - .* - etainverter.(gen_VRE_STOR[solar_and_wind_resources]) - * - weight) .* - esr_vrestor.( - gen_VRE_STOR[solar_and_wind_resources], - tag = i) * price) + dfESRRev[SOLAR_WIND, esr_col] = (((value.(EP[:vP_WIND][SOLAR_WIND, :]).data * weight) .* + esr_vrestor.(gen[SOLAR_WIND], tag = i) * price) + + (value.(EP[:vP_SOLAR][SOLAR_WIND, :]).data .* + etainverter.(gen[SOLAR_WIND]) * weight) .* + esr_vrestor.(gen[SOLAR_WIND], tag = i) * price) end end end diff --git a/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl b/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl index 66c8ed7efb..e0a731d177 100644 --- a/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl +++ b/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl @@ -32,4 +32,54 @@ function write_opwrap_lds_stor_init(path::AbstractString, CSV.write(joinpath(path, "StorageInit.csv"), dftranspose(dfStorageInit, false), header = false) + + # Write storage evolution over full time horizon + hours_per_subperiod = inputs["hours_per_subperiod"]; + t_interior = 2:hours_per_subperiod + T_hor = hours_per_subperiod*NPeriods # total number of time steps in time horizon + SOC_t = zeros(G, T_hor) + stor_long_duration = inputs["STOR_LONG_DURATION"] + stor_hydro_long_duration = inputs["STOR_HYDRO_LONG_DURATION"] + period_map = inputs["Period_Map"].Rep_Period_Index + pP_max = inputs["pP_Max"] + e_total_cap = value.(EP[:eTotalCap]) + v_charge = value.(EP[:vCHARGE]) + v_P = value.(EP[:vP]) + if setup["ParameterScale"] == 1 + v_charge *= ModelScalingFactor + v_P *= ModelScalingFactor + end + if !isempty(stor_hydro_long_duration) + v_spill = value.(EP[:vSPILL]) + end + for r in 1:NPeriods + w = period_map[r] + t_r = hours_per_subperiod * (r - 1) + 1 + t_start_w = hours_per_subperiod * (w - 1) + 1 + t_interior = 2:hours_per_subperiod + + if !isempty(stor_long_duration) + SOC_t[stor_long_duration, t_r] = socw[stor_long_duration, r] .* (1 .- self_discharge.(gen[stor_long_duration])) .+ efficiency_up.(gen[stor_long_duration]) .* v_charge[stor_long_duration, t_start_w] .- 1 ./ efficiency_down.(gen[stor_long_duration]) .* v_P[stor_long_duration, t_start_w] + + for t_int in t_interior + t = hours_per_subperiod * (w - 1) + t_int + SOC_t[stor_long_duration, t_r + t_int - 1] = SOC_t[stor_long_duration, t_r + t_int - 2] .* (1 .- self_discharge.(gen[stor_long_duration])) .+ efficiency_up.(gen[stor_long_duration]) .* v_charge[stor_long_duration, t] .- 1 ./ efficiency_down.(gen[stor_long_duration]) .* v_P[stor_long_duration, t] + end + end + + if !isempty(stor_hydro_long_duration) + SOC_t[stor_hydro_long_duration, t_r] = socw[stor_hydro_long_duration, r] .- 1 ./ efficiency_down.(gen[stor_long_duration]) .* v_P[stor_hydro_long_duration, t_start_w] .- v_spill[stor_hydro_long_duration, t_start_w] .+ pP_max[stor_hydro_long_duration, t_start_w] .* e_total_cap[stor_hydro_long_duration] + + for t_int in t_interior + t = hours_per_subperiod * (w - 1) + t_int + SOC_t[stor_hydro_long_duration, t_r + t_int - 1] = SOC_t[stor_hydro_long_duration, t_r + t_int - 2] .- 1 ./ efficiency_down.(gen[stor_long_duration]) .* v_P[stor_hydro_long_duration, t] .- v_spill[stor_hydro_long_duration, t] .+ pP_max[stor_hydro_long_duration, t] .* e_total_cap[stor_hydro_long_duration] + end + end + end + df_SOC_t = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) + df_SOC_t = hcat(df_SOC_t, DataFrame(SOC_t, :auto)) + auxNew_Names = [Symbol("Resource"); Symbol("Zone"); [Symbol("n$t") for t in 1:T_hor]] + rename!(df_SOC_t,auxNew_Names) + CSV.write(joinpath(path, "StorageEvol.csv"), dftranspose(df_SOC_t, false), writeheader=false) + end diff --git a/src/write_outputs/write_costs.jl b/src/write_outputs/write_costs.jl index c54206fcae..9193865911 100644 --- a/src/write_outputs/write_costs.jl +++ b/src/write_outputs/write_costs.jl @@ -127,6 +127,10 @@ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) dfCost[9, 2] += value(EP[:eTotalCMinCapSlack]) end + if haskey(inputs, "MaxCapPriceCap") + dfCost[9, 2] += value(EP[:eTotalCMaxCapSlack]) + end + if haskey(inputs, "H2DemandPriceCap") dfCost[9, 2] += value(EP[:eTotalCH2DemandSlack]) end @@ -166,7 +170,7 @@ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) ELECTROLYZERS_ZONE = intersect(inputs["ELECTROLYZER"], Y_ZONE) CCS_ZONE = intersect(inputs["CCS"], Y_ZONE) - eCFix = sum(value.(EP[:eCFix][Y_ZONE])) + eCFix = sum(value.(EP[:eCFix][Y_ZONE]), init = 0.0) tempCFix += eCFix tempCTotal += eCFix diff --git a/src/write_outputs/write_curtailment.jl b/src/write_outputs/write_curtailment.jl index aaa9234e59..4d41289750 100644 --- a/src/write_outputs/write_curtailment.jl +++ b/src/write_outputs/write_curtailment.jl @@ -25,12 +25,11 @@ function write_curtailment(path::AbstractString, inputs::Dict, setup::Dict, EP:: SOLAR = setdiff(inputs["VS_SOLAR"], inputs["VS_WIND"]) WIND = setdiff(inputs["VS_WIND"], inputs["VS_SOLAR"]) SOLAR_WIND = intersect(inputs["VS_SOLAR"], inputs["VS_WIND"]) - gen_VRE_STOR = gen.VreStorage if !isempty(SOLAR) curtailment[SOLAR, :] = (value.(EP[:eTotalCap_SOLAR][SOLAR]).data .* - inputs["pP_Max_Solar"][SOLAR, :] .- - value.(EP[:vP_SOLAR][SOLAR, :]).data) .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.solar .!= 0)]) + inputs["pP_Max_Solar"][SOLAR, :] .- + value.(EP[:vP_SOLAR][SOLAR, :]).data) .* + etainverter.(gen[SOLAR]) end if !isempty(WIND) curtailment[WIND, :] = (value.(EP[:eTotalCap_WIND][WIND]).data .* @@ -41,7 +40,7 @@ function write_curtailment(path::AbstractString, inputs::Dict, setup::Dict, EP:: curtailment[SOLAR_WIND, :] = ((value.(EP[:eTotalCap_SOLAR])[SOLAR_WIND].data .* inputs["pP_Max_Solar"][SOLAR_WIND, :] .- value.(EP[:vP_SOLAR][SOLAR_WIND, :]).data) .* - etainverter.(gen_VRE_STOR[((gen_VRE_STOR.wind .!= 0) .& (gen_VRE_STOR.solar .!= 0))]) + etainverter.(gen[SOLAR_WIND]) + (value.(EP[:eTotalCap_WIND][SOLAR_WIND]).data .* inputs["pP_Max_Wind"][SOLAR_WIND, :] .- diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index ef42e9ca87..2311c8d1a4 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -87,39 +87,37 @@ function write_net_revenue(path::AbstractString, dfCap[1:G, :EndEnergyCap] dfNetRevenue.Fixed_OM_cost_charge_MW = fixed_om_cost_charge_per_mwyr.(gen) .* dfCap[1:G, :EndChargeCap] - dfNetRevenue.Var_OM_cost_out = var_om_cost_per_mwh.(gen) .* dfPower[1:G, :AnnualSum] if !isempty(VRE_STOR) if !isempty(SOLAR) dfNetRevenue.Fixed_OM_cost_MW[VRE_STOR] += fixed_om_solar_cost_per_mwyr.(gen_VRE_STOR) .* dfVreStor[1:VRE_STOR_LENGTH, :EndCapSolar] - dfNetRevenue.Var_OM_cost_out[SOLAR] += var_om_cost_per_mwh_solar.(gen_VRE_STOR[(gen_VRE_STOR.solar .!= 0)]) .* - (value.(EP[:vP_SOLAR][SOLAR, :]).data .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.solar .!= 0)]) * + dfNetRevenue.Var_OM_cost_out[SOLAR] += var_om_cost_per_mwh_solar.(gen[SOLAR]) .* + (value.(EP[:vP_SOLAR][SOLAR, :]).data .* + etainverter.(gen[SOLAR]) * inputs["omega"]) end if !isempty(WIND) dfNetRevenue.Fixed_OM_cost_MW[VRE_STOR] += fixed_om_wind_cost_per_mwyr.(gen_VRE_STOR) .* - dfVreStor[1:VRE_STOR_LENGTH, - :EndCapWind] - dfNetRevenue.Var_OM_cost_out[WIND] += var_om_cost_per_mwh_wind.(gen_VRE_STOR[(gen_VRE_STOR.wind .!= 0)]) .* - (value.(EP[:vP_WIND][WIND, :]).data * - inputs["omega"]) + dfVreStor[1:VRE_STOR_LENGTH, :EndCapWind] + + dfNetRevenue.Var_OM_cost_out[WIND] += var_om_cost_per_mwh_wind.(gen[WIND]) .* + (value.(EP[:vP_WIND][WIND, :]).data * + inputs["omega"]) end if !isempty(DC) dfNetRevenue.Fixed_OM_cost_MW[VRE_STOR] += fixed_om_inverter_cost_per_mwyr.(gen_VRE_STOR) .* - dfVreStor[1:VRE_STOR_LENGTH, - :EndCapDC] + dfVreStor[1:VRE_STOR_LENGTH, :EndCapDC] end if !isempty(DC_DISCHARGE) - dfNetRevenue.Var_OM_cost_out[DC_DISCHARGE] += var_om_cost_per_mwh_discharge_dc.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)]) .* - (value.(EP[:vP_DC_DISCHARGE][DC_DISCHARGE,:]).data .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)]) * - inputs["omega"]) + dfNetRevenue.Var_OM_cost_out[DC_DISCHARGE] += var_om_cost_per_mwh_discharge_dc.(gen[DC_DISCHARGE]) .* + (value.(EP[:vP_DC_DISCHARGE][DC_DISCHARGE,:]).data .* + etainverter.(gen[DC_DISCHARGE]) * + inputs["omega"]) end if !isempty(AC_DISCHARGE) - dfNetRevenue.Var_OM_cost_out[AC_DISCHARGE] += var_om_cost_per_mwh_discharge_ac.(gen_VRE_STOR[(gen_VRE_STOR.stor_ac_discharge .!= 0)]) .* + dfNetRevenue.Var_OM_cost_out[AC_DISCHARGE] += var_om_cost_per_mwh_discharge_ac.(gen[AC_DISCHARGE]) .* (value.(EP[:vP_AC_DISCHARGE][AC_DISCHARGE,:]).data * inputs["omega"]) end end @@ -145,15 +143,14 @@ function write_net_revenue(path::AbstractString, end if !isempty(VRE_STOR) if !isempty(DC_CHARGE) - dfNetRevenue.Var_OM_cost_in[DC_CHARGE] += var_om_cost_per_mwh_charge_dc.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_charge .!= 0)]) .* + dfNetRevenue.Var_OM_cost_in[DC_CHARGE] += var_om_cost_per_mwh_charge_dc.(gen[DC_CHARGE]) .* (value.(EP[:vP_DC_CHARGE][DC_CHARGE,:]).data ./ - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_charge .!= 0)]) * + etainverter.(gen[DC_CHARGE]) * inputs["omega"]) end if !isempty(AC_CHARGE) - dfNetRevenue.Var_OM_cost_in[AC_CHARGE] += var_om_cost_per_mwh_charge_ac.(gen_VRE_STOR[(gen_VRE_STOR.stor_ac_charge .!= 0)]) .* - (value.(EP[:vP_AC_CHARGE][AC_CHARGE, - :]).data * inputs["omega"]) + dfNetRevenue.Var_OM_cost_in[AC_CHARGE] += var_om_cost_per_mwh_charge_ac.(gen[AC_CHARGE]) .* + (value.(EP[:vP_AC_CHARGE][AC_CHARGE, :]).data * inputs["omega"]) end end @@ -258,30 +255,18 @@ function write_net_revenue(path::AbstractString, .+dfNetRevenue.OperatingReserveRevenue .+dfNetRevenue.OperatingRegulationRevenue - dfNetRevenue.Cost = (dfNetRevenue.Inv_cost_MW - .+ - dfNetRevenue.Inv_cost_MWh - .+ - dfNetRevenue.Inv_cost_charge_MW - .+ - dfNetRevenue.Fixed_OM_cost_MW - .+ - dfNetRevenue.Fixed_OM_cost_MWh - .+ - dfNetRevenue.Fixed_OM_cost_charge_MW - .+ - dfNetRevenue.Var_OM_cost_out - .+ - dfNetRevenue.Var_OM_cost_in - .+ - dfNetRevenue.Fuel_cost - .+ - dfNetRevenue.Charge_cost - .+ - dfNetRevenue.EmissionsCost - .+ - dfNetRevenue.StartCost - .+ + dfNetRevenue.Cost = (dfNetRevenue.Inv_cost_MW .+ + dfNetRevenue.Inv_cost_MWh .+ + dfNetRevenue.Inv_cost_charge_MW .+ + dfNetRevenue.Fixed_OM_cost_MW .+ + dfNetRevenue.Fixed_OM_cost_MWh .+ + dfNetRevenue.Fixed_OM_cost_charge_MW .+ + dfNetRevenue.Var_OM_cost_out .+ + dfNetRevenue.Var_OM_cost_in .+ + dfNetRevenue.Fuel_cost .+ + dfNetRevenue.Charge_cost .+ + dfNetRevenue.EmissionsCost .+ + dfNetRevenue.StartCost .+ dfNetRevenue.CO2SequestrationCost) dfNetRevenue.Profit = dfNetRevenue.Revenue .- dfNetRevenue.Cost diff --git a/src/write_outputs/write_subsidy_revenue.jl b/src/write_outputs/write_subsidy_revenue.jl index f74bede14c..3262ec94d7 100644 --- a/src/write_outputs/write_subsidy_revenue.jl +++ b/src/write_outputs/write_subsidy_revenue.jl @@ -54,59 +54,39 @@ function write_subsidy_revenue(path::AbstractString, inputs::Dict, setup::Dict, if !isempty(inputs["VRE_STOR"]) gen_VRE_STOR = gen.VreStorage HAS_MIN_CAP_STOR = ids_with_policy(gen_VRE_STOR, min_cap_stor, tag = mincap) - MIN_CAP_GEN_SOLAR = ids_with_policy(gen_VRE_STOR, - min_cap_solar, - tag = mincap) + MIN_CAP_GEN_SOLAR = ids_with_policy(gen_VRE_STOR, min_cap_solar, tag = mincap) MIN_CAP_GEN_WIND = ids_with_policy(gen_VRE_STOR, min_cap_wind, tag = mincap) - MIN_CAP_GEN_ASYM_DC_DIS = intersect(inputs["VS_ASYM_DC_DISCHARGE"], - HAS_MIN_CAP_STOR) - MIN_CAP_GEN_ASYM_AC_DIS = intersect(inputs["VS_ASYM_AC_DISCHARGE"], - HAS_MIN_CAP_STOR) + MIN_CAP_GEN_ASYM_DC_DIS = intersect(inputs["VS_ASYM_DC_DISCHARGE"], HAS_MIN_CAP_STOR) + MIN_CAP_GEN_ASYM_AC_DIS = intersect(inputs["VS_ASYM_AC_DISCHARGE"], HAS_MIN_CAP_STOR) MIN_CAP_GEN_SYM_DC = intersect(inputs["VS_SYM_DC"], HAS_MIN_CAP_STOR) MIN_CAP_GEN_SYM_AC = intersect(inputs["VS_SYM_AC"], HAS_MIN_CAP_STOR) if !isempty(MIN_CAP_GEN_SOLAR) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SOLAR] .+= ((value.(EP[:eTotalCap_SOLAR][MIN_CAP_GEN_SOLAR]).data) - .* - etainverter.(gen[ids_with_policy( - gen, - min_cap_solar, - tag = mincap)]) - * - (dual.(EP[:cZoneMinCapReq][mincap]))) + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SOLAR] .+= ((value.(EP[:eTotalCap_SOLAR][MIN_CAP_GEN_SOLAR]).data) .* + etainverter.(gen[MIN_CAP_GEN_SOLAR]) * + (dual.(EP[:cZoneMinCapReq][mincap]))) end if !isempty(MIN_CAP_GEN_WIND) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_WIND] .+= ((value.(EP[:eTotalCap_WIND][MIN_CAP_GEN_WIND]).data) - * + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_WIND] .+= ((value.(EP[:eTotalCap_WIND][MIN_CAP_GEN_WIND]).data) * (dual.(EP[:cZoneMinCapReq][mincap]))) end if !isempty(MIN_CAP_GEN_ASYM_DC_DIS) - MIN_CAP_GEN_ASYM_DC_DIS = intersect(inputs["VS_ASYM_DC_DISCHARGE"], - HAS_MIN_CAP_STOR) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_ASYM_DC_DIS] .+= ((value.(EP[:eTotalCapDischarge_DC][MIN_CAP_GEN_ASYM_DC_DIS].data) - .* - etainverter.(gen_VRE_STOR[min_cap_stor.(gen_VRE_STOR, tag = mincap) .== 1 .& (gen_VRE_STOR.stor_dc_discharge .== 2)])) - * - (dual.(EP[:cZoneMinCapReq][mincap]))) + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_ASYM_DC_DIS] .+= ((value.(EP[:eTotalCapDischarge_DC][MIN_CAP_GEN_ASYM_DC_DIS].data) .* + etainverter.(gen[MIN_CAP_GEN_ASYM_DC_DIS])) * + (dual.(EP[:cZoneMinCapReq][mincap]))) end if !isempty(MIN_CAP_GEN_ASYM_AC_DIS) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_ASYM_AC_DIS] .+= ((value.(EP[:eTotalCapDischarge_AC][MIN_CAP_GEN_ASYM_AC_DIS]).data) - * - (dual.(EP[:cZoneMinCapReq][mincap]))) + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_ASYM_AC_DIS] .+= ((value.(EP[:eTotalCapDischarge_AC][MIN_CAP_GEN_ASYM_AC_DIS]).data) * + (dual.(EP[:cZoneMinCapReq][mincap]))) end if !isempty(MIN_CAP_GEN_SYM_DC) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SYM_DC] .+= ((value.(EP[:eTotalCap_STOR][MIN_CAP_GEN_SYM_DC]).data - .* - power_to_energy_dc.(gen_VRE_STOR[(min_cap_stor.(gen_VRE_STOR, tag = mincap) .== 1 .& (gen_VRE_STOR.stor_dc_discharge .== 1))]) - .* - etainverter.(gen_VRE_STOR[(min_cap_stor.(gen_VRE_STOR, tag = mincap) .== 1 .& (gen_VRE_STOR.stor_dc_discharge .== 1))])) - * + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SYM_DC] .+= ((value.(EP[:eTotalCap_STOR][MIN_CAP_GEN_SYM_DC]).data .* + power_to_energy_dc.(gen[MIN_CAP_GEN_SYM_DC]) .* + etainverter.(gen[MIN_CAP_GEN_SYM_DC])) * (dual.(EP[:cZoneMinCapReq][mincap]))) end if !isempty(MIN_CAP_GEN_SYM_AC) - dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SYM_AC] .+= ((value.(EP[:eTotalCap_STOR][MIN_CAP_GEN_SYM_AC]).data - .* - power_to_energy_ac.(gen_VRE_STOR[(min_cap_stor.(gen_VRE_STOR, tag = mincap) .== 1 .& (gen_VRE_STOR.stor_ac_discharge .== 1))])) - * + dfRegSubRevenue.SubsidyRevenue[MIN_CAP_GEN_SYM_AC] .+= ((value.(EP[:eTotalCap_STOR][MIN_CAP_GEN_SYM_AC]).data .* + power_to_energy_ac.(gen[MIN_CAP_GEN_SYM_AC])) * (dual.(EP[:cZoneMinCapReq][mincap]))) end end diff --git a/src/write_outputs/write_vre_stor.jl b/src/write_outputs/write_vre_stor.jl index 26e1c72686..a50687c957 100644 --- a/src/write_outputs/write_vre_stor.jl +++ b/src/write_outputs/write_vre_stor.jl @@ -354,7 +354,7 @@ function write_vre_stor_charge(path::AbstractString, inputs::Dict, setup::Dict, AnnualSum = Array{Union{Missing, Float32}}(undef, size(DC_CHARGE)[1])) charge_dc = zeros(size(DC_CHARGE)[1], T) charge_dc = value.(EP[:vP_DC_CHARGE]).data ./ - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)]) * + etainverter.(gen[DC_CHARGE]) * (setup["ParameterScale"] == 1 ? ModelScalingFactor : 1) dfCharge_DC.AnnualSum .= charge_dc * inputs["omega"] @@ -410,7 +410,7 @@ function write_vre_stor_discharge(path::AbstractString, Zone = inputs["ZONES_DC_DISCHARGE"], AnnualSum = Array{Union{Missing, Float32}}(undef, size(DC_DISCHARGE)[1])) power_vre_stor = value.(EP[:vP_DC_DISCHARGE]).data .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.stor_dc_discharge .!= 0)]) + etainverter.(gen[DC_DISCHARGE]) if setup["ParameterScale"] == 1 power_vre_stor *= ModelScalingFactor end @@ -486,7 +486,7 @@ function write_vre_stor_discharge(path::AbstractString, Zone = inputs["ZONES_SOLAR"], AnnualSum = Array{Union{Missing, Float32}}(undef, size(SOLAR)[1])) vre_vre_stor = value.(EP[:vP_SOLAR]).data .* - etainverter.(gen_VRE_STOR[(gen_VRE_STOR.solar .!= 0)]) + etainverter.(gen[SOLAR]) if setup["ParameterScale"] == 1 vre_vre_stor *= ModelScalingFactor end diff --git a/test/DCOPF/system/Network.csv b/test/DCOPF/system/Network.csv index 3da5f52659..5bd44c8a9e 100644 --- a/test/DCOPF/system/Network.csv +++ b/test/DCOPF/system/Network.csv @@ -1,10 +1,10 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1,Angle_Limit_Rad,Line_Voltage_kV,Line_Resistance_Ohms,Line_Reactance_Ohms -BUS1,z1,1,1,4,250,BUS1_to_BUS4,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,68.5584 -BUS2,z2,2,4,5,250,BUS4_to_BUS5,0.5,0.015,500,12000,0.95,0,0,0.785398,345,20.23425,109.503 -BUS3,z3,3,5,6,150,BUS5_to_BUS6,0.5,0.015,500,12000,0.95,0,0,0.785398,345,46.41975,202.3425 -BUS4,z4,4,3,6,300,BUS3_to_BUS6,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,69.74865 -BUS5,z5,5,6,7,150,BUS6_to_BUS7,0.5,0.015,500,12000,0.95,0,0,0.785398,345,14.163975,119.9772 -BUS6,z6,6,7,8,250,BUS7_to_BUS8,0.5,0.015,500,12000,0.95,0,0,0.785398,345,10.117125,85.698 -BUS7,z7,7,8,2,250,BUS8_to_BUS2,0.5,0.015,500,12000,0.95,0,0,0.785398,345,0,74.390625 -BUS8,z8,8,8,9,250,BUS8_to_BUS9,0.5,0.015,500,12000,0.95,0,0,0.785398,345,38.088,191.63025 -BUS9,z9,9,9,4,250,BUS9_to_BUS4,0.5,0.015,500,12000,0.95,0,0,0.785398,345,11.9025,101.17125 +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1,Angle_Limit_Rad,Line_Voltage_kV,Line_Resistance_Ohms,Line_Reactance_Ohms +BUS1,z1,1,1,4,250,BUS1_to_BUS4,0.5,0.015,500,12000,0.95,0,0.785398,345,0,68.5584 +BUS2,z2,2,4,5,250,BUS4_to_BUS5,0.5,0.015,500,12000,0.95,0,0.785398,345,20.23425,109.503 +BUS3,z3,3,5,6,150,BUS5_to_BUS6,0.5,0.015,500,12000,0.95,0,0.785398,345,46.41975,202.3425 +BUS4,z4,4,3,6,300,BUS3_to_BUS6,0.5,0.015,500,12000,0.95,0,0.785398,345,0,69.74865 +BUS5,z5,5,6,7,150,BUS6_to_BUS7,0.5,0.015,500,12000,0.95,0,0.785398,345,14.163975,119.9772 +BUS6,z6,6,7,8,250,BUS7_to_BUS8,0.5,0.015,500,12000,0.95,0,0.785398,345,10.117125,85.698 +BUS7,z7,7,8,2,250,BUS8_to_BUS2,0.5,0.015,500,12000,0.95,0,0.785398,345,0,74.390625 +BUS8,z8,8,8,9,250,BUS8_to_BUS9,0.5,0.015,500,12000,0.95,0,0.785398,345,38.088,191.63025 +BUS9,z9,9,9,4,250,BUS9_to_BUS4,0.5,0.015,500,12000,0.95,0,0.785398,345,11.9025,101.17125 \ No newline at end of file diff --git a/test/expression_manipulation_test.jl b/test/expression_manipulation_test.jl index 71891d80ac..6c403284d0 100644 --- a/test/expression_manipulation_test.jl +++ b/test/expression_manipulation_test.jl @@ -92,6 +92,12 @@ let GenX.add_similar_to_expression!(EP[:large_expr], EP[:large_const_expr]) @test all(EP[:large_expr][:] .== 18.0) + # Test add_similar_to_expression! with AbstractArray{Number} + @expression(EP, eArr1[i = 1:100, j = 1:50], i * 10.0+j * 10.0) + @expression(EP, eArr2[i = 1:100, j = 1:50], -(i * 10.0 + j * 10.0)) + GenX.add_similar_to_expression!(EP[:eArr1], EP[:eArr2]) + @test all(EP[:eArr1][:] .== 0.0) + # Test add_similar_to_expression! returns an error if the dimensions don't match GenX.create_empty_expression!(EP, :small_expr, (2, 3)) @test_throws ErrorException GenX.add_similar_to_expression!(EP[:large_expr], diff --git a/test/load_resources/test_gen_non_colocated/system/Network.csv b/test/load_resources/test_gen_non_colocated/system/Network.csv index a26c95a6be..b26a04379e 100644 --- a/test/load_resources/test_gen_non_colocated/system/Network.csv +++ b/test/load_resources/test_gen_non_colocated/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,z1,z2,z3,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -NENGREST,z1,1,1,-1,0,2950,NENGREST_to_NENG_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -NENG_CT,z2,2,1,0,-1,2000,NENGREST_to_NENG_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -NENG_ME,z3,,,,,,,,,,,,, \ No newline at end of file +,Network_zones,Network_Lines,z1,z2,z3,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +NENGREST,z1,1,1,-1,0,2950,NENGREST_to_NENG_CT,123.0584,0.012305837,2950,12060,0.95,0 +NENG_CT,z2,2,1,0,-1,2000,NENGREST_to_NENG_ME,196.5385,0.019653847,2000,19261,0.95,0 +NENG_ME,z3,,,,,,,,,,,, \ No newline at end of file diff --git a/test/three_zones/system/Network.csv b/test/three_zones/system/Network.csv index c6413479ff..c2acbc65a1 100644 --- a/test/three_zones/system/Network.csv +++ b/test/three_zones/system/Network.csv @@ -1,4 +1,4 @@ -,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_1,CapRes_Excl_1 -MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0,0 -CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0,0 -ME,z3,,,,,,,,,,,, +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,2,1,3,2000,MA_to_ME,196.5385,0.019653847,2000,19261,0.95,0 +ME,z3,,,,,,,,,,, \ No newline at end of file diff --git a/test/writing_outputs/test_zone_no_resources.jl b/test/writing_outputs/test_zone_no_resources.jl new file mode 100644 index 0000000000..8b19423fc4 --- /dev/null +++ b/test/writing_outputs/test_zone_no_resources.jl @@ -0,0 +1,77 @@ +module TestZoneNoResources + +using Test +using DataFrames + +include(joinpath(@__DIR__, "../utilities.jl")) + +function prepare_costs_test(test_path, inputs, genx_setup, EP) + settings = GenX.default_settings() + merge!(settings, genx_setup) + GenX.write_costs(test_path, inputs, settings, EP) + costs_path = joinpath(test_path, "costs.csv") + costs_test = CSV.read(costs_path, DataFrame) + costs_test[!, :Zone1] = tryparse.(Float64, replace(costs_test[!, :Zone1], "-" => "0.0")) + costs_test[!, :Zone2] = tryparse.(Float64, replace(costs_test[!, :Zone2], "-" => "0.0")) + costs_test[!, :Zone2] = replace(costs_test[!, :Zone2], nothing => 0.0) + return costs_test +end + +function prepare_costs_true() + df = DataFrame( + ["cTotal" 5.177363815260002e12 4.027191550200002e12 1.1501722650599993e12; + "cFix" 0.0 0.0 0.0; + "cVar" 5.849292224195126e-8 0.0 5.849292224195126e-8; + "cFuel" 0.0 0.0 0.0; + "cNSE" 5.177363815260002e12 4.027191550200002e12 1.1501722650599993e12; + "cStart" 0.0 0.0 0.0; + "cUnmetRsv" 0.0 0.0 0.0; + "cNetworkExp" 0.0 0.0 0.0; + "cUnmetPolicyPenalty" 0.0 0.0 0.0; + "cCO2" 0.0 0.0 0.0], + [:Costs, :Total, :Zone1, :Zone2]) + + df[!, :Costs] = convert(Vector{String}, df[!, :Costs]) + df[!, :Total] = convert(Vector{Float64}, df[!, :Total]) + df[!, :Zone1] = convert(Vector{Float64}, df[!, :Zone1]) + df[!, :Zone2] = convert(Vector{Float64}, df[!, :Zone2]) + return df +end + +function test_case() + test_path = joinpath(@__DIR__, "zone_no_resources") + obj_true = 5.1773638153e12 + costs_true = prepare_costs_true() + + # Define test setup + genx_setup = Dict("NetworkExpansion" => 1, + "Trans_Loss_Segments" => 1, + "UCommit" => 2, + "CO2Cap" => 2, + "StorageLosses" => 1, + "WriteShadowPrices" => 1) + + # Run the case and get the objective value and tolerance + EP, inputs, _ = redirect_stdout(devnull) do + run_genx_case_testing(test_path, genx_setup) + end + obj_test = objective_value(EP) + optimal_tol_rel = get_attribute(EP, "dual_feasibility_tolerance") + optimal_tol = optimal_tol_rel * obj_test # Convert to absolute tolerance + + # Test the objective value + test_result = @test obj_test≈obj_true atol=optimal_tol + + # Test the costs + costs_test = prepare_costs_test(test_path, inputs, genx_setup, EP) + test_result = @test costs_test[!, Not(:Costs)] ≈ costs_true[!, Not(:Costs)] + + # Remove the costs file + rm(joinpath(test_path, "costs.csv")) + + return nothing +end + +test_case() + +end # module TestZoneNoResources diff --git a/test/writing_outputs/zone_no_resources/highs_settings.yml b/test/writing_outputs/zone_no_resources/highs_settings.yml new file mode 100644 index 0000000000..e4f1ad0245 --- /dev/null +++ b/test/writing_outputs/zone_no_resources/highs_settings.yml @@ -0,0 +1,11 @@ +# HiGHS Solver Parameters +# Common solver settings +Feasib_Tol: 1.0e-05 # Primal feasibility tolerance # [type: double, advanced: false, range: [1e-10, inf], default: 1e-07] +Optimal_Tol: 1.0e-05 # Dual feasibility tolerance # [type: double, advanced: false, range: [1e-10, inf], default: 1e-07] +TimeLimit: 1.0e23 # Time limit # [type: double, advanced: false, range: [0, inf], default: inf] +Pre_Solve: choose # Presolve option: "off", "choose" or "on" # [type: string, advanced: false, default: "choose"] +Method: ipm #HiGHS-specific solver settings # Solver option: "simplex", "choose" or "ipm" # [type: string, advanced: false, default: "choose"] + +# run the crossover routine for ipx +# [type: string, advanced: "on", range: {"off", "on"}, default: "off"] +run_crossover: "on" diff --git a/test/writing_outputs/zone_no_resources/policies/CO2_cap.csv b/test/writing_outputs/zone_no_resources/policies/CO2_cap.csv new file mode 100644 index 0000000000..dee326e6cb --- /dev/null +++ b/test/writing_outputs/zone_no_resources/policies/CO2_cap.csv @@ -0,0 +1,3 @@ +,Network_zones,CO_2_Cap_Zone_1,CO_2_Cap_Zone_2,CO_2_Max_tons_MWh_1,CO_2_Max_tons_MWh_2,CO_2_Max_Mtons_1,CO_2_Max_Mtons_2 +MA,z1,1,0,0.05,0,0.018,0 +CT,z2,0,1,0,0.05,0,0.025 \ No newline at end of file diff --git a/test/writing_outputs/zone_no_resources/resources/Storage.csv b/test/writing_outputs/zone_no_resources/resources/Storage.csv new file mode 100644 index 0000000000..d5a892cca1 --- /dev/null +++ b/test/writing_outputs/zone_no_resources/resources/Storage.csv @@ -0,0 +1,2 @@ +Resource,Zone,Model,New_Build,Can_Retire,Existing_Cap_MW,Existing_Cap_MWh,Max_Cap_MW,Max_Cap_MWh,Min_Cap_MW,Min_Cap_MWh,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster +CT_battery,2,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,CT,0 \ No newline at end of file diff --git a/test/writing_outputs/zone_no_resources/resources/Thermal.csv b/test/writing_outputs/zone_no_resources/resources/Thermal.csv new file mode 100644 index 0000000000..2a8f68172e --- /dev/null +++ b/test/writing_outputs/zone_no_resources/resources/Thermal.csv @@ -0,0 +1,2 @@ +Resource,Zone,Model,New_Build,Can_Retire,Existing_Cap_MW,Max_Cap_MW,Min_Cap_MW,Inv_Cost_per_MWyr,Fixed_OM_Cost_per_MWyr,Var_OM_Cost_per_MWh,Heat_Rate_MMBTU_per_MWh,Fuel,Cap_Size,Start_Cost_per_MW,Start_Fuel_MMBTU_per_MW,Up_Time,Down_Time,Ramp_Up_Percentage,Ramp_Dn_Percentage,Min_Power,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster +CT_natural_gas_combined_cycle,2,1,1,0,0,-1,0,65400,10287,3.55,7.43,CT_NG,250,91,2,6,6,0.64,0.64,0.468,0.25,0.5,0,0,CT,1 \ No newline at end of file diff --git a/test/writing_outputs/zone_no_resources/resources/Vre.csv b/test/writing_outputs/zone_no_resources/resources/Vre.csv new file mode 100644 index 0000000000..3f32d9b84b --- /dev/null +++ b/test/writing_outputs/zone_no_resources/resources/Vre.csv @@ -0,0 +1,3 @@ +Resource,Zone,Num_VRE_Bins,New_Build,Can_Retire,Existing_Cap_MW,Max_Cap_MW,Min_Cap_MW,Inv_Cost_per_MWyr,Fixed_OM_Cost_per_MWyr,Var_OM_Cost_per_MWh,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster +CT_onshore_wind,2,1,0,0,0,-1,0,97200,43205,0.1,0,0,0,0,CT,1 +CT_solar_pv,2,1,0,0,0,-1,0,85300,18760,0,0,0,0,0,CT,1 \ No newline at end of file diff --git a/test/writing_outputs/zone_no_resources/system/Demand_data.csv b/test/writing_outputs/zone_no_resources/system/Demand_data.csv new file mode 100644 index 0000000000..4234d3f830 --- /dev/null +++ b/test/writing_outputs/zone_no_resources/system/Demand_data.csv @@ -0,0 +1,121 @@ +Voll,Demand_Segment,Cost_of_Demand_Curtailment_per_MW,Max_Demand_Curtailment,$/MWh,Rep_Periods,Timesteps_per_Rep_Period,Sub_Weights,Time_Index,Demand_MW_z1,Demand_MW_z2 +50000,1,1.0,1.0,2000,5,24,24.0,1,7822.0,2234.0 +,2,0.9,0.04,1800,,,2592.0,2,7494.0,2140.0 +,3,0.55,0.024,1100,,,6096.0,3,7343.0,2097.0 +,4,0.2,0.003,400,,,24.0,4,7289.0,2082.0 +,,,,,,,24.0,5,7482.0,2137.0 +,,,,,,,,6,8142.0,2325.0 +,,,,,,,,7,9388.0,2681.0 +,,,,,,,,8,10233.0,2922.0 +,,,,,,,,9,10494.0,2997.0 +,,,,,,,,10,10665.0,3046.0 +,,,,,,,,11,10780.0,3079.0 +,,,,,,,,12,10817.0,3089.0 +,,,,,,,,13,10743.0,3068.0 +,,,,,,,,14,10657.0,3044.0 +,,,,,,,,15,10516.0,3003.0 +,,,,,,,,16,10468.0,2990.0 +,,,,,,,,17,10788.0,3081.0 +,,,,,,,,18,11254.0,3214.0 +,,,,,,,,19,11088.0,3167.0 +,,,,,,,,20,10715.0,3060.0 +,,,,,,,,21,10312.0,2945.0 +,,,,,,,,22,9767.0,2790.0 +,,,,,,,,23,9041.0,2582.0 +,,,,,,,,24,8305.0,2372.0 +,,,,,,,,25,7611.0,2174.0 +,,,,,,,,26,7284.0,2080.0 +,,,,,,,,27,7110.0,2031.0 +,,,,,,,,28,7082.0,2023.0 +,,,,,,,,29,7266.0,2075.0 +,,,,,,,,30,7941.0,2268.0 +,,,,,,,,31,9190.0,2625.0 +,,,,,,,,32,9935.0,2837.0 +,,,,,,,,33,10145.0,2897.0 +,,,,,,,,34,10199.0,2913.0 +,,,,,,,,35,10213.0,2917.0 +,,,,,,,,36,10135.0,2895.0 +,,,,,,,,37,9964.0,2845.0 +,,,,,,,,38,9842.0,2811.0 +,,,,,,,,39,9677.0,2764.0 +,,,,,,,,40,9588.0,2739.0 +,,,,,,,,41,9757.0,2786.0 +,,,,,,,,42,10423.0,2976.0 +,,,,,,,,43,10732.0,3065.0 +,,,,,,,,44,10465.0,2989.0 +,,,,,,,,45,10112.0,2888.0 +,,,,,,,,46,9608.0,2744.0 +,,,,,,,,47,8902.0,2543.0 +,,,,,,,,48,8169.0,2333.0 +,,,,,,,,49,7338.0,2096.0 +,,,,,,,,50,6938.0,1982.0 +,,,,,,,,51,6751.0,1928.0 +,,,,,,,,52,6676.0,1907.0 +,,,,,,,,53,6840.0,1953.0 +,,,,,,,,54,7300.0,2085.0 +,,,,,,,,55,8454.0,2414.0 +,,,,,,,,56,9469.0,2704.0 +,,,,,,,,57,10006.0,2858.0 +,,,,,,,,58,10341.0,2954.0 +,,,,,,,,59,10626.0,3035.0 +,,,,,,,,60,10780.0,3079.0 +,,,,,,,,61,10849.0,3099.0 +,,,,,,,,62,10977.0,3135.0 +,,,,,,,,63,10950.0,3127.0 +,,,,,,,,64,10892.0,3111.0 +,,,,,,,,65,10868.0,3104.0 +,,,,,,,,66,10767.0,3075.0 +,,,,,,,,67,10550.0,3013.0 +,,,,,,,,68,10414.0,2974.0 +,,,,,,,,69,10478.0,2992.0 +,,,,,,,,70,10018.0,2861.0 +,,,,,,,,71,9029.0,2579.0 +,,,,,,,,72,8087.0,2309.0 +,,,,,,,,73,10503.0,3000.0 +,,,,,,,,74,9889.0,2825.0 +,,,,,,,,75,9493.0,2711.0 +,,,,,,,,76,9245.0,2640.0 +,,,,,,,,77,9268.0,2647.0 +,,,,,,,,78,9643.0,2754.0 +,,,,,,,,79,10684.0,3051.0 +,,,,,,,,80,12036.0,3437.0 +,,,,,,,,81,13120.0,3747.0 +,,,,,,,,82,14080.0,4021.0 +,,,,,,,,83,14910.0,4258.0 +,,,,,,,,84,15478.0,4421.0 +,,,,,,,,85,15870.0,4533.0 +,,,,,,,,86,16225.0,4633.0 +,,,,,,,,87,16448.0,4698.0 +,,,,,,,,88,16617.0,4746.0 +,,,,,,,,89,16717.0,4774.0 +,,,,,,,,90,16579.0,4735.0 +,,,,,,,,91,16199.0,4626.0 +,,,,,,,,92,15701.0,4484.0 +,,,,,,,,93,15416.0,4403.0 +,,,,,,,,94,14854.0,4243.0 +,,,,,,,,95,13581.0,3878.0 +,,,,,,,,96,12317.0,3518.0 +,,,,,,,,97,7899.0,2256.0 +,,,,,,,,98,7613.0,2174.0 +,,,,,,,,99,7477.0,2135.0 +,,,,,,,,100,7470.0,2134.0 +,,,,,,,,101,7699.0,2199.0 +,,,,,,,,102,8428.0,2407.0 +,,,,,,,,103,9761.0,2787.0 +,,,,,,,,104,10471.0,2991.0 +,,,,,,,,105,10643.0,3040.0 +,,,,,,,,106,10719.0,3061.0 +,,,,,,,,107,10802.0,3085.0 +,,,,,,,,108,10835.0,3095.0 +,,,,,,,,109,10820.0,3090.0 +,,,,,,,,110,10811.0,3087.0 +,,,,,,,,111,10750.0,3070.0 +,,,,,,,,112,10888.0,3110.0 +,,,,,,,,113,11635.0,3323.0 +,,,,,,,,114,12129.0,3464.0 +,,,,,,,,115,12036.0,3437.0 +,,,,,,,,116,11714.0,3346.0 +,,,,,,,,117,11207.0,3201.0 +,,,,,,,,118,10396.0,2969.0 +,,,,,,,,119,9383.0,2680.0 +,,,,,,,,120,8476.0,2420.0 diff --git a/test/writing_outputs/zone_no_resources/system/Fuels_data.csv b/test/writing_outputs/zone_no_resources/system/Fuels_data.csv new file mode 100644 index 0000000000..ae37d8b77b --- /dev/null +++ b/test/writing_outputs/zone_no_resources/system/Fuels_data.csv @@ -0,0 +1,122 @@ +Time_Index,CT_NG,None +0,0.05306,0.0 +1,5.45,0.0 +2,5.45,0.0 +3,5.45,0.0 +4,5.45,0.0 +5,5.45,0.0 +6,5.45,0.0 +7,5.45,0.0 +8,5.45,0.0 +9,5.45,0.0 +10,5.45,0.0 +11,5.45,0.0 +12,5.45,0.0 +13,5.45,0.0 +14,5.45,0.0 +15,5.45,0.0 +16,5.45,0.0 +17,5.45,0.0 +18,5.45,0.0 +19,5.45,0.0 +20,5.45,0.0 +21,5.45,0.0 +22,5.45,0.0 +23,5.45,0.0 +24,5.45,0.0 +25,4.09,0.0 +26,4.09,0.0 +27,4.09,0.0 +28,4.09,0.0 +29,4.09,0.0 +30,4.09,0.0 +31,4.09,0.0 +32,4.09,0.0 +33,4.09,0.0 +34,4.09,0.0 +35,4.09,0.0 +36,4.09,0.0 +37,4.09,0.0 +38,4.09,0.0 +39,4.09,0.0 +40,4.09,0.0 +41,4.09,0.0 +42,4.09,0.0 +43,4.09,0.0 +44,4.09,0.0 +45,4.09,0.0 +46,4.09,0.0 +47,4.09,0.0 +48,4.09,0.0 +49,1.82,0.0 +50,1.82,0.0 +51,1.82,0.0 +52,1.82,0.0 +53,1.82,0.0 +54,1.82,0.0 +55,1.82,0.0 +56,1.82,0.0 +57,1.82,0.0 +58,1.82,0.0 +59,1.82,0.0 +60,1.82,0.0 +61,1.82,0.0 +62,1.82,0.0 +63,1.82,0.0 +64,1.82,0.0 +65,1.82,0.0 +66,1.82,0.0 +67,1.82,0.0 +68,1.82,0.0 +69,1.82,0.0 +70,1.82,0.0 +71,1.82,0.0 +72,1.82,0.0 +73,1.89,0.0 +74,1.89,0.0 +75,1.89,0.0 +76,1.89,0.0 +77,1.89,0.0 +78,1.89,0.0 +79,1.89,0.0 +80,1.89,0.0 +81,1.89,0.0 +82,1.89,0.0 +83,1.89,0.0 +84,1.89,0.0 +85,1.89,0.0 +86,1.89,0.0 +87,1.89,0.0 +88,1.89,0.0 +89,1.89,0.0 +90,1.89,0.0 +91,1.89,0.0 +92,1.89,0.0 +93,1.89,0.0 +94,1.89,0.0 +95,1.89,0.0 +96,1.89,0.0 +97,2.78,0.0 +98,2.78,0.0 +99,2.78,0.0 +100,2.78,0.0 +101,2.78,0.0 +102,2.78,0.0 +103,2.78,0.0 +104,2.78,0.0 +105,2.78,0.0 +106,2.78,0.0 +107,2.78,0.0 +108,2.78,0.0 +109,2.78,0.0 +110,2.78,0.0 +111,2.78,0.0 +112,2.78,0.0 +113,2.78,0.0 +114,2.78,0.0 +115,2.78,0.0 +116,2.78,0.0 +117,2.78,0.0 +118,2.78,0.0 +119,2.78,0.0 +120,2.78,0.0 diff --git a/test/writing_outputs/zone_no_resources/system/Generators_variability.csv b/test/writing_outputs/zone_no_resources/system/Generators_variability.csv new file mode 100644 index 0000000000..80a537f30c --- /dev/null +++ b/test/writing_outputs/zone_no_resources/system/Generators_variability.csv @@ -0,0 +1,121 @@ +Time_Index,CT_onshore_wind,CT_solar_pv +1,0.705949306,0 +2,0.834924579,0 +3,0.832703173,0 +4,0.727586865,0 +5,0.626110256,0 +6,0.721315265,0 +7,0.785158873,0 +8,0.59819752,0 +9,0.567111433,0 +10,0.326491237,0.0064 +11,0.390583217,0.116 +12,0.287067473,0.0999 +13,0.229321212,0.1202 +14,0.154025629,0.192 +15,0.115687042,0.1404 +16,0.054644316,0.0697 +17,0.088804618,0 +18,0.72049433,0 +19,0.834395289,0 +20,0.950648248,0 +21,0.999782085,0 +22,1,0 +23,1,0 +24,1,0 +25,0.465583175,0 +26,0.707297444,0 +27,0.895804107,0 +28,0.819945991,0 +29,0.610500693,0 +30,0.34757489,0 +31,0.285657108,0 +32,0.317218393,0 +33,0.254971772,0.1509 +34,0.306124657,0.3546 +35,0.72285372,0.5514 +36,0.749075055,0.5828 +37,0.766450584,0.5721 +38,0.583024323,0.5944 +39,0.89966023,0.5804 +40,0.768344879,0.5083 +41,0.941289306,0.3311 +42,0.691129565,0.0839 +43,0.369385242,0 +44,0.543988705,0 +45,0.627581239,0 +46,0.891589403,0 +47,0.663651288,0 +48,0.636843503,0 +49,0.431610733,0 +50,0.574139476,0 +51,0.5398283,0 +52,0.201132476,0 +53,0.107555799,0 +54,0.144015923,0.0126 +55,0.071583487,0.1019 +56,0.205921009,0.2045 +57,0.161220312,0.3112 +58,0.336054265,0.3663 +59,0.368090123,0.4167 +60,0.454866886,0.4684 +61,0.460774302,0.4928 +62,0.431218863,0.4656 +63,0.424021393,0.3782 +64,0.402401239,0.3149 +65,0.201657325,0.2465 +66,0.31398356,0.1617 +67,0.642302394,0.0083 +68,0.458561152,0 +69,0.278454691,0 +70,0.406244844,0 +71,0.48908928,0 +72,0.247558758,0 +73,0.46454832,0 +74,0.619871557,0 +75,0.782924116,0 +76,0.544351637,0 +77,0.388339579,0 +78,0.188761607,0.0003 +79,0.056012779,0.0996 +80,0.011642265,0.2184 +81,0.005336904,0.3801 +82,0.036412083,0.497 +83,0.113800742,0.566 +84,0.309363514,0.5632 +85,0.537328064,0.5305 +86,0.75558275,0.5783 +87,0.804839015,0.5735 +88,0.814335048,0.4853 +89,0.82010901,0.4051 +90,0.700861871,0.2135 +91,0.377394527,0.0909 +92,0.301600695,0 +93,0.539320409,0 +94,0.604777336,0 +95,0.605847716,0 +96,0.867583036,0 +97,1.83E-05,0 +98,0,0 +99,0,0 +100,0,0 +101,0,0 +102,0,0 +103,0.000799759,0 +104,0.00032822,0 +105,0,0.0745 +106,0,0.2522 +107,5.05E-06,0.3334 +108,0,0.3485 +109,0,0.3388 +110,0,0.3379 +111,6.15E-05,0.3133 +112,0.000322065,0.1924 +113,0.000258478,0 +114,0.000254347,0 +115,0.00095264,0 +116,0.001598039,0 +117,0.002736128,0 +118,0.004819703,0 +119,0.002815315,0 +120,0.001111662,0 \ No newline at end of file diff --git a/test/writing_outputs/zone_no_resources/system/Network.csv b/test/writing_outputs/zone_no_resources/system/Network.csv new file mode 100644 index 0000000000..8924aa60bd --- /dev/null +++ b/test/writing_outputs/zone_no_resources/system/Network.csv @@ -0,0 +1,3 @@ +,Network_zones,Network_Lines,Start_Zone,End_Zone,Line_Max_Flow_MW,transmission_path_name,distance_mile,Line_Loss_Percentage,Line_Max_Reinforcement_MW,Line_Reinforcement_Cost_per_MWyr,DerateCapRes_1,CapRes_Excl_1 +MA,z1,1,1,2,2950,MA_to_CT,123.0584,0.012305837,2950,12060,0.95,0 +CT,z2,,,,,,,,,,, \ No newline at end of file