diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7c777f20..74000284d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed default value of parameter scaling to 1 (#866) +- Deprecate HYDRO `min_power` in favour of `min_flow` (#864). ## [0.4.5] - 2025-07-07 diff --git a/docs/src/User_Guide/model_input.md b/docs/src/User_Guide/model_input.md index d0f672ac31..ef5a45f4f6 100644 --- a/docs/src/User_Guide/model_input.md +++ b/docs/src/User_Guide/model_input.md @@ -275,7 +275,8 @@ Each file contains cost and performance parameters for various generators and ot --- |**Column Name** | **Description**| | :------------ | :-----------| -|Min\_Power |[0,1], The minimum generation level for a unit as a fraction of total capacity. This value cannot be higher than the smallest time-dependent CF value for a resource in `Generators_variability.csv`.| +|Min\_Flow |[0,1], The minimum generation level for a unit as a fraction of total capacity. **Note**: setting a value greater than the time-dependent CF values for a resource in `Generators_variability.csv` can result in an infeasible model, because the unit might not be able to generate enough power and/or discharge enough water to meet the minimum flow requirement. Always double check the variability data before running the model. +|Min\_Power |[0,1], **DEPRECATED** (use `Min_Flow` instead). Retained for backwards compatibility; behaves identically to `Min_Flow`. |Ramp\_Up\_Percentage |[0,1], Maximum increase in power output from between two periods (typically hours), reported as a fraction of nameplate capacity.| |Ramp\_Dn\_Percentage |[0,1], Maximum decrease in power output from between two periods (typically hours), reported as a fraction of nameplate capacity.| |Hydro\_Energy\_to\_Power\_Ratio |The rated number of hours of reservoir hydro storage at peak discharge power output. (hours). | diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index bd98d4bebe..cac76e1c8e 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -83,6 +83,10 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) reserves_term = @expression(EP, [y in HYDRO_RES, t in 1:T], 0) regulation_term = @expression(EP, [y in HYDRO_RES, t in 1:T], 0) + # precompute min flow values for all hydro resources for efficiency + # Note: this will print the deprecation warning only once for each resource + min_flow_values = map(y -> min_flow(gen[y]), HYDRO_RES) + if setup["OperationalReserves"] > 0 HYDRO_RES_REG = intersect(HYDRO_RES, inputs["REG"]) # Set of reservoir hydro resources with regulation reserves HYDRO_RES_RSV = intersect(HYDRO_RES, inputs["RSV"]) # Set of reservoir hydro resources with spinning reserves @@ -160,7 +164,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) ramp_down_fraction(gen[y]) * EP[:eTotalCap][y] # Minimum streamflow running requirements (power generation and spills must be >= min value) in all hours cHydroMinFlow[y in HYDRO_RES, t in 1:T], - EP[:vP][y, t] + EP[:vSPILL][y, t] >= min_power(gen[y]) * EP[:eTotalCap][y] + EP[:vP][y, t] + EP[:vSPILL][y, t] >= min_flow_values[y] * EP[:eTotalCap][y] # DEV NOTE: When creating new hydro inputs, should rename Min_Power with Min_flow or similar for clarity since this includes spilled water as well # Maximum discharging rate must be less than power rating OR available stored energy at start of hour, whichever is less diff --git a/src/model/resources/resources.jl b/src/model/resources/resources.jl index 66370b3fb9..e300fd6f8f 100644 --- a/src/model/resources/resources.jl +++ b/src/model/resources/resources.jl @@ -717,8 +717,8 @@ function efficiency_down(r::T) where {T <: Union{Hydro, Storage}} end # Ramp up and down +min_power(r::Union{Electrolyzer, Thermal}) = get(r, :min_power, default_zero) const VarPower = Union{Electrolyzer, Hydro, Thermal} -min_power(r::VarPower) = get(r, :min_power, default_zero) ramp_up_fraction(r::VarPower) = get(r, :ramp_up_percentage, default_percent) ramp_down_fraction(r::VarPower) = get(r, :ramp_dn_percentage, default_percent) @@ -853,6 +853,26 @@ end Returns the indices of all hydro resources in the vector `rs`. """ hydro(rs::Vector{T}) where {T <: AbstractResource} = findall(r -> isa(r, Hydro), rs) +# deprecate min_power(r::Hydro) in favor of min_flow(r::Hydro) +# TODO: remove this in the next breaking release +function min_power(r::Hydro) + Base.depwarn("`min_power(r::Hydro)` is deprecated, use `min_flow(r::Hydro)` instead.", :min_power, force = true) + return get(r, :min_power, default_zero) +end +function min_flow(r::Hydro) + if haskey(r, :min_flow) && haskey(r, :min_power) + @warn "Both `min_flow` and `min_power` are defined for resource $(resource_name(r)). Using `min_flow`." + return r.min_flow + elseif haskey(r, :min_flow) + return r.min_flow + # if only min_power is defined, use it and warn the user + elseif haskey(r, :min_power) + @warn "Column `min_power` is deprecated, column `min_flow` will replace it soon, please update your input data." + return r.min_power + else + return default_zero + end +end # THERMAL interface """ diff --git a/test/test_load_resource_data.jl b/test/test_load_resource_data.jl index 00d979b197..b0c893143d 100644 --- a/test/test_load_resource_data.jl +++ b/test/test_load_resource_data.jl @@ -242,6 +242,35 @@ function test_resource_specific_attributes(gen, dfGen, inputs) dfGen[rs, :hydro_energy_to_power_ratio] end +function test_min_power_deprecation(gen, dfGen) + hydro_res = GenX.hydro(gen) + # 1. min_power should still work + @test GenX.min_power.(gen[hydro_res]) == dfGen[hydro_res, :min_power] + @test GenX.min_power.(gen[hydro_res]) == [0.117, 0.18, 0.402] + # 2. min_power should throw a deprecation warning + @test_logs (:warn, "`min_power(r::Hydro)` is deprecated, use `min_flow(r::Hydro)` instead.") GenX.min_power(gen[hydro_res[1]]) + # 3. min_flow should work with old column name + @test GenX.min_flow.(gen[hydro_res]) == [0.117, 0.18, 0.402] + # 4. min_flow should be the same as min_power with old column name + @test GenX.min_flow.(gen[hydro_res]) == GenX.min_power.(gen[hydro_res]) + # 5. min_flow should be using the new column name + test_hydro = GenX.Hydro(Dict(:resource => "test_hydro", + :min_flow => 10.0)) + @test GenX.min_flow(test_hydro) == 10.0 + @test GenX.min_power(test_hydro) == 0.0 + # 6. min_flow has the priority over min_power with old column name + test_hydro = GenX.Hydro(Dict(:resource => "test_hydro", + :min_flow => 10.0, + :min_power => 5.0)) + @test GenX.min_flow(test_hydro) == 10.0 + @test GenX.min_power(test_hydro) == 5.0 + # 7. default is zero + @test GenX.min_flow(GenX.Hydro(Dict(:resource => "test_hydro"))) == 0.0 + # 8. resources that are not Hydro, Electrolyzer, or Thermal should throw a method error + vre_res = GenX.vre(gen) + @test_throws MethodError GenX.min_flow(gen[vre_res[1]]) +end + function test_load_resources_data() setup = Dict("ParameterScale" => 0, "OperationalReserves" => 1, @@ -290,6 +319,11 @@ function test_load_resources_data() @testset "resource-specific attributes" begin test_resource_specific_attributes(gen, dfGen, inputs) end + + # Test that min_flow deprecation works + @testset "Min power deprecation" begin + test_min_power_deprecation(gen, dfGen) + end end function test_load_VRE_STOR_data()