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
Row
Resource
MA_natural_gas_combined_cycle
CT_natural_gas_combined_cycle
ME_natural_gas_combined_cycle
MA_solar_pv
CT_onshore_wind
CT_solar_pv
ME_onshore_wind
MA_battery
CT_battery
ME_battery
Total
String15
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
1
Zone
1.0
2.0
3.0
1.0
2.0
2.0
3.0
1.0
2.0
3.0
0.0
2
AnnualSum
1.04015e7
3.42459e6
8.94975e5
2.47213e7
2.90683e7
2.69884e7
2.625e7
5.06354e6
1.45833e7
4.90368e6
1.463e8
3
t1
-0.0
-0.0
-0.0
-0.0
8510.78
-0.0
5300.61
0.0
2537.45
673.34
17022.2
4
t2
-0.0
-0.0
-0.0
-0.0
8420.78
-0.0
6282.04
0.0
2537.45
0.0
17240.3
5
t3
-0.0
-0.0
-0.0
-0.0
8367.78
-0.0
2409.84
0.0
2537.45
1828.24
15143.3
6
t4
-0.0
-0.0
-0.0
-0.0
8353.78
-0.0
2762.24
1591.46
2537.45
0.0
15244.9
7
t5
-0.0
-0.0
-0.0
-0.0
7482.39
-0.0
0.0
1617.46
2980.64
1384.62
13465.1
8
t6
-0.0
-0.0
-0.0
-0.0
2429.93
-0.0
2797.24
1717.96
5535.37
0.0
12480.5
9
t7
-0.0
-0.0
-0.0
-0.0
11868.8
-0.0
1374.73
1320.78
871.443
1340.67
16776.4
10
t8
-0.0
-0.0
-0.0
-0.0
2656.93
-0.0
0.0
2115.96
5535.37
1452.62
11760.9
11
t9
-0.0
-0.0
-0.0
3061.28
0.0
3110.8
2982.24
868.817
5389.44
0.0
15412.6
12
t10
-0.0
-0.0
-0.0
6100.22
7597.99
5543.69
0.0
0.0
0.0
1521.12
20763.0
13
t11
-0.0
-0.0
-0.0
8314.29
0.0
6341.98
3080.24
0.0
2458.82
0.0
20195.3
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋮
1839
t1837
-0.0
-0.0
-0.0
6712.18
2541.6
6736.37
305.608
1410.33
763.726
1427.82
19897.6
1840
t1838
-0.0
-0.0
-0.0
6514.15
0.0
6847.24
3153.24
0.0
3464.22
0.0
19978.9
1841
t1839
-0.0
-0.0
-0.0
5582.07
3848.88
6280.2
0.0
195.422
2048.3
1571.12
19526.0
1842
t1840
-0.0
-0.0
-0.0
3688.13
9349.98
4892.7
3490.61
1006.02
0.0
0.0
22427.4
1843
t1841
-0.0
-0.0
-0.0
509.22
8124.99
1351.08
3653.06
1218.5
2507.8
1828.24
19192.9
1844
t1842
-0.0
-0.0
-0.0
-0.0
2918.2
-0.0
6896.82
2194.61
5535.37
256.863
17801.9
1845
t1843
-0.0
-0.0
-0.0
-0.0
6800.37
-0.0
7324.66
1838.11
3950.15
41.9472
19955.2
1846
t1844
-0.0
-0.0
-0.0
-0.0
9505.82
-0.0
5683.66
1744.78
2567.93
838.077
20340.3
1847
t1845
-0.0
-0.0
-0.0
-0.0
3491.93
-0.0
5128.56
1597.61
5535.37
1107.49
16861.0
1848
t1846
-0.0
-0.0
-0.0
-0.0
12135.6
-0.0
5021.75
1341.11
1140.56
1125.9
20764.9
1849
t1847
-0.0
-0.0
-0.0
-0.0
8875.71
-0.0
3605.98
974.61
2665.48
1783.79
17905.6
1850
t1848
-0.0
-0.0
-0.0
-0.0
13549.1
-0.0
4098.0
541.61
205.31
1478.27
19872.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
Row
region
Resource
zone
Cluster
R_ID
Inv_cost_MW
Inv_cost_MWh
Inv_cost_charge_MW
Fixed_OM_cost_MW
Fixed_OM_cost_MWh
Fixed_OM_cost_charge_MW
Var_OM_cost_out
Fuel_cost
Var_OM_cost_in
StartCost
Charge_cost
CO2SequestrationCost
EnergyRevenue
SubsidyRevenue
OperatingReserveRevenue
OperatingRegulationRevenue
ReserveMarginRevenue
ESRRevenue
EmissionsCost
RegSubsidyRevenue
Revenue
Cost
Profit
String3
String31
Int64
Int64
Int64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
Float64
1
MA
MA_natural_gas_combined_cycle
1
1
1
5.54734e8
0.0
0.0
8.72561e7
0.0
0.0
3.69253e7
2.10416e8
0.0
3.84832e7
0.0
0.0
2.77103e9
0.0
0.0
0.0
0.0
0.0
1.84321e9
0.0
2.77103e9
2.77103e9
1.43051e-6
2
CT
CT_natural_gas_combined_cycle
2
1
2
1.42906e8
0.0
0.0
2.11911e7
0.0
0.0
1.22258e7
4.97792e7
0.0
7.75292e6
0.0
0.0
8.4423e8
0.0
0.0
0.0
0.0
0.0
6.10375e8
0.0
8.4423e8
8.4423e8
1.19209e-7
3
ME
ME_natural_gas_combined_cycle
3
1
3
3.52336e7
0.0
0.0
8.77661e6
0.0
0.0
4.02739e6
2.26505e7
0.0
3.33663e6
0.0
0.0
2.19267e8
0.0
0.0
0.0
0.0
0.0
1.45243e8
0.0
2.19267e8
2.19267e8
0.0
4
MA
MA_solar_pv
1
1
4
1.27007e9
0.0
0.0
2.79327e8
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.5494e9
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.5494e9
1.5494e9
-2.86102e-6
5
CT
CT_onshore_wind
2
1
5
1.40748e9
0.0
0.0
6.25617e8
0.0
0.0
2.90683e6
0.0
0.0
0.0
0.0
0.0
2.036e9
0.0
0.0
0.0
0.0
0.0
0.0
0.0
2.036e9
2.036e9
-5.00679e-6
6
CT
CT_solar_pv
2
1
6
1.35108e9
0.0
0.0
2.97142e8
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.64822e9
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.64822e9
1.64822e9
9.53674e-7
7
ME
ME_onshore_wind
3
1
7
1.03673e9
0.0
0.0
4.60821e8
0.0
0.0
2.625e6
0.0
0.0
0.0
0.0
0.0
1.50017e9
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.50017e9
1.50017e9
2.38419e-6
8
MA
MA_battery
1
0
8
4.29792e7
2.23673e8
0.0
1.07426e7
5.59033e7
0.0
7.59532e5
0.0
8.97367e5
0.0
1.3432e8
0.0
4.48833e8
0.0
0.0
0.0
0.0
0.0
0.0
0.0
4.48833e8
4.69275e8
-2.0442e7
9
CT
CT_battery
2
0
9
1.08405e8
5.73615e8
0.0
2.70957e7
1.43365e8
0.0
2.1875e6
0.0
2.58447e6
0.0
5.24177e8
0.0
1.31941e9
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.31941e9
1.38143e9
-6.20165e7
10
ME
ME_battery
3
0
10
3.58043e7
1.03994e8
0.0
8.94925e6
2.59915e7
0.0
7.35552e5
0.0
8.69036e5
0.0
3.81057e7
0.0
2.03732e8
0.0
0.0
0.0
0.0
0.0
0.0
0.0
2.03732e8
2.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
Row
Zone
1
2
3
Total
String15
Float64
Float64
Float64
Float64
1
CO2_Price_1
444.921
0.0
0.0
0.0
2
CO2_Price_2
0.0
468.668
0.0
0.0
3
CO2_Price_3
0.0
0.0
240.86
0.0
4
AnnualSum
4.14279e6
1.30236e6
6.03017e5
6.04816e6
5
t1
0.0
0.0
0.0
0.0
6
t2
0.0
0.0
0.0
0.0
7
t3
0.0
0.0
0.0
0.0
8
t4
0.0
0.0
0.0
0.0
9
t5
0.0
0.0
0.0
0.0
10
t6
0.0
0.0
0.0
0.0
11
t7
0.0
0.0
0.0
0.0
12
t8
0.0
0.0
0.0
0.0
13
t9
0.0
0.0
0.0
0.0
⋮
⋮
⋮
⋮
⋮
⋮
1841
t1837
0.0
0.0
0.0
0.0
1842
t1838
0.0
0.0
0.0
0.0
1843
t1839
0.0
0.0
0.0
0.0
1844
t1840
0.0
0.0
0.0
0.0
1845
t1841
0.0
0.0
0.0
0.0
1846
t1842
0.0
0.0
0.0
0.0
1847
t1843
0.0
0.0
0.0
0.0
1848
t1844
0.0
0.0
0.0
0.0
1849
t1845
0.0
0.0
0.0
0.0
1850
t1846
0.0
0.0
0.0
0.0
1851
t1847
0.0
0.0
0.0
0.0
1852
t1848
0.0
0.0
0.0
0.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
Row
Zone
1
2
3
Total
String15
Float64
Float64
Float64
Float64
1
AnnualSum
1.68155e7
1.41088e7
4310.21
3.09286e7
2
t1
997.169
0.0
0.0
997.169
3
t2
997.169
0.0
0.0
997.169
4
t3
997.169
0.0
0.0
997.169
5
t4
997.169
0.0
0.0
997.169
6
t5
997.169
0.0
0.0
997.169
7
t6
997.169
0.0
0.0
997.169
8
t7
997.169
0.0
0.0
997.169
9
t8
997.169
0.0
0.0
997.169
10
t9
997.169
0.0
0.0
997.169
11
t10
1471.46
0.0
0.0
1471.46
12
t11
997.169
0.0
0.0
997.169
13
t12
1115.81
0.0
0.0
1115.81
⋮
⋮
⋮
⋮
⋮
⋮
1838
t1837
2789.35
1012.99
0.0
3802.34
1839
t1838
2835.21
1012.99
0.0
3848.2
1840
t1839
2520.57
1012.99
0.0
3533.56
1841
t1840
1496.47
445.85
0.0
1942.32
1842
t1841
2571.26
1012.99
0.0
3584.25
1843
t1842
2835.21
1012.99
0.0
3848.2
1844
t1843
2835.21
1012.99
0.0
3848.2
1845
t1844
2625.42
960.184
0.0
3585.6
1846
t1845
2506.32
342.391
0.0
2848.71
1847
t1846
2277.59
342.391
0.0
2619.98
1848
t1847
1960.08
524.526
0.0
2484.6
1849
t1848
1566.77
342.391
0.0
1909.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 @@
-
\ No newline at end of file
+
\ 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