diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abde2ecb3..d62980d78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix access of eELOSSByZone expr before initialization (#541) - Correctly write unmet reserves (in reg_dn.csv) (#575) - Correctly scale total reserves column (in reg_dn.csv) (#594) +- Add validation for `Reg_Max` and `Rsv_Max` columns in `Generators_data.csv` when `MUST_RUN` is set to 1 (#576) ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). diff --git a/src/model/resources/resources.jl b/src/model/resources/resources.jl index 99e142dfe8..5927fd0b05 100644 --- a/src/model/resources/resources.jl +++ b/src/model/resources/resources.jl @@ -33,6 +33,37 @@ function check_resource_type_flags(r::GenXResource) return error_strings end +@doc raw""" + check_mustrun_reserve_contribution(r::GenXResource) + +Make sure that a MUST_RUN resource has Reg_Max and Rsv_Max set to 0 (since they cannot contribute to reserves). +""" +function check_mustrun_reserve_contribution(r::GenXResource) + not_set = resource_attribute_not_set() + value = get(r, :MUST_RUN, not_set) + + error_strings = String[] + + if value == not_set + # not MUST_RUN so the rest is not applicable + return error_strings + end + + reg_max = get(r, :Reg_Max, not_set) + if reg_max != 0 + e = string("Resource ", resource_name(r), " has :MUST_RUN = ", value, " but :Reg_Max = ", reg_max, ".\n", + "MUST_RUN units must have Reg_Max = 0 since they cannot contribute to reserves.") + push!(error_strings, e) + end + rsv_max = get(r, :Rsv_Max, not_set) + if rsv_max != 0 + e = string("Resource ", resource_name(r), " has :MUST_RUN = ", value, " but :Rsv_Max = ", rsv_max, ".\n", + "MUST_RUN units must have Rsv_Max = 0 since they cannot contribute to reserves.") + push!(error_strings, e) + end + return error_strings +end + @doc raw""" check_longdurationstorage_applicability(r::GenXResource) @@ -110,6 +141,7 @@ function check_resource(r::GenXResource)::Vector{String} e = [e; check_resource_type_flags(r)] e = [e; check_longdurationstorage_applicability(r)] e = [e; check_maintenance_applicability(r)] + e = [e; check_mustrun_reserve_contribution(r)] return e end diff --git a/test/resource_test.jl b/test/resource_test.jl index 7524e27534..cbf07c1315 100644 --- a/test/resource_test.jl +++ b/test/resource_test.jl @@ -65,23 +65,71 @@ let :STOR => 0, :LDS => 1) + # MUST_RUN units contribution to reserves + must_run = Resource(:Resource => "must_run", + :THERM => 0, + :FLEX => 0, + :HYDRO => 0, + :VRE => 0, + :MUST_RUN => 1, + :STOR => 0, + :LDS => 0, + :Reg_Max => 0, + :Rsv_Max => 0) + bad_must_run = Resource(:Resource => "bad_must_run", + :THERM => 0, + :FLEX => 0, + :HYDRO => 0, + :VRE => 0, + :MUST_RUN => 1, + :STOR => 0, + :LDS => 0, + :Reg_Max => 0.083333333, + :Rsv_Max => 0.166666667) + bad_mustrun_reg = Resource(:Resource => "bad_mustrun_reg", + :THERM => 0, + :FLEX => 0, + :HYDRO => 0, + :VRE => 0, + :MUST_RUN => 1, + :STOR => 0, + :LDS => 0, + :Reg_Max => 0.083333333, + :Rsv_Max => 0) + bad_mustrun_rsv = Resource(:Resource => "bad_mustrun_rsv", + :THERM => 0, + :FLEX => 0, + :HYDRO => 0, + :VRE => 0, + :MUST_RUN => 1, + :STOR => 0, + :LDS => 0, + :Reg_Max => 0, + :Rsv_Max => 0.166666667) + function check_okay(resource) e = check_resource(resource) + println(e) @test length(e) == 0 end function check_bad(resource) e = check_resource(resource) + println(e) @test length(e) > 0 end check_okay(therm) check_okay(stor_lds) check_okay(hydro_lds) + check_okay(must_run) check_bad(bad_lds) check_bad(bad_none) check_bad(bad_twotypes) + check_bad(bad_must_run) + check_bad(bad_mustrun_reg) + check_bad(bad_mustrun_rsv) multiple_resources = [therm, stor_lds, hydro_lds] check_okay(multiple_resources)