From d6a5756eff508e00d641a1fd37d7e450916b9047 Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:06:29 -0600 Subject: [PATCH 01/25] Add `incremental_initial_input`, `decremental_initial_input` --- src/models/cost_functions/MarketBidCost.jl | 51 ++++++++++++++++------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index a26751f9c2..65f55987b5 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -11,7 +11,7 @@ Compatible with most US Market bidding mechanisms that support demand and genera """ @kwdef mutable struct MarketBidCost <: OperationalCost "No load cost" - no_load_cost::Union{TimeSeriesKey, Float64} + no_load_cost::Union{TimeSeriesKey, Nothing, Float64} = nothing "Start-up cost at different stages of the thermal cycle as the unit cools after a shutdown (e.g., *hot*, *warm*, or *cold* starts). Warm is also referred to as intermediate in some markets. Can also accept a single value if there is only one @@ -19,20 +19,24 @@ Compatible with most US Market bidding mechanisms that support demand and genera start_up::Union{TimeSeriesKey, StartUpStages} "Shut-down cost" shut_down::Float64 - "Sell Offer Curves data, which can be a time series or a [`CostCurve`](@ref) using - [`PiecewiseIncrementalCurve`](@ref)" + "Sell Offer Curves data, which can be a time series of [`PiecewiseStepData`](@ref) or a + [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" incremental_offer_curves::Union{ Nothing, - TimeSeriesKey, + TimeSeriesKey, # piecewise step data CostCurve{PiecewiseIncrementalCurve}, } = nothing - "Buy Offer Curves data, can be a time series or a [`CostCurve`](@ref) using - [`PiecewiseIncrementalCurve`](@ref)" + "Buy Offer Curves data, which can be a time series of [`PiecewiseStepData`](@ref) or a + [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" decremental_offer_curves::Union{ Nothing, TimeSeriesKey, CostCurve{PiecewiseIncrementalCurve}, } = nothing + "If using a time series for incremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" + incremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing + "If using a time series for decremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" + decremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing "Bids for the ancillary services" ancillary_service_offers::Vector{Service} = Vector{Service}() end @@ -54,6 +58,23 @@ MarketBidCost( ancillary_service_offers, ) +MarketBidCost( + no_load_cost::Float64, + start_up::Union{TimeSeriesKey, StartUpStages}, + shut_down, + incremental_offer_curves, + decremental_offer_curves, + ancillary_service_offers, +) = + MarketBidCost(; + no_load_cost = no_load_cost, + start_up = start_up, + shut_down = shut_down, + incremental_offer_curves = incremental_offer_curves, + decremental_offer_curves = decremental_offer_curves, + ancillary_service_offers = ancillary_service_offers + ) + # Constructor for demo purposes; non-functional. function MarketBidCost(::Nothing) MarketBidCost(; @@ -73,18 +94,22 @@ function MarketBidCost( shut_down, incremental_offer_curves = nothing, decremental_offer_curves = nothing, + incremental_initial_input = nothing, + decremental_initial_input = nothing, ancillary_service_offers = Vector{Service}(), ) # Intended for use with generators that are not multi-start (e.g. ThermalStandard). # Operators use `hot` when they don’t have multiple stages. start_up_multi = (hot = Float64(start_up), warm = 0.0, cold = 0.0) - return MarketBidCost( - no_load_cost, - start_up_multi, - shut_down, - incremental_offer_curves, - decremental_offer_curves, - ancillary_service_offers, + return MarketBidCost(; + no_load_cost = no_load_cost, + start_up = start_up_multi, + shut_down = shut_down, + incremental_offer_curves = incremental_offer_curves, + decremental_offer_curves = decremental_offer_curves, + incremental_initial_input = incremental_initial_input, + decremental_initial_input = decremental_initial_input, + ancillary_service_offers = ancillary_service_offers, ) end From 52321a00c9f67c0fd081f4ac08e20e86b06ac3f6 Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:11:22 -0600 Subject: [PATCH 02/25] First draft of support for `incremental_initial_input` WIP -- not fully tested and probably bad in a number of ways --- src/models/cost_function_timeseries.jl | 122 +++++++++++++++++---- src/models/cost_functions/MarketBidCost.jl | 46 ++++++-- test/test_cost_functions.jl | 98 +++++++++++++---- 3 files changed, 214 insertions(+), 52 deletions(-) diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index dbd2bf0db3..287e1dbf5f 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -4,7 +4,6 @@ function _validate_market_bid_cost(cost, context) StackTraces.stacktrace()[2].func, context, MarketBidCost, cost)) end -# VALIDATORS function _validate_reserve_demand_curve(cost, name) !(cost isa CostCurve{PiecewiseIncrementalCurve}) && throw( ArgumentError( @@ -94,12 +93,6 @@ Helper function for cost getters. - `start_time`: as in `get_time_series` - `len`: as in `get_time_series` """ -_process_get_cost(_, _, cost::Nothing, _, _, _, _) = throw( - ArgumentError( - "This cost component is empty, please use the corresponding setter to add cost data.", - ), -) - function _process_get_cost(::Type{T}, _, cost::T, transform_fn, start_time::Union{Nothing, Dates.DateTime}, len::Union{Nothing, Int}, @@ -123,18 +116,59 @@ end # GETTER IMPLEMENTATIONS """ -Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If -this field is a time series, the user may specify `start_time` and `len` and the function -returns a `TimeArray` of `CostCurve`s; if the field is not a time series, the function -returns a single `CostCurve`. +Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any +of the relevant fields (`incremental_offer_curves`, `initial_input`, `no_load_cost`) are +time series, the user may specify `start_time` and `len` and the function returns a +`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a +single `CostCurve`. """ -get_variable_cost( +function get_variable_cost( device::StaticInjection, cost::MarketBidCost; start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, -) = _process_get_cost(CostCurve{PiecewiseIncrementalCurve}, device, - get_incremental_offer_curves(cost), make_market_bid_curve, start_time, len) +) + function_data = if (get_incremental_offer_curves(cost) isa TimeSeriesKey) + get_incremental_offer_curves(device, cost; start_time = start_time, len = len) + else + get_incremental_offer_curves(device, cost) + end + initial_input = if (get_incremental_initial_input(cost) isa TimeSeriesKey) + get_incremental_initial_input(device, cost; start_time = start_time, len = len) + else + get_incremental_initial_input(device, cost) + end + input_at_zero = if (get_no_load_cost(cost) isa TimeSeriesKey) + get_no_load_cost(device, cost; start_time = start_time, len = len) + else + get_no_load_cost(device, cost) + end + params::Vector{Any} = [function_data, initial_input, input_at_zero] + first_time_series = findfirst(isa.(params, TimeSeries.TimeArray)) + if !isnothing(first_time_series) + timestamps = TimeSeries.timestamp(params[first_time_series]) + for (i, param) in enumerate(params) + if !(param isa TimeSeries.TimeArray) + params[i] = + TimeSeries.TimeArray(timestamps, fill(param, length(timestamps))) + end + end + !allequal(TimeSeries.timestamp.(params)) && + throw( + ArgumentError( + "Time series mismatch between incremental_offer_curves, incremental_initial_input, and no_load_cost", + ), + ) + @show collect(zip(collect.(TimeSeries.values.(params))...)) |> length + @show first(collect(zip(collect.(TimeSeries.values.(params))...))) + return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), + [ + make_market_bid_curve(fd, ii, iaz) for + (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) + ]) + end + return make_market_bid_curve(input_at_zero, initial_input, function_data) +end """ Retrieve the variable cost data for a `ReserveDemandCurve`. The user may specify @@ -188,22 +222,51 @@ function get_fuel_cost(component::StaticInjection; ) end +""" +Retrieve the `incremental_offer_curves` for a `StaticInjection` device with a +`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len` +and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the +function returns a single `Float64` or `Nothing`. +""" +get_incremental_offer_curves( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) = _process_get_cost(Union{PiecewiseStepData, CostCurve{PiecewiseIncrementalCurve}}, + device, get_incremental_offer_curves(cost), nothing, start_time, len) + +# TODO decremental + """ Retrieve the no-load cost data for a `StaticInjection` device with a `MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len` and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the function -returns a single `Float64`. +returns a single `Float64` or `Nothing`. """ get_no_load_cost( device::StaticInjection, cost::MarketBidCost; start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Float64, device, +) = _process_get_cost(Union{Nothing, Float64}, device, get_no_load_cost(cost), nothing, start_time, len) """ -Retrieve the no-load cost data for a `StaticInjection` device with a `MarketBidCost`. If +Retrieve the `incremental_initial_input` for a `StaticInjection` device with a `MarketBidCost`. +""" +get_incremental_initial_input( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) = _process_get_cost(Union{Nothing, Float64}, device, + get_incremental_initial_input(cost), nothing, start_time, len) + +# TODO decremental + +""" +Retrieve the startup cost data for a `StaticInjection` device with a `MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len` and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the function returns a single `Float64`. @@ -323,7 +386,7 @@ function set_fuel_cost!( end """ -Set the no-load cost for a `StaticInjection` device with a `MarketBidCost` to either a single number or a time series. +Set the no-load cost for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series. # Arguments - `sys::System`: PowerSystem System @@ -337,10 +400,31 @@ function set_no_load_cost!( ) market_bid_cost = get_operation_cost(component) _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - to_set = _process_set_cost(Float64, Float64, sys, component, data) + to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) set_no_load_cost!(market_bid_cost, to_set) end +""" +Set the `incremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series. + +# Arguments +- `sys::System`: PowerSystem System +- `component::StaticInjection`: Static injection device +- `time_series_data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype `Float64`. +""" +function set_incremental_initial_input!( + sys::System, + component::StaticInjection, + data::Union{Float64, IS.TimeSeriesData}, +) + market_bid_cost = get_operation_cost(component) + _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") + to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) + set_incremental_initial_input!(market_bid_cost, to_set) +end + +# TODO decremental + """ Set the startup cost for a `StaticInjection` device with a `MarketBidCost` to either a single `StartUpStages` or a time series. diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index 65f55987b5..decda78778 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -35,8 +35,7 @@ Compatible with most US Market bidding mechanisms that support demand and genera } = nothing "If using a time series for incremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" incremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing - "If using a time series for decremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" - decremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing + # TODO decremental "Bids for the ancillary services" ancillary_service_offers::Vector{Service} = Vector{Service}() end @@ -72,13 +71,13 @@ MarketBidCost( shut_down = shut_down, incremental_offer_curves = incremental_offer_curves, decremental_offer_curves = decremental_offer_curves, - ancillary_service_offers = ancillary_service_offers + ancillary_service_offers = ancillary_service_offers, ) # Constructor for demo purposes; non-functional. function MarketBidCost(::Nothing) MarketBidCost(; - no_load_cost = 0.0, + no_load_cost = nothing, start_up = (hot = START_COST, warm = START_COST, cold = START_COST), shut_down = 0.0, ) @@ -95,7 +94,6 @@ function MarketBidCost( incremental_offer_curves = nothing, decremental_offer_curves = nothing, incremental_initial_input = nothing, - decremental_initial_input = nothing, ancillary_service_offers = Vector{Service}(), ) # Intended for use with generators that are not multi-start (e.g. ThermalStandard). @@ -108,7 +106,6 @@ function MarketBidCost( incremental_offer_curves = incremental_offer_curves, decremental_offer_curves = decremental_offer_curves, incremental_initial_input = incremental_initial_input, - decremental_initial_input = decremental_initial_input, ancillary_service_offers = ancillary_service_offers, ) end @@ -121,8 +118,11 @@ get_start_up(value::MarketBidCost) = value.start_up get_shut_down(value::MarketBidCost) = value.shut_down """Get [`MarketBidCost`](@ref) `incremental_offer_curves`.""" get_incremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves -"""Get [`MarketBidCost`](@ref) `incremental_offer_curves`.""" +"""Get [`MarketBidCost`](@ref) `decremental_offer_curves`.""" get_decremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves +"""Get [`MarketBidCost`](@ref) `incremental_initial_input`.""" +get_incremental_initial_input(value::MarketBidCost) = value.incremental_initial_input +# TODO decremental """Get [`MarketBidCost`](@ref) `ancillary_service_offers`.""" get_ancillary_service_offers(value::MarketBidCost) = value.ancillary_service_offers @@ -135,6 +135,10 @@ set_shut_down!(value::MarketBidCost, val) = value.shut_down = val """Set [`MarketBidCost`](@ref) `incremental_offer_curves`.""" set_incremental_offer_curves!(value::MarketBidCost, val) = value.incremental_offer_curves = val +"""Set [`MarketBidCost`](@ref) `incremental_initial_input`.""" +set_incremental_initial_input!(value::MarketBidCost, val) = + value.incremental_initial_input = val +# TODO decremental """Set [`MarketBidCost`](@ref) `incremental_offer_curves`.""" set_decremental_offer_curves!(value::MarketBidCost, val) = value.decremental_offer_curves = val @@ -146,10 +150,7 @@ set_ancillary_service_offers!(value::MarketBidCost, val) = # curves in MarketBidCost) is a CostCurve{PiecewiseIncrementalCurve} with NaN initial input # and first x-coordinate function is_market_bid_curve(curve::ProductionVariableCostCurve) - (curve isa CostCurve{PiecewiseIncrementalCurve}) || return false - value_curve = get_value_curve(curve) - return isnan(get_initial_input(value_curve)) && - isnan(first(get_x_coords(get_function_data(value_curve)))) + return (curve isa CostCurve{PiecewiseIncrementalCurve}) end """ @@ -166,6 +167,18 @@ function make_market_bid_curve(powers::Vector{Float64}, return make_market_bid_curve(fd; power_units = power_units) end +make_market_bid_curve(initial_input::Union{Nothing, Real}, x_coords::Vector, slopes::Vector; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = + CostCurve(PiecewiseIncrementalCurve(initial_input, x_coords, slopes), power_units) + +make_market_bid_curve(input_at_zero::Union{Nothing, Real}, + initial_input::Union{Nothing, Real}, x_coords::Vector, slopes::Vector; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = + CostCurve( + PiecewiseIncrementalCurve(input_at_zero, initial_input, x_coords, slopes), + power_units, + ) + """ Make a CostCurve{PiecewiseIncrementalCurve} suitable for inclusion in a MarketBidCost from the FunctionData that might be used to store such a cost curve in a time series. @@ -177,7 +190,16 @@ function make_market_bid_curve(data::PiecewiseStepData; "The first x-coordinate in the PiecewiseStepData representation must be NaN", ), ) - cc = CostCurve(IncrementalCurve(data, NaN), power_units) + cc = CostCurve(IncrementalCurve(data, nothing), power_units) @assert is_market_bid_curve(cc) return cc end + +make_market_bid_curve(fd::PiecewiseStepData, initial_input::Union{Nothing, Real}; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = + CostCurve(IncrementalCurve(fd, initial_input), power_units) + +make_market_bid_curve(fd::PiecewiseStepData, initial_input::Union{Nothing, Real}, + input_at_zero::Union{Nothing, Real}; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = + CostCurve(IncrementalCurve(fd, initial_input, input_at_zero), power_units) diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 9a73427109..66bd4d3dcf 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -311,13 +311,27 @@ end @test is_market_bid_curve(make_market_bid_curve(get_function_data(mbc))) @test_throws ArgumentError make_market_bid_curve( [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0]) + + mbc2 = make_market_bid_curve(20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) + @test is_market_bid_curve(mbc2) + @test is_market_bid_curve( + make_market_bid_curve(get_function_data(mbc2), get_initial_input(mbc2)), + ) + + mbc3 = make_market_bid_curve(18.0, 20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) + @test is_market_bid_curve(mbc3) + @test is_market_bid_curve( + make_market_bid_curve(get_function_data(mbc2), get_initial_input(mbc3)), + ) end test_costs = Dict( CostCurve{QuadraticCurve} => repeat([CostCurve(QuadraticCurve(999.0, 2.0, 1.0))], 24), - CostCurve{PiecewiseIncrementalCurve} => + PiecewiseStepData => repeat([make_market_bid_curve([2.0, 3.0], [4.0, 6.0])], 24), + PiecewiseIncrementalCurve => + repeat([make_market_bid_curve(18.0, 20.0, [1.0, 2.0, 3.0], [4.0, 6.0])], 24), Float64 => collect(11.0:34.0), PSY.StartUpStages => @@ -338,15 +352,15 @@ test_costs = Dict( generator = get_component(ThermalStandard, sys, "322_CT_6") market_bid = MarketBidCost(nothing) set_operation_cost!(generator, market_bid) - forecast = IS.Deterministic( + forecast_fd = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_quadratic)), resolution, ) - @test_throws TypeError set_variable_cost!(sys, generator, forecast) + @test_throws TypeError set_variable_cost!(sys, generator, forecast_fd) for s in generator.services - forecast = IS.Deterministic(get_name(s), service_data, resolution) - @test_throws TypeError set_service_bid!(sys, generator, s, forecast) + forecast_fd = IS.Deterministic(get_name(s), service_data, resolution) + @test_throws TypeError set_service_bid!(sys, generator, s, forecast_fd) end end @@ -355,27 +369,30 @@ end resolution = Dates.Hour(1) name = "test" horizon = 24 - data_pwl = SortedDict(initial_time => test_costs[CostCurve{PiecewiseIncrementalCurve}]) + data_pwl = SortedDict(initial_time => test_costs[PiecewiseStepData]) service_data = data_pwl sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") generator = get_component(ThermalStandard, sys, "322_CT_6") market_bid = MarketBidCost(nothing) set_operation_cost!(generator, market_bid) - forecast = IS.Deterministic( + forecast_fd = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - set_variable_cost!(sys, generator, forecast) + set_variable_cost!(sys, generator, forecast_fd) + for s in generator.services - forecast = IS.Deterministic( + forecast_fd = IS.Deterministic( get_name(s), Dict(k => get_function_data.(v) for (k, v) in pairs(service_data)), resolution, ) - set_service_bid!(sys, generator, s, forecast) + set_service_bid!(sys, generator, s, forecast_fd) end + iocs = get_incremental_offer_curves(generator, market_bid) + isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time) @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) @@ -388,6 +405,45 @@ end end end +@testset "Test MarketBidCost with PiecewiseLinearData Cost Timeseries, initial_input, and no_load_cost" begin + initial_time = Dates.DateTime("2020-01-01") + resolution = Dates.Hour(1) + name = "test" + horizon = 24 + data_pwl = SortedDict(initial_time => test_costs[PiecewiseIncrementalCurve]) + service_data = data_pwl + sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") + generator = get_component(ThermalStandard, sys, "322_CT_6") + market_bid = MarketBidCost(nothing) + set_operation_cost!(generator, market_bid) + forecast_fd = IS.Deterministic( + "variable_cost_function_data", + Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), + resolution, + ) + set_variable_cost!(sys, generator, forecast_fd) + + forecast_ii = IS.Deterministic( + "variable_cost_initial_input", + Dict(k => get_initial_input.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), + resolution, + ) + PSY.set_incremental_initial_input!(sys, generator, forecast_ii) + + forecast_iaz = IS.Deterministic( + "variable_cost_input_at_zero", + Dict(k => get_input_at_zero.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), + resolution, + ) + set_no_load_cost!(sys, generator, forecast_iaz) + + iocs = get_incremental_offer_curves(generator, market_bid) + @show iocs + isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) + cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time) + @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) +end + @testset "Test MarketBidCost with single `start_up::Number` value" begin expected = (hot = 1.0, warm = 0.0, cold = 0.0) # should only be used for the `hot` value. cost = MarketBidCost(; start_up = 1, no_load_cost = rand(), shut_down = rand()) @@ -400,17 +456,17 @@ end other_time = initial_time + resolution name = "test" horizon = 24 - data_pwl = SortedDict(initial_time => test_costs[CostCurve{PiecewiseIncrementalCurve}], - other_time => test_costs[CostCurve{PiecewiseIncrementalCurve}]) + data_pwl = SortedDict(initial_time => test_costs[PiecewiseStepData], + other_time => test_costs[PiecewiseStepData]) sys = System(100.0) reserve = ReserveDemandCurve{ReserveUp}(nothing) add_component!(sys, reserve) - forecast = IS.Deterministic( + forecast_fd = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - set_variable_cost!(sys, reserve, forecast) + set_variable_cost!(sys, reserve, forecast_fd) cost_forecast = get_variable_cost(reserve; start_time = initial_time) @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) end @@ -433,8 +489,8 @@ end resolution = Dates.Hour(1) horizon = 24 data_float = SortedDict(initial_time => test_costs[Float64]) - forecast = IS.Deterministic("fuel_cost", data_float, resolution) - set_fuel_cost!(sys, generator, forecast) + forecast_fd = IS.Deterministic("fuel_cost", data_float, resolution) + set_fuel_cost!(sys, generator, forecast_fd) fuel_forecast = get_fuel_cost(generator; start_time = initial_time) @test first(TimeSeries.values(fuel_forecast)) == first(data_float[initial_time]) fuel_forecast = get_fuel_cost(generator) # missing start_time filled in with initial time @@ -448,7 +504,7 @@ end set_operation_cost!(generator, market_bid) op_cost = get_operation_cost(generator) - @test get_no_load_cost(generator, op_cost) == 0.0 + @test get_no_load_cost(generator, op_cost) === nothing set_no_load_cost!(sys, generator, 1.23) @test get_no_load_cost(generator, op_cost) == 1.23 @@ -457,9 +513,9 @@ end resolution = Dates.Hour(1) horizon = 24 data_float = SortedDict(initial_time => test_costs[Float64]) - forecast = IS.Deterministic("no_load_cost", data_float, resolution) + forecast_fd = IS.Deterministic("no_load_cost", data_float, resolution) - set_no_load_cost!(sys, generator, forecast) + set_no_load_cost!(sys, generator, forecast_fd) @test first(TimeSeries.values(get_no_load_cost(generator, op_cost))) == first(data_float[initial_time]) end @@ -482,13 +538,13 @@ end resolution = Dates.Hour(1) horizon = 24 data_sus = SortedDict(initial_time => test_costs[PSY.StartUpStages]) - forecast = IS.Deterministic( + forecast_fd = IS.Deterministic( "start_up", Dict(k => Tuple.(v) for (k, v) in pairs(data_sus)), resolution, ) - set_start_up!(sys, generator, forecast) + set_start_up!(sys, generator, forecast_fd) @test first(TimeSeries.values(get_start_up(generator, op_cost))) == first(data_sus[initial_time]) end From f4568d2e07abcfa13c3920ef250a78c7afaba531 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 30 Jul 2024 12:30:59 -0700 Subject: [PATCH 03/25] add dc current information --- src/descriptors/power_system_structs.json | 14 ++++++++++ .../generated/InterconnectingConverter.jl | 26 ++++++++++++++++--- src/models/generated/includes.jl | 4 +++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index aa3db10041..1f1cd3e010 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -2565,6 +2565,20 @@ "data_type": "MinMax", "needs_conversion": true }, + { + "name": "dc_current", + "comment": "DC current (A) on the converter", + "null_value": "0.0", + "data_type": "Float64", + "valid_range": "dc_current_limits", + "validation_action": "warn" + }, + { + "name": "dc_current_limits", + "comment": "Minimum and maximum stable dc current limits (A)", + "null_value": "(min=0.0, max=0.0)", + "data_type": "MinMax" + }, { "name": "base_power", "comment": "Base power of the converter in MVA", diff --git a/src/models/generated/InterconnectingConverter.jl b/src/models/generated/InterconnectingConverter.jl index d94c5eff35..745f6254f0 100644 --- a/src/models/generated/InterconnectingConverter.jl +++ b/src/models/generated/InterconnectingConverter.jl @@ -13,6 +13,8 @@ This file is auto-generated. Do not edit. active_power::Float64 rating::Float64 active_power_limits::MinMax + dc_current::Float64 + dc_current_limits::MinMax base_power::Float64 loss_function::Union{LinearCurve, QuadraticCurve} services::Vector{Service} @@ -31,6 +33,8 @@ Interconnecting Power Converter (IPC) for transforming power from an ACBus to a - `active_power::Float64`: Active power (MW) on the DC side, validation range: `active_power_limits` - `rating::Float64`: Maximum output power rating of the converter (MVA), validation range: `(0, nothing)` - `active_power_limits::MinMax`: Minimum and maximum stable active power levels (MW) +- `dc_current::Float64`: DC current (A) on the converter, validation range: `dc_current_limits` +- `dc_current_limits::MinMax`: Minimum and maximum stable dc current limits (A) - `base_power::Float64`: Base power of the converter in MVA, validation range: `(0, nothing)` - `loss_function::Union{LinearCurve, QuadraticCurve}`: (default: `LinearCurve(0.0)`) Linear or quadratic loss function with respect to the converter current - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to @@ -53,6 +57,10 @@ mutable struct InterconnectingConverter <: StaticInjection rating::Float64 "Minimum and maximum stable active power levels (MW)" active_power_limits::MinMax + "DC current (A) on the converter" + dc_current::Float64 + "Minimum and maximum stable dc current limits (A)" + dc_current_limits::MinMax "Base power of the converter in MVA" base_power::Float64 "Linear or quadratic loss function with respect to the converter current" @@ -67,12 +75,12 @@ mutable struct InterconnectingConverter <: StaticInjection internal::InfrastructureSystemsInternal end -function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) +function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) end -function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function, services, dynamic_injector, ext, internal, ) +function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function, services, dynamic_injector, ext, internal, ) end # Constructor for demo purposes; non-functional. @@ -85,6 +93,8 @@ function InterconnectingConverter(::Nothing) active_power=0.0, rating=0.0, active_power_limits=(min=0.0, max=0.0), + dc_current=0.0, + dc_current_limits=(min=0.0, max=0.0), base_power=0.0, loss_function=LinearCurve(0.0), services=Device[], @@ -107,6 +117,10 @@ get_active_power(value::InterconnectingConverter) = get_value(value, value.activ get_rating(value::InterconnectingConverter) = get_value(value, value.rating) """Get [`InterconnectingConverter`](@ref) `active_power_limits`.""" get_active_power_limits(value::InterconnectingConverter) = get_value(value, value.active_power_limits) +"""Get [`InterconnectingConverter`](@ref) `dc_current`.""" +get_dc_current(value::InterconnectingConverter) = value.dc_current +"""Get [`InterconnectingConverter`](@ref) `dc_current_limits`.""" +get_dc_current_limits(value::InterconnectingConverter) = value.dc_current_limits """Get [`InterconnectingConverter`](@ref) `base_power`.""" get_base_power(value::InterconnectingConverter) = value.base_power """Get [`InterconnectingConverter`](@ref) `loss_function`.""" @@ -132,6 +146,10 @@ set_active_power!(value::InterconnectingConverter, val) = value.active_power = s set_rating!(value::InterconnectingConverter, val) = value.rating = set_value(value, val) """Set [`InterconnectingConverter`](@ref) `active_power_limits`.""" set_active_power_limits!(value::InterconnectingConverter, val) = value.active_power_limits = set_value(value, val) +"""Set [`InterconnectingConverter`](@ref) `dc_current`.""" +set_dc_current!(value::InterconnectingConverter, val) = value.dc_current = val +"""Set [`InterconnectingConverter`](@ref) `dc_current_limits`.""" +set_dc_current_limits!(value::InterconnectingConverter, val) = value.dc_current_limits = val """Set [`InterconnectingConverter`](@ref) `base_power`.""" set_base_power!(value::InterconnectingConverter, val) = value.base_power = val """Set [`InterconnectingConverter`](@ref) `loss_function`.""" diff --git a/src/models/generated/includes.jl b/src/models/generated/includes.jl index 685525a3b5..c8076888fb 100644 --- a/src/models/generated/includes.jl +++ b/src/models/generated/includes.jl @@ -486,6 +486,8 @@ export get_dP_lim export get_db export get_dbd_pnts export get_dc_bus +export get_dc_current +export get_dc_current_limits export get_dc_dc_inductor export get_dc_link_capacitance export get_delta_t @@ -1034,6 +1036,8 @@ export set_dP_lim! export set_db! export set_dbd_pnts! export set_dc_bus! +export set_dc_current! +export set_dc_current_limits! export set_dc_dc_inductor! export set_dc_link_capacitance! export set_delta_t! From b122ead1fa28054582f3ee40bb468bd4a0525ae2 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 6 Aug 2024 20:23:27 -0700 Subject: [PATCH 04/25] update max dc current --- src/descriptors/power_system_structs.json | 12 ++++---- .../generated/InterconnectingConverter.jl | 28 +++++++++---------- src/models/generated/includes.jl | 4 +-- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index 1f1cd3e010..e8e6902040 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -2569,15 +2569,13 @@ "name": "dc_current", "comment": "DC current (A) on the converter", "null_value": "0.0", - "data_type": "Float64", - "valid_range": "dc_current_limits", - "validation_action": "warn" + "data_type": "Float64" }, { - "name": "dc_current_limits", - "comment": "Minimum and maximum stable dc current limits (A)", - "null_value": "(min=0.0, max=0.0)", - "data_type": "MinMax" + "name": "max_dc_current", + "comment": "Maximum stable dc current limits (A)", + "null_value": "0.0", + "data_type": "Float64" }, { "name": "base_power", diff --git a/src/models/generated/InterconnectingConverter.jl b/src/models/generated/InterconnectingConverter.jl index 745f6254f0..a53e6ba221 100644 --- a/src/models/generated/InterconnectingConverter.jl +++ b/src/models/generated/InterconnectingConverter.jl @@ -14,7 +14,7 @@ This file is auto-generated. Do not edit. rating::Float64 active_power_limits::MinMax dc_current::Float64 - dc_current_limits::MinMax + max_dc_current::Float64 base_power::Float64 loss_function::Union{LinearCurve, QuadraticCurve} services::Vector{Service} @@ -33,8 +33,8 @@ Interconnecting Power Converter (IPC) for transforming power from an ACBus to a - `active_power::Float64`: Active power (MW) on the DC side, validation range: `active_power_limits` - `rating::Float64`: Maximum output power rating of the converter (MVA), validation range: `(0, nothing)` - `active_power_limits::MinMax`: Minimum and maximum stable active power levels (MW) -- `dc_current::Float64`: DC current (A) on the converter, validation range: `dc_current_limits` -- `dc_current_limits::MinMax`: Minimum and maximum stable dc current limits (A) +- `dc_current::Float64`: DC current (A) on the converter +- `max_dc_current::Float64`: Maximum stable dc current limits (A) - `base_power::Float64`: Base power of the converter in MVA, validation range: `(0, nothing)` - `loss_function::Union{LinearCurve, QuadraticCurve}`: (default: `LinearCurve(0.0)`) Linear or quadratic loss function with respect to the converter current - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to @@ -59,8 +59,8 @@ mutable struct InterconnectingConverter <: StaticInjection active_power_limits::MinMax "DC current (A) on the converter" dc_current::Float64 - "Minimum and maximum stable dc current limits (A)" - dc_current_limits::MinMax + "Maximum stable dc current limits (A)" + max_dc_current::Float64 "Base power of the converter in MVA" base_power::Float64 "Linear or quadratic loss function with respect to the converter current" @@ -75,12 +75,12 @@ mutable struct InterconnectingConverter <: StaticInjection internal::InfrastructureSystemsInternal end -function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) +function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) end -function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, dc_current_limits, base_power, loss_function, services, dynamic_injector, ext, internal, ) +function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function, services, dynamic_injector, ext, internal, ) end # Constructor for demo purposes; non-functional. @@ -94,7 +94,7 @@ function InterconnectingConverter(::Nothing) rating=0.0, active_power_limits=(min=0.0, max=0.0), dc_current=0.0, - dc_current_limits=(min=0.0, max=0.0), + max_dc_current=0.0, base_power=0.0, loss_function=LinearCurve(0.0), services=Device[], @@ -119,8 +119,8 @@ get_rating(value::InterconnectingConverter) = get_value(value, value.rating) get_active_power_limits(value::InterconnectingConverter) = get_value(value, value.active_power_limits) """Get [`InterconnectingConverter`](@ref) `dc_current`.""" get_dc_current(value::InterconnectingConverter) = value.dc_current -"""Get [`InterconnectingConverter`](@ref) `dc_current_limits`.""" -get_dc_current_limits(value::InterconnectingConverter) = value.dc_current_limits +"""Get [`InterconnectingConverter`](@ref) `max_dc_current`.""" +get_max_dc_current(value::InterconnectingConverter) = value.max_dc_current """Get [`InterconnectingConverter`](@ref) `base_power`.""" get_base_power(value::InterconnectingConverter) = value.base_power """Get [`InterconnectingConverter`](@ref) `loss_function`.""" @@ -148,8 +148,8 @@ set_rating!(value::InterconnectingConverter, val) = value.rating = set_value(val set_active_power_limits!(value::InterconnectingConverter, val) = value.active_power_limits = set_value(value, val) """Set [`InterconnectingConverter`](@ref) `dc_current`.""" set_dc_current!(value::InterconnectingConverter, val) = value.dc_current = val -"""Set [`InterconnectingConverter`](@ref) `dc_current_limits`.""" -set_dc_current_limits!(value::InterconnectingConverter, val) = value.dc_current_limits = val +"""Set [`InterconnectingConverter`](@ref) `max_dc_current`.""" +set_max_dc_current!(value::InterconnectingConverter, val) = value.max_dc_current = val """Set [`InterconnectingConverter`](@ref) `base_power`.""" set_base_power!(value::InterconnectingConverter, val) = value.base_power = val """Set [`InterconnectingConverter`](@ref) `loss_function`.""" diff --git a/src/models/generated/includes.jl b/src/models/generated/includes.jl index c8076888fb..ff21485800 100644 --- a/src/models/generated/includes.jl +++ b/src/models/generated/includes.jl @@ -487,7 +487,6 @@ export get_db export get_dbd_pnts export get_dc_bus export get_dc_current -export get_dc_current_limits export get_dc_dc_inductor export get_dc_link_capacitance export get_delta_t @@ -572,6 +571,7 @@ export get_max_constant_active_power export get_max_constant_reactive_power export get_max_current_active_power export get_max_current_reactive_power +export get_max_dc_current export get_max_impedance_active_power export get_max_impedance_reactive_power export get_max_output_fraction @@ -1037,7 +1037,6 @@ export set_db! export set_dbd_pnts! export set_dc_bus! export set_dc_current! -export set_dc_current_limits! export set_dc_dc_inductor! export set_dc_link_capacitance! export set_delta_t! @@ -1122,6 +1121,7 @@ export set_max_constant_active_power! export set_max_constant_reactive_power! export set_max_current_active_power! export set_max_current_reactive_power! +export set_max_dc_current! export set_max_impedance_active_power! export set_max_impedance_reactive_power! export set_max_output_fraction! From 848abd19aca3518794ce1ce855caff9af14e42a2 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 6 Aug 2024 20:31:05 -0700 Subject: [PATCH 05/25] add new loss model for two-terminal hvdc --- src/descriptors/power_system_structs.json | 7 ++++--- src/models/generated/TwoTerminalHVDCLine.jl | 14 +++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index e8e6902040..2e531e8cda 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -1046,9 +1046,10 @@ }, { "name": "loss", - "comment": "Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow)", - "null_value": "(l0=0.0, l1=0.0)", - "data_type": "NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}" + "comment": "Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments.", + "null_value": "LinearCurve(0.0)", + "data_type": "Union{LinearCurve, PiecewiseIncrementalCurve}", + "default": "LinearCurve(0.0)" }, { "name": "services", diff --git a/src/models/generated/TwoTerminalHVDCLine.jl b/src/models/generated/TwoTerminalHVDCLine.jl index 1a4696c78f..1863e4a66f 100644 --- a/src/models/generated/TwoTerminalHVDCLine.jl +++ b/src/models/generated/TwoTerminalHVDCLine.jl @@ -14,7 +14,7 @@ This file is auto-generated. Do not edit. active_power_limits_to::MinMax reactive_power_limits_from::MinMax reactive_power_limits_to::MinMax - loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}} + loss::Union{LinearCurve, PiecewiseIncrementalCurve} services::Vector{Service} ext::Dict{String, Any} internal::InfrastructureSystemsInternal @@ -33,7 +33,7 @@ This model is appropriate for operational simulations with a linearized DC power - `active_power_limits_to::MinMax`: Minimum and maximum active power flows to the TO node (MW) - `reactive_power_limits_from::MinMax`: Minimum and maximum reactive power limits to the FROM node (MVAR) - `reactive_power_limits_to::MinMax`: Minimum and maximum reactive power limits to the TO node (MVAR) -- `loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}`: Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow) +- `loss::Union{LinearCurve, PiecewiseIncrementalCurve}`: (default: `LinearCurve(0.0)`) Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments. - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to - `ext::Dict{String, Any}`: (default: `Dict{String, Any}()`) An *ext*ra dictionary for users to add metadata that are not used in simulation, such as latitude and longitude. See [Adding additional fields](@ref) - `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference @@ -55,8 +55,8 @@ mutable struct TwoTerminalHVDCLine <: ACBranch reactive_power_limits_from::MinMax "Minimum and maximum reactive power limits to the TO node (MVAR)" reactive_power_limits_to::MinMax - "Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow)" - loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}} + "Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments." + loss::Union{LinearCurve, PiecewiseIncrementalCurve} "Services that this device contributes to" services::Vector{Service} "An *ext*ra dictionary for users to add metadata that are not used in simulation, such as latitude and longitude. See [Adding additional fields](@ref)" @@ -65,11 +65,11 @@ mutable struct TwoTerminalHVDCLine <: ACBranch internal::InfrastructureSystemsInternal end -function TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services=Device[], ext=Dict{String, Any}(), ) +function TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss=LinearCurve(0.0), services=Device[], ext=Dict{String, Any}(), ) TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services, ext, InfrastructureSystemsInternal(), ) end -function TwoTerminalHVDCLine(; name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services=Device[], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) +function TwoTerminalHVDCLine(; name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss=LinearCurve(0.0), services=Device[], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services, ext, internal, ) end @@ -84,7 +84,7 @@ function TwoTerminalHVDCLine(::Nothing) active_power_limits_to=(min=0.0, max=0.0), reactive_power_limits_from=(min=0.0, max=0.0), reactive_power_limits_to=(min=0.0, max=0.0), - loss=(l0=0.0, l1=0.0), + loss=LinearCurve(0.0), services=Device[], ext=Dict{String, Any}(), ) From 1ba7cb47c0560ef5f2360b576119637c5101bc2d Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 6 Aug 2024 20:36:06 -0700 Subject: [PATCH 06/25] update data and parsers for new loss model --- src/parsers/power_models_data.jl | 2 +- src/parsers/power_system_table_data.jl | 2 +- test/data_14bus_pu.jl | 4 ++-- test/data_5bus_pu.jl | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parsers/power_models_data.jl b/src/parsers/power_models_data.jl index 4880060867..536c95f9b4 100644 --- a/src/parsers/power_models_data.jl +++ b/src/parsers/power_models_data.jl @@ -784,7 +784,7 @@ function make_dcline(name::String, d::Dict, bus_f::ACBus, bus_t::ACBus) active_power_limits_to = (min = d["pmint"], max = d["pmaxt"]), reactive_power_limits_from = (min = d["qminf"], max = d["qmaxf"]), reactive_power_limits_to = (min = d["qmint"], max = d["qmaxt"]), - loss = (l0 = d["loss0"], l1 = d["loss1"]), + loss = LinearCurve(d["loss1"], d["loss0"]), ) end diff --git a/src/parsers/power_system_table_data.jl b/src/parsers/power_system_table_data.jl index 7c9ea3f464..eb2bb1092e 100644 --- a/src/parsers/power_system_table_data.jl +++ b/src/parsers/power_system_table_data.jl @@ -467,7 +467,7 @@ function dc_branch_csv_parser!(sys::System, data::PowerSystemTableData) :max_reactive_power_limit_to, ) - loss = (l0 = 0.0, l1 = dc_branch.loss) #TODO: Can we infer this from the other data?, + loss = LinearCurve(dc_branch.loss) #TODO: Can we infer this from the other data?, value = TwoTerminalHVDCLine(; name = dc_branch.name, diff --git a/test/data_14bus_pu.jl b/test/data_14bus_pu.jl index 6c5abd2306..5e05a65e49 100644 --- a/test/data_14bus_pu.jl +++ b/test/data_14bus_pu.jl @@ -189,7 +189,7 @@ branches14_dc(nodes14) = [ (min = -600.0, max = 600), (min = -600.0, max = 600), (min = -600.0, max = 600), - (l0 = 0.01, l1 = 0.001), + LinearCurve(0.001, 0.01), ), TwoTerminalHVDCLine( "DCLine4", @@ -200,7 +200,7 @@ branches14_dc(nodes14) = [ (min = -600.0, max = 600), (min = -600.0, max = 600), (min = -600.0, max = 600), - (l0 = 0.01, l1 = 0.001), + LinearCurve(0.001, 0.01), ), #Line("Line3", true, 0.0, 0.0, Arc(from=nodes14[2],to=nodes14[3]), 0.04699, 0.19797, (from=0.0219, to=0.0219), 5.522, 1.04), #Line("Line4", true, 0.0, 0.0, Arc(from=nodes14[2],to=nodes14[4]), 0.05811, 0.17632, (from=0.017, to=0.017), 6.052, 1.04), diff --git a/test/data_5bus_pu.jl b/test/data_5bus_pu.jl index 5e071e6669..77999cc72f 100644 --- a/test/data_5bus_pu.jl +++ b/test/data_5bus_pu.jl @@ -41,7 +41,7 @@ branches5_dc(nodes5) = [ (min = -3000, max = 3000), (min = -3000.0, max = 3000.0), (min = -3000.0, max = 3000.0), - (l0 = 0.0, l1 = 0.01), + LinearCurve(0.01), ), Line( "3", From 1dcabd535d62dac2ab1fb5e94e6adf5dbcfd5b2d Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 25 Sep 2024 22:52:36 -0700 Subject: [PATCH 07/25] [WIP] Update MarketBidCost interface --- src/PowerSystems.jl | 1 + src/models/cost_function_timeseries.jl | 231 ++++++++++++++++++++- src/models/cost_functions/MarketBidCost.jl | 94 +++++---- 3 files changed, 279 insertions(+), 47 deletions(-) diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index b311860c42..dfe79ca746 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -388,6 +388,7 @@ export get_decremental_offer_curves, set_decremental_offer_curves! export get_ancillary_service_offers, set_ancillary_service_offers! export get_services_bid export set_variable_cost! +export set_incremental_variable_cost!, set_decremental_variable_cost! export set_service_bid! export iterate_windows export get_window diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index 287e1dbf5f..3e803db417 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -128,6 +128,9 @@ function get_variable_cost( start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, ) + if typeof(get_incremental_offer_curves(cost)) <: CostCurve + return get_incremental_offer_curves(cost) + end function_data = if (get_incremental_offer_curves(cost) isa TimeSeriesKey) get_incremental_offer_curves(device, cost; start_time = start_time, len = len) else @@ -159,15 +162,102 @@ function get_variable_cost( "Time series mismatch between incremental_offer_curves, incremental_initial_input, and no_load_cost", ), ) - @show collect(zip(collect.(TimeSeries.values.(params))...)) |> length - @show first(collect(zip(collect.(TimeSeries.values.(params))...))) + #@show collect(zip(collect.(TimeSeries.values.(params))...)) |> length + #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), [ - make_market_bid_curve(fd, ii, iaz) for + _make_market_bid_curve(fd, ii; input_at_zero = iaz) for (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) ]) end - return make_market_bid_curve(input_at_zero, initial_input, function_data) + return make_market_bid_curve( + function_data, + initial_input; + input_at_zero = input_at_zero, + ) +end + +""" +Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any +of the relevant fields (`incremental_offer_curves`, `initial_input`, `no_load_cost`) are +time series, the user may specify `start_time` and `len` and the function returns a +`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a +single `CostCurve`. +""" +function get_incremental_variable_cost( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) + return get_variable_cost( + device, + cost; + start_time = start_time, + len = len, + ) +end + +""" +Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any +of the relevant fields (`decremental_offer_curves`, `initial_input`, `no_load_cost`) are +time series, the user may specify `start_time` and `len` and the function returns a +`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a +single `CostCurve`. +""" +function get_decremental_variable_cost( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) + if typeof(get_decremental_offer_curves(cost)) <: CostCurve + return get_decremental_offer_curves(cost) + end + function_data = if (get_decremental_offer_curves(cost) isa TimeSeriesKey) + get_decremental_offer_curves(device, cost; start_time = start_time, len = len) + else + get_decremental_offer_curves(device, cost) + end + initial_input = if (get_decremental_initial_input(cost) isa TimeSeriesKey) + get_decremental_initial_input(device, cost; start_time = start_time, len = len) + else + get_decremental_initial_input(device, cost) + end + input_at_zero = if (get_no_load_cost(cost) isa TimeSeriesKey) + get_no_load_cost(device, cost; start_time = start_time, len = len) + else + get_no_load_cost(device, cost) + end + params::Vector{Any} = [function_data, initial_input, input_at_zero] + first_time_series = findfirst(isa.(params, TimeSeries.TimeArray)) + if !isnothing(first_time_series) + timestamps = TimeSeries.timestamp(params[first_time_series]) + for (i, param) in enumerate(params) + if !(param isa TimeSeries.TimeArray) + params[i] = + TimeSeries.TimeArray(timestamps, fill(param, length(timestamps))) + end + end + !allequal(TimeSeries.timestamp.(params)) && + throw( + ArgumentError( + "Time series mismatch between incremental_offer_curves, incremental_initial_input, and no_load_cost", + ), + ) + #@show collect(zip(collect.(TimeSeries.values.(params))...)) |> length + #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) + return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), + [ + _make_market_bid_curve(fd, ii; input_at_zero = iaz) for + (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) + ]) + end + return make_market_bid_curve( + function_data, + initial_input; + input_at_zero = input_at_zero, + ) end """ @@ -236,7 +326,19 @@ get_incremental_offer_curves( ) = _process_get_cost(Union{PiecewiseStepData, CostCurve{PiecewiseIncrementalCurve}}, device, get_incremental_offer_curves(cost), nothing, start_time, len) -# TODO decremental +""" +Retrieve the `decremental_offer_curves` for a `StaticInjection` device with a +`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len` +and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the +function returns a single `Float64` or `Nothing`. +""" +get_decremental_offer_curves( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) = _process_get_cost(Union{PiecewiseStepData, CostCurve{PiecewiseIncrementalCurve}}, + device, get_decremental_offer_curves(cost), nothing, start_time, len) """ Retrieve the no-load cost data for a `StaticInjection` device with a `MarketBidCost`. If @@ -263,7 +365,16 @@ get_incremental_initial_input( ) = _process_get_cost(Union{Nothing, Float64}, device, get_incremental_initial_input(cost), nothing, start_time, len) -# TODO decremental +""" +Retrieve the `decremental_initial_input` for a `StaticInjection` device with a `MarketBidCost`. +""" +get_decremental_initial_input( + device::StaticInjection, + cost::MarketBidCost; + start_time::Union{Nothing, Dates.DateTime} = nothing, + len::Union{Nothing, Int} = nothing, +) = _process_get_cost(Union{Nothing, Float64}, device, + get_decremental_initial_input(cost), nothing, start_time, len) """ Retrieve the startup cost data for a `StaticInjection` device with a `MarketBidCost`. If @@ -309,7 +420,7 @@ end # SETTER IMPLEMENTATIONS """ -Set the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. +Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. # Arguments - `sys::System`: PowerSystem System @@ -317,14 +428,27 @@ Set the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. - `time_series_data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype `PiecewiseStepData`. +- `power_units::UnitSystem`: Units to be used for data. Must be NATURAL_UNITS for """ function set_variable_cost!( sys::System, component::StaticInjection, data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, + power_units::UnitSystem, ) market_bid_cost = get_operation_cost(component) _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") + if (typeof(data) <: CostCurve{PiecewiseIncrementalCurve}) && + (data.power_units != power_units) + throw( + ArgumentError( + "Units specified in CostCurve data differs from the units specified in the set cost.", + ), + ) + end + if (typeof(data) <: IS.TimeSeriesData) && (power_units != UnitSystem.NATURAL_UNITS) + throw(ArgumentError("Time Series data for MarketBidCost must be in NATURAL_UNITS.")) + end to_set = _process_set_cost( CostCurve{PiecewiseIncrementalCurve}, PiecewiseStepData, @@ -332,7 +456,73 @@ function set_variable_cost!( component, data, ) + set_incremental_offer_curves!(market_bid_cost, to_set) + return +end + +""" +Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. + +# Arguments +- `sys::System`: PowerSystem System +- `component::StaticInjection`: Static injection device +- `time_series_data::Union{Nothing, IS.TimeSeriesData, + CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype + `PiecewiseStepData`. +- `power_units::UnitSystem`: Units to be used for data. +""" +function set_incremental_variable_cost!( + sys::System, + component::StaticInjection, + data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, + power_units::UnitSystem, +) + set_variable_cost!(sys, component, data, power_units) + return +end + +""" +Set the decremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. + +# Arguments +- `sys::System`: PowerSystem System +- `component::StaticInjection`: Static injection device +- `time_series_data::Union{Nothing, IS.TimeSeriesData, + CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype + `PiecewiseStepData`. +- `power_units::UnitSystem`: Units to be used for data. +""" +function set_decremental_variable_cost!( + sys::System, + component::StaticInjection, + data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, + power_units::UnitSystem, +) + market_bid_cost = get_operation_cost(component) + _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") + + if (typeof(data) <: CostCurve{PiecewiseIncrementalCurve}) && + (data.power_units != power_units) + throw( + ArgumentError( + "Units specified in CostCurve data differs from the units specified in the set cost.", + ), + ) + end + if (typeof(data) <: IS.TimeSeriesData) && (power_units != UnitSystem.NATURAL_UNITS) + throw(ArgumentError("Time Series data for MarketBidCost must be in NATURAL_UNITS.")) + end + to_set = _process_set_cost( + CostCurve{PiecewiseIncrementalCurve}, + PiecewiseStepData, + sys, + component, + data, + ) + + set_decremental_offer_curves!(market_bid_cost, to_set) + return end """ @@ -423,7 +613,24 @@ function set_incremental_initial_input!( set_incremental_initial_input!(market_bid_cost, to_set) end -# TODO decremental +""" +Set the `decremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series. + +# Arguments +- `sys::System`: PowerSystem System +- `component::StaticInjection`: Static injection device +- `time_series_data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype `Float64`. +""" +function set_decremental_initial_input!( + sys::System, + component::StaticInjection, + data::Union{Float64, IS.TimeSeriesData}, +) + market_bid_cost = get_operation_cost(component) + _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") + to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) + set_decremental_initial_input!(market_bid_cost, to_set) +end """ Set the startup cost for a `StaticInjection` device with a `MarketBidCost` to either a single `StartUpStages` or a time series. @@ -458,6 +665,7 @@ function set_service_bid!( component::StaticInjection, service::Service, time_series_data::IS.TimeSeriesData, + power_units::UnitSystem, ) data_type = IS.eltype_data(time_series_data) !(data_type <: PiecewiseStepData) && @@ -471,6 +679,13 @@ function set_service_bid!( "Name provided in the TimeSeries Data $(get_name(time_series_data)), doesn't match the Service $(get_name(service)).", ) end + if power_units != UnitSystem.NATURAL_UNITS + throw( + ArgumentError( + "Power Unit specified for service market bids must be NATURAL_UNITS", + ), + ) + end verify_device_eligibility(sys, component, service) add_time_series!(sys, component, time_series_data) ancillary_service_offers = get_ancillary_service_offers(get_operation_cost(component)) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index decda78778..eb60b415df 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -35,7 +35,8 @@ Compatible with most US Market bidding mechanisms that support demand and genera } = nothing "If using a time series for incremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" incremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing - # TODO decremental + "If using a time series for decremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" + decremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing "Bids for the ancillary services" ancillary_service_offers::Vector{Service} = Vector{Service}() end @@ -94,6 +95,7 @@ function MarketBidCost( incremental_offer_curves = nothing, decremental_offer_curves = nothing, incremental_initial_input = nothing, + decremental_initial_input = nothing, ancillary_service_offers = Vector{Service}(), ) # Intended for use with generators that are not multi-start (e.g. ThermalStandard). @@ -106,6 +108,7 @@ function MarketBidCost( incremental_offer_curves = incremental_offer_curves, decremental_offer_curves = decremental_offer_curves, incremental_initial_input = incremental_initial_input, + dencremental_initial_input = decremental_initial_input, ancillary_service_offers = ancillary_service_offers, ) end @@ -122,7 +125,8 @@ get_incremental_offer_curves(value::MarketBidCost) = value.incremental_offer_cur get_decremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves """Get [`MarketBidCost`](@ref) `incremental_initial_input`.""" get_incremental_initial_input(value::MarketBidCost) = value.incremental_initial_input -# TODO decremental +"""Get [`MarketBidCost`](@ref) `decremental_initial_input`.""" +get_decremental_initial_input(value::MarketBidCost) = value.decremental_initial_input """Get [`MarketBidCost`](@ref) `ancillary_service_offers`.""" get_ancillary_service_offers(value::MarketBidCost) = value.ancillary_service_offers @@ -138,14 +142,22 @@ set_incremental_offer_curves!(value::MarketBidCost, val) = """Set [`MarketBidCost`](@ref) `incremental_initial_input`.""" set_incremental_initial_input!(value::MarketBidCost, val) = value.incremental_initial_input = val -# TODO decremental """Set [`MarketBidCost`](@ref) `incremental_offer_curves`.""" set_decremental_offer_curves!(value::MarketBidCost, val) = value.decremental_offer_curves = val +"""Set [`MarketBidCost`](@ref) `decremental_initial_input`.""" +set_decremental_initial_input!(value::MarketBidCost, val) = + value.decremental_initial_input = val """Set [`MarketBidCost`](@ref) `ancillary_service_offers`.""" set_ancillary_service_offers!(value::MarketBidCost, val) = value.ancillary_service_offers = val +"""Auxiliary Method for setting up start up that are not multi-start""" +function set_start_up!(value::MarketBidCost, val::Real) + start_up_multi = (hot = Float64(val), warm = 0.0, cold = 0.0) + set_start_up!(value, start_up_multi) +end + # Each market bid curve (the elements that make up the incremental and decremental offer # curves in MarketBidCost) is a CostCurve{PiecewiseIncrementalCurve} with NaN initial input # and first x-coordinate @@ -155,51 +167,55 @@ end """ Make a CostCurve{PiecewiseIncrementalCurve} suitable for inclusion in a MarketBidCost from a -vector of power values, a vector of marginal costs, and an optional units system. The -minimum power, and cost at minimum power, are not represented. +vector of power values, a vector of marginal costs, a float of initial input, and an optional units system and input at zero. + +# Examples +```julia +mbc = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0) +mbc2 = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0; input_at_zero = 10.0) +mbc3 = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0; power_inputs = UnitSystem.NATURAL_UNITS) +``` """ function make_market_bid_curve(powers::Vector{Float64}, - marginal_costs::Vector{Float64}; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) - (length(powers) != length(marginal_costs)) && - throw(ArgumentError("Must specify an equal number of powers and marginal_costs")) - fd = PiecewiseStepData(vcat(NaN, powers), marginal_costs) - return make_market_bid_curve(fd; power_units = power_units) + marginal_costs::Vector{Float64}, + initial_input::Float64; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS, + input_at_zero::Union{Nothing, Float64} = nothing) + if length(powers) == length(marginal_costs) + 1 + fd = PiecewiseStepData(powers, marginal_costs) + return make_market_bid_curve( + fd, + initial_input; + power_units = power_units, + input_at_zero, + ) + else + throw( + ArgumentError( + "Must specify exactly one more number of powers ($(length(powers))) than marginal_costs ($(length(marginal_costs)))", + ), + ) + end end -make_market_bid_curve(initial_input::Union{Nothing, Real}, x_coords::Vector, slopes::Vector; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = - CostCurve(PiecewiseIncrementalCurve(initial_input, x_coords, slopes), power_units) - -make_market_bid_curve(input_at_zero::Union{Nothing, Real}, - initial_input::Union{Nothing, Real}, x_coords::Vector, slopes::Vector; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = - CostCurve( - PiecewiseIncrementalCurve(input_at_zero, initial_input, x_coords, slopes), - power_units, - ) - """ Make a CostCurve{PiecewiseIncrementalCurve} suitable for inclusion in a MarketBidCost from the FunctionData that might be used to store such a cost curve in a time series. """ -function make_market_bid_curve(data::PiecewiseStepData; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) - !isnan(first(get_x_coords(data))) && throw( - ArgumentError( - "The first x-coordinate in the PiecewiseStepData representation must be NaN", - ), - ) - cc = CostCurve(IncrementalCurve(data, nothing), power_units) +function make_market_bid_curve(data::PiecewiseStepData, + initial_input::Float64; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS, + input_at_zero::Union{Nothing, Float64} = nothing) + cc = CostCurve(IncrementalCurve(data, initial_input, input_at_zero), power_units) @assert is_market_bid_curve(cc) return cc end -make_market_bid_curve(fd::PiecewiseStepData, initial_input::Union{Nothing, Real}; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = - CostCurve(IncrementalCurve(fd, initial_input), power_units) - -make_market_bid_curve(fd::PiecewiseStepData, initial_input::Union{Nothing, Real}, - input_at_zero::Union{Nothing, Real}; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS) = - CostCurve(IncrementalCurve(fd, initial_input, input_at_zero), power_units) +function _make_market_bid_curve(data::PiecewiseStepData, + initial_input::Union{Nothing, Float64}; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS, + input_at_zero::Union{Nothing, Float64} = nothing) + cc = CostCurve(IncrementalCurve(data, initial_input, input_at_zero), power_units) + @assert is_market_bid_curve(cc) + return cc +end From d67bdcc3921792d9f58ed2507e1b80148e0f81fa Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 25 Sep 2024 22:52:45 -0700 Subject: [PATCH 08/25] [WIP] Update test cost functions --- test/test_cost_functions.jl | 97 ++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index b9c81a5b5d..9ccc242607 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -12,33 +12,80 @@ "FuelCurve with power_units UnitSystem.NATURAL_UNITS = 2, fuel_cost 4.0, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" end -@testset "Test market bid cost interface" begin - mbc = make_market_bid_curve([100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0]) +@testset "Test MarketBidCost direct struct creation" begin + sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") + generator = get_component(ThermalStandard, sys, "322_CT_6") + #Update generator cost to MarketBidCost using Natural Units + powers = [22.0, 33.0, 44.0, 55.0] # MW + marginal_costs = [25.0, 26.0, 28.0] # $/MWh + initial_input = 50.0 # $/h + mbc = MarketBidCost(; + start_up = 0.0, + shut_down = 0.0, + incremental_offer_curves = CostCurve( + PiecewiseIncrementalCurve( + initial_input, + powers, + marginal_costs, + ), + ), + ) + set_operation_cost!(generator, mbc) + @test get_operation_cost(generator) isa MarketBidCost +end + +@testset "Test Make market bid curve interface" begin + mbc = make_market_bid_curve( + [0.0, 100.0, 105.0, 120.0, 130.0], + [25.0, 26.0, 28.0, 30.0], + 10.0, + ) @test is_market_bid_curve(mbc) @test is_market_bid_curve(make_market_bid_curve(get_function_data(mbc))) @test_throws ArgumentError make_market_bid_curve( - [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0]) + [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0, 40.0]) - mbc2 = make_market_bid_curve(20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) + mbc2 = make_market_bid_curve([1.0, 2.0, 3.0], [4.0, 6.0], 10.0; input_at_zero = 2.0) @test is_market_bid_curve(mbc2) @test is_market_bid_curve( make_market_bid_curve(get_function_data(mbc2), get_initial_input(mbc2)), ) - mbc3 = make_market_bid_curve(18.0, 20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) - @test is_market_bid_curve(mbc3) - @test is_market_bid_curve( - make_market_bid_curve(get_function_data(mbc2), get_initial_input(mbc3)), - ) + sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") + generator = get_component(ThermalStandard, sys, "322_CT_6") + market_bid = MarketBidCost(nothing) + mbc3 = make_market_bid_curve([22.0, 33.0, 44.0, 55.0], [25.0, 26.0, 28.0], 50.0) + set_incremental_offer_curves!(market_bid, mbc3) + set_start_up!(market_bid, 0.0) + set_operation_cost!(generator, market_bid) + @test get_operation_cost(generator) isa MarketBidCost end test_costs = Dict( CostCurve{QuadraticCurve} => repeat([CostCurve(QuadraticCurve(999.0, 2.0, 1.0))], 24), PiecewiseStepData => - repeat([make_market_bid_curve([2.0, 3.0], [4.0, 6.0])], 24), + repeat( + [ + PSY._make_market_bid_curve( + PiecewiseStepData([0.0, 2.0, 3.0], [4.0, 6.0]), + nothing, + ), + ], + 24, + ), PiecewiseIncrementalCurve => - repeat([make_market_bid_curve(18.0, 20.0, [1.0, 2.0, 3.0], [4.0, 6.0])], 24), + repeat( + [ + make_market_bid_curve( + [1.0, 2.0, 3.0], + [4.0, 6.0], + 18.0; + input_at_zero = 20.0, + ), + ], + 24, + ), Float64 => collect(11.0:34.0), PSY.StartUpStages => @@ -64,10 +111,11 @@ test_costs = Dict( Dict(k => get_function_data.(v) for (k, v) in pairs(data_quadratic)), resolution, ) - @test_throws TypeError set_variable_cost!(sys, generator, forecast_fd) + power_units = UnitSystem.NATURAL_UNITS + @test_throws TypeError set_variable_cost!(sys, generator, forecast_fd, power_units) for s in generator.services forecast_fd = IS.Deterministic(get_name(s), service_data, resolution) - @test_throws TypeError set_service_bid!(sys, generator, s, forecast_fd) + @test_throws TypeError set_service_bid!(sys, generator, s, forecast_fd, power_units) end end @@ -76,26 +124,40 @@ end resolution = Dates.Hour(1) name = "test" horizon = 24 + power_units = UnitSystem.NATURAL_UNITS data_pwl = SortedDict(initial_time => test_costs[PiecewiseStepData]) service_data = data_pwl sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") generator = get_component(ThermalStandard, sys, "322_CT_6") market_bid = MarketBidCost(nothing) set_operation_cost!(generator, market_bid) - forecast_fd = IS.Deterministic( + forecast_fd = Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - set_variable_cost!(sys, generator, forecast_fd) + @test_throws ArgumentError set_variable_cost!( + sys, + generator, + forecast_fd, + UnitSystem.SYSTEM_BASE, + ) + set_variable_cost!(sys, generator, forecast_fd, power_units) for s in generator.services - forecast_fd = IS.Deterministic( + forecast_fd = Deterministic( get_name(s), Dict(k => get_function_data.(v) for (k, v) in pairs(service_data)), resolution, ) - set_service_bid!(sys, generator, s, forecast_fd) + @test_throws ArgumentError set_service_bid!( + sys, + generator, + s, + forecast_fd, + UnitSystem.SYSTEM_BASE, + ) + set_service_bid!(sys, generator, s, forecast_fd, power_units) end iocs = get_incremental_offer_curves(generator, market_bid) @@ -145,7 +207,6 @@ end set_no_load_cost!(sys, generator, forecast_iaz) iocs = get_incremental_offer_curves(generator, market_bid) - @show iocs isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time) @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) From 1b73b6501998cd0cb0247c8e1c5dd64aa3b79e53 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 26 Sep 2024 18:54:04 -0700 Subject: [PATCH 09/25] update auxiliary functions --- src/models/cost_functions/MarketBidCost.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index eb60b415df..d7a25c6acb 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -91,7 +91,7 @@ Accepts a single `start_up` value to use as the `hot` value, with `warm` and `co function MarketBidCost( no_load_cost, start_up::Real, - shut_down, + shut_down; incremental_offer_curves = nothing, decremental_offer_curves = nothing, incremental_initial_input = nothing, @@ -108,7 +108,7 @@ function MarketBidCost( incremental_offer_curves = incremental_offer_curves, decremental_offer_curves = decremental_offer_curves, incremental_initial_input = incremental_initial_input, - dencremental_initial_input = decremental_initial_input, + decremental_initial_input = decremental_initial_input, ancillary_service_offers = ancillary_service_offers, ) end @@ -211,8 +211,11 @@ function make_market_bid_curve(data::PiecewiseStepData, return cc end -function _make_market_bid_curve(data::PiecewiseStepData, - initial_input::Union{Nothing, Float64}; +""" +Auxiliary make market bid curve for timeseries with nothing inputs. +""" +function _make_market_bid_curve(data::PiecewiseStepData; + initial_input::Union{Nothing, Float64} = nothing, power_units::UnitSystem = UnitSystem.NATURAL_UNITS, input_at_zero::Union{Nothing, Float64} = nothing) cc = CostCurve(IncrementalCurve(data, initial_input, input_at_zero), power_units) From 4e569356fd0a9671a022d49beffc74bd0ab5ab95 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 26 Sep 2024 18:54:17 -0700 Subject: [PATCH 10/25] use auxiliary method for getters --- src/models/cost_function_timeseries.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index 3e803db417..8f1af801f3 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -166,7 +166,7 @@ function get_variable_cost( #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), [ - _make_market_bid_curve(fd, ii; input_at_zero = iaz) for + _make_market_bid_curve(fd; initial_input = ii, input_at_zero = iaz) for (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) ]) end @@ -249,7 +249,7 @@ function get_decremental_variable_cost( #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), [ - _make_market_bid_curve(fd, ii; input_at_zero = iaz) for + _make_market_bid_curve(fd; initial_input = ii, input_at_zero = iaz) for (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) ]) end @@ -269,7 +269,7 @@ get_variable_cost( start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, ) = _process_get_cost(CostCurve{PiecewiseIncrementalCurve}, service, get_variable(service), - make_market_bid_curve, start_time, len) + _make_market_bid_curve, start_time, len) """ Return service bid time series data for a `StaticInjection` device with a `MarketBidCost`. @@ -292,7 +292,7 @@ function get_services_bid( len = len, count = 1, ) - converted = read_and_convert_ts(ts, service, start_time, len, make_market_bid_curve) + converted = read_and_convert_ts(ts, service, start_time, len, _make_market_bid_curve) return converted end From 729f7cb5d27bc2562392e1b737ae406b45b9d378 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 26 Sep 2024 18:54:21 -0700 Subject: [PATCH 11/25] update tests --- test/test_cost_functions.jl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 9ccc242607..ff04b3752d 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -20,7 +20,7 @@ end marginal_costs = [25.0, 26.0, 28.0] # $/MWh initial_input = 50.0 # $/h mbc = MarketBidCost(; - start_up = 0.0, + start_up = (hot = 0.0, warm = 0.0, cold = 0.0), shut_down = 0.0, incremental_offer_curves = CostCurve( PiecewiseIncrementalCurve( @@ -41,9 +41,11 @@ end 10.0, ) @test is_market_bid_curve(mbc) - @test is_market_bid_curve(make_market_bid_curve(get_function_data(mbc))) + @test is_market_bid_curve( + make_market_bid_curve(get_function_data(mbc), get_initial_input(mbc)), + ) @test_throws ArgumentError make_market_bid_curve( - [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0, 40.0]) + [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0, 40.0], 10.0) mbc2 = make_market_bid_curve([1.0, 2.0, 3.0], [4.0, 6.0], 10.0; input_at_zero = 2.0) @test is_market_bid_curve(mbc2) @@ -69,7 +71,6 @@ test_costs = Dict( [ PSY._make_market_bid_curve( PiecewiseStepData([0.0, 2.0, 3.0], [4.0, 6.0]), - nothing, ), ], 24, @@ -179,6 +180,7 @@ end resolution = Dates.Hour(1) name = "test" horizon = 24 + power_units = UnitSystem.NATURAL_UNITS data_pwl = SortedDict(initial_time => test_costs[PiecewiseIncrementalCurve]) service_data = data_pwl sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") @@ -190,7 +192,7 @@ end Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - set_variable_cost!(sys, generator, forecast_fd) + set_variable_cost!(sys, generator, forecast_fd, power_units) forecast_ii = IS.Deterministic( "variable_cost_initial_input", @@ -214,7 +216,10 @@ end @testset "Test MarketBidCost with single `start_up::Number` value" begin expected = (hot = 1.0, warm = 0.0, cold = 0.0) # should only be used for the `hot` value. - cost = MarketBidCost(; start_up = 1, no_load_cost = rand(), shut_down = rand()) + no_load_cost = rand() + start_up = 1.0 + shut_down = rand() + cost = MarketBidCost(no_load_cost, start_up, shut_down) @test get_start_up(cost) == expected end From 7dbb0f87217c4f4ecaa528b49b87ef04eef73ca7 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 26 Sep 2024 18:54:27 -0700 Subject: [PATCH 12/25] update exports --- src/PowerSystems.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index dfe79ca746..f330eced7c 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -385,6 +385,8 @@ export get_start_up export get_shut_down export get_incremental_offer_curves, set_incremental_offer_curves! export get_decremental_offer_curves, set_decremental_offer_curves! +export get_incremental_initial_input, set_incremental_initial_input! +export get_decremental_initial_input, set_decremental_initial_input! export get_ancillary_service_offers, set_ancillary_service_offers! export get_services_bid export set_variable_cost! From 41232cfddea53c2c5091c601762f11af32624b9d Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 14:04:13 -0700 Subject: [PATCH 13/25] fix test serialization typo --- test/test_serialization.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_serialization.jl b/test/test_serialization.jl index 50fdce8724..e30624695c 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -106,7 +106,8 @@ end add_component!(sys, gen) ta = TimeSeries.TimeArray(dates, data) time_series = IS.SingleTimeSeries(; name = "variable_cost", data = ta) - set_variable_cost!(sys, gen, time_series) + power_units = UnitSystem.NATURAL_UNITS + set_variable_cost!(sys, gen, time_series, power_units) service = ConstantReserve{ReserveDown}(; name = "init_$i", available = false, @@ -125,6 +126,7 @@ end gen, service, IS.SingleTimeSeries(; name = "init_$i", data = ta), + power_units, ) end _, result = validate_serialization(sys) From 42ac087cdc7963b0e027d514eebde723586787b8 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 14:56:07 -0700 Subject: [PATCH 14/25] decremental tests --- test/test_cost_functions.jl | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index ff04b3752d..2698571c0d 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -214,6 +214,46 @@ end @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) end +@testset "Test MarketBidCost with Decremental PiecewiseLinearData Cost Timeseries, initial_input, and no_load_cost" begin + initial_time = Dates.DateTime("2020-01-01") + resolution = Dates.Hour(1) + name = "test" + horizon = 24 + power_units = UnitSystem.NATURAL_UNITS + data_pwl = SortedDict(initial_time => test_costs[PiecewiseIncrementalCurve]) + service_data = data_pwl + sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") + generator = get_component(ThermalStandard, sys, "322_CT_6") + market_bid = MarketBidCost(nothing) + set_operation_cost!(generator, market_bid) + forecast_fd = IS.Deterministic( + "decremental_variable_cost_function_data", + Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), + resolution, + ) + set_decremental_variable_cost!(sys, generator, forecast_fd, power_units) + + forecast_ii = IS.Deterministic( + "decremental_variable_cost_initial_input", + Dict(k => get_initial_input.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), + resolution, + ) + PSY.set_decremental_initial_input!(sys, generator, forecast_ii) + + forecast_iaz = IS.Deterministic( + "variable_cost_input_at_zero", + Dict(k => get_input_at_zero.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), + resolution, + ) + set_no_load_cost!(sys, generator, forecast_iaz) + + iocs = get_decremental_offer_curves(generator, market_bid) + isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) + cost_forecast = + get_decremental_variable_cost(generator, market_bid; start_time = initial_time) + @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) +end + @testset "Test MarketBidCost with single `start_up::Number` value" begin expected = (hot = 1.0, warm = 0.0, cold = 0.0) # should only be used for the `hot` value. no_load_cost = rand() From 790846a66b4dbeb107a4a99e227800b50c38b9fc Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 14:56:18 -0700 Subject: [PATCH 15/25] update auxiliary methods --- src/models/cost_function_timeseries.jl | 15 +++++++++------ src/models/cost_functions/MarketBidCost.jl | 14 +++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index 8f1af801f3..2a1c2709e4 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -426,8 +426,9 @@ Set the incremental variable cost bid for a `StaticInjection` device with a `Mar - `sys::System`: PowerSystem System - `component::StaticInjection`: Static injection device - `time_series_data::Union{Nothing, IS.TimeSeriesData, - CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype - `PiecewiseStepData`. + CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype + `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and + not accepted for time series data. - `power_units::UnitSystem`: Units to be used for data. Must be NATURAL_UNITS for """ function set_variable_cost!( @@ -468,8 +469,9 @@ Set the incremental variable cost bid for a `StaticInjection` device with a `Mar - `sys::System`: PowerSystem System - `component::StaticInjection`: Static injection device - `time_series_data::Union{Nothing, IS.TimeSeriesData, - CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype - `PiecewiseStepData`. + CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype + `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and + not accepted for time series data. - `power_units::UnitSystem`: Units to be used for data. """ function set_incremental_variable_cost!( @@ -489,8 +491,9 @@ Set the decremental variable cost bid for a `StaticInjection` device with a `Mar - `sys::System`: PowerSystem System - `component::StaticInjection`: Static injection device - `time_series_data::Union{Nothing, IS.TimeSeriesData, - CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype - `PiecewiseStepData`. + CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype + `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and + not accepted for time series data. - `power_units::UnitSystem`: Units to be used for data. """ function set_decremental_variable_cost!( diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index d7a25c6acb..d5753171e6 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -19,14 +19,14 @@ Compatible with most US Market bidding mechanisms that support demand and genera start_up::Union{TimeSeriesKey, StartUpStages} "Shut-down cost" shut_down::Float64 - "Sell Offer Curves data, which can be a time series of [`PiecewiseStepData`](@ref) or a + "Sell Offer Curves data, which can be a time series of `PiecewiseStepData` or a [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" incremental_offer_curves::Union{ Nothing, TimeSeriesKey, # piecewise step data CostCurve{PiecewiseIncrementalCurve}, } = nothing - "Buy Offer Curves data, which can be a time series of [`PiecewiseStepData`](@ref) or a + "Buy Offer Curves data, which can be a time series of `PiecewiseStepData` or a [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" decremental_offer_curves::Union{ Nothing, @@ -47,6 +47,8 @@ MarketBidCost( shut_down, incremental_offer_curves, decremental_offer_curves, + incremental_initial_input, + decremental_initial_input, ancillary_service_offers, ) = MarketBidCost( @@ -55,6 +57,8 @@ MarketBidCost( shut_down, incremental_offer_curves, decremental_offer_curves, + incremental_initial_input, + decremental_initial_input, ancillary_service_offers, ) @@ -64,6 +68,8 @@ MarketBidCost( shut_down, incremental_offer_curves, decremental_offer_curves, + incremental_initial_input, + decremental_initial_input, ancillary_service_offers, ) = MarketBidCost(; @@ -72,6 +78,8 @@ MarketBidCost( shut_down = shut_down, incremental_offer_curves = incremental_offer_curves, decremental_offer_curves = decremental_offer_curves, + incremental_initial_input = incremental_initial_input, + decremental_initial_input = decremental_initial_input, ancillary_service_offers = ancillary_service_offers, ) @@ -122,7 +130,7 @@ get_shut_down(value::MarketBidCost) = value.shut_down """Get [`MarketBidCost`](@ref) `incremental_offer_curves`.""" get_incremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves """Get [`MarketBidCost`](@ref) `decremental_offer_curves`.""" -get_decremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves +get_decremental_offer_curves(value::MarketBidCost) = value.decremental_offer_curves """Get [`MarketBidCost`](@ref) `incremental_initial_input`.""" get_incremental_initial_input(value::MarketBidCost) = value.incremental_initial_input """Get [`MarketBidCost`](@ref) `decremental_initial_input`.""" From 985783022c1286216ecd04d7e439f0c59ec549ac Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 14:56:23 -0700 Subject: [PATCH 16/25] update exports --- src/PowerSystems.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index f330eced7c..103d62df3a 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -380,6 +380,7 @@ export get_data export iterate_components export get_time_series_multiple export get_variable_cost +export get_incremental_variable_cost, get_decremental_variable_cost export get_no_load_cost export get_start_up export get_shut_down From df835896b2014be9bf85839d0c31a87f2ad5f8e3 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 15:07:38 -0700 Subject: [PATCH 17/25] remove extra auxiliary function --- src/models/cost_functions/MarketBidCost.jl | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index d5753171e6..6d6b5ac2ff 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -62,27 +62,6 @@ MarketBidCost( ancillary_service_offers, ) -MarketBidCost( - no_load_cost::Float64, - start_up::Union{TimeSeriesKey, StartUpStages}, - shut_down, - incremental_offer_curves, - decremental_offer_curves, - incremental_initial_input, - decremental_initial_input, - ancillary_service_offers, -) = - MarketBidCost(; - no_load_cost = no_load_cost, - start_up = start_up, - shut_down = shut_down, - incremental_offer_curves = incremental_offer_curves, - decremental_offer_curves = decremental_offer_curves, - incremental_initial_input = incremental_initial_input, - decremental_initial_input = decremental_initial_input, - ancillary_service_offers = ancillary_service_offers, - ) - # Constructor for demo purposes; non-functional. function MarketBidCost(::Nothing) MarketBidCost(; From e197a5b2920304ed3731df03436da4e2f50fe7f0 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 15:32:01 -0700 Subject: [PATCH 18/25] add auxiliary constructor --- src/models/cost_functions/MarketBidCost.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index 6d6b5ac2ff..7d9f0907c8 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -41,6 +41,7 @@ Compatible with most US Market bidding mechanisms that support demand and genera ancillary_service_offers::Vector{Service} = Vector{Service}() end +"Auxiliary Constructor for Deserialization with Integer at no load cost" MarketBidCost( no_load_cost::Integer, start_up::Union{TimeSeriesKey, StartUpStages}, @@ -62,6 +63,25 @@ MarketBidCost( ancillary_service_offers, ) +"""Auxiliary Constructor for TestData""" +MarketBidCost( + no_load_cost::Float64, + start_up::Union{TimeSeriesKey, StartUpStages}, + shut_down, + incremental_offer_curves, + ancillary_service_offers, +) = + MarketBidCost( + Float64(no_load_cost), + start_up, + shut_down, + incremental_offer_curves, + nothing, + nothing, + nothing, + ancillary_service_offers, + ) + # Constructor for demo purposes; non-functional. function MarketBidCost(::Nothing) MarketBidCost(; From 22fbec5c2aae2ac0a10eafcfd9de4a7b1207ac81 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 27 Sep 2024 15:50:46 -0700 Subject: [PATCH 19/25] update auxiliary method --- src/models/cost_functions/MarketBidCost.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index 7d9f0907c8..b184ee0fe5 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -69,6 +69,7 @@ MarketBidCost( start_up::Union{TimeSeriesKey, StartUpStages}, shut_down, incremental_offer_curves, + decremental_offer_curves, ancillary_service_offers, ) = MarketBidCost( @@ -76,7 +77,7 @@ MarketBidCost( start_up, shut_down, incremental_offer_curves, - nothing, + decremental_offer_curves, nothing, nothing, ancillary_service_offers, From af89171e09f2b5efb3bdd1340eece6465e81ad5a Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Sun, 29 Sep 2024 20:05:44 -0700 Subject: [PATCH 20/25] add docs --- docs/src/how_to/market_bid_cost.md | 101 +++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/docs/src/how_to/market_bid_cost.md b/docs/src/how_to/market_bid_cost.md index 4a1bd8ce13..8ca855df06 100644 --- a/docs/src/how_to/market_bid_cost.md +++ b/docs/src/how_to/market_bid_cost.md @@ -4,9 +4,61 @@ A [`MarketBidCost`](@ref) is an `OperationalCost` data structure that allows the cost model that is very similar to most US electricity market auctions with bids for energy and ancillary services jointly. This page showcases how to create data for this cost function. -## Adding Energy bids to MarketBidCost +## Adding a Single Incremental Energy bids to MarketBidCost -### Step 1: Constructiong device with MarketBidCost +### Construct directly the MarketBidCost using the `make_market_bid_curve` method. + +The `make_market_bid_curve` creates an incremental or decremental offer curve from a vector of `n` power values, a vector of `n-1` marginal costs and single initial input. For example, the following code creates an incremental offer curve: + +```@repl market_bid_cost +using PowerSystems, Dates +proposed_offer_curve = + make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0) +``` + +Then a device with MarketBidCost can be directly instantiated using: + +```@repl market_bid_cost +using PowerSystems, Dates +bus = ACBus(1, "nodeE", "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing) + +generator = ThermalStandard(; + name = "Brighton", + available = true, + status = true, + bus = bus, + active_power = 6.0, + reactive_power = 1.50, + rating = 0.75, + prime_mover_type = PrimeMovers.ST, + fuel = ThermalFuels.COAL, + active_power_limits = (min = 0.0, max = 6.0), + reactive_power_limits = (min = -4.50, max = 4.50), + time_limits = (up = 0.015, down = 0.015), + ramp_limits = (up = 5.0, down = 3.0), + operation_cost = MarketBidCost(; + no_load_cost = 0.0, + start_up = (hot = 0.0, warm = 0.0, cold = 0.0), + shut_down = 0.0, + incremental_offer_curves = proposed_offer_curve, + ), + base_power = 100.0, +) +``` + +Similarly, a decremental offer curve can also be created directly using the same helper method: + +```@repl market_bid_cost +using PowerSystems, Dates +decremental_offer = + make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [30.0, 28.0, 26.0, 25.0], 50.0) +``` + +and can be added to a `MarketBidCost` using the field `decremental_offer_curves`. + +## Adding Time Series Energy bids to MarketBidCost + +### Step 1: Constructing device with MarketBidCost When using [`MarketBidCost`](@ref), the user can add the cost struct to the device specifying only certain elements, at this point the actual energy cost bids don't need to be populated/passed. @@ -43,19 +95,22 @@ generator = ThermalStandard(; ### Step 2: Creating the `TimeSeriesData` for the Market Bid The user is expected to pass the `TimeSeriesData` that holds the energy bid data which can be -of any type (i.e. `SingleTimeSeries` or `Deterministic`) and data can be `Array{Float64}`, -`Array{Tuple{Float64, Float64}}` or `Array{Array{Tuple{Float64,Float64}}`. If the data is -just floats then the cost in the optimization is seen as a constant variable cost, but if -data is a Tuple or `Array{Tuple}` then the model expects the tuples to be cost & power-point -pairs (cost in $/p.u-hr & power-point in p.u-hr), which is modeled same as TwoPartCost or -ThreePartCost. Code below shows an example of how to build a TimeSeriesData. +of any type (i.e. `SingleTimeSeries` or `Deterministic`) and data must be `PiecewiseStepData`. +This data type is created by specifying a vector of `n` powers, and `n-1` marginal costs. +The data must be specified in natural units, that is power in MW and marginal cost in $/MWh +or it will not be accepted when adding to the system. +Code below shows an example of how to build a Deterministic TimeSeries. ```@repl market_bid_cost +initial_time = Dates.DateTime("2020-01-01") +psd1 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [2.901, 5.8272, 8.941]) +psd2 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [3.001, 6.0072, 9.001]) data = Dict( - Dates.DateTime("2020-01-01") => [ - [(0.0, 0.05), (290.1, 0.0733), (582.72, 0.0967), (894.1, 0.120)], - [(0.0, 0.05), (300.1, 0.0733), (600.72, 0.0967), (900.1, 0.120)]], + initial_time => [ + psd1, + psd2, + ], ) time_series_data = Deterministic(; name = "variable_cost", @@ -64,20 +119,6 @@ time_series_data = Deterministic(; ) ``` -**NOTE:** Due to [limitations in DataStructures.jl](https://github.com/JuliaCollections/DataStructures.jl/issues/239), -in `PowerSystems.jl` when creating Forecasts or TimeSeries for your MarketBidCost, you need -to define your data as in the example or with a very explicit container. Otherwise, it won't -discern the types properly in the constructor and will return `SortedDict{Any,Any,Base.Order.ForwardOrdering}` which causes the constructor in `PowerSystems.jl` to fail. For instance, you need to define -the `Dict` with the data as follows: - -```julia -# Very verbose dict definition -data = Dict{DateTime, Array{Array{Tuple{Float64, Float64}, 1}, 1}}() -for t in range(initial_time_sys; step = Hour(1), length = window_count) - data[t] = MY_BID_DATA -end -``` - ### Step 3a: Adding Energy Bid TimeSeriesData to the device To add energy market bids time-series to the `MarketBidCost`, use `set_variable_cost!`. The @@ -86,12 +127,20 @@ arguments for `set_variable_cost!` are: - `sys::System`: PowerSystem System - `component::StaticInjection`: Static injection device - `time_series_data::TimeSeriesData`: TimeSeriesData + - `power_units::UnitSystem`: UnitSystem + +Currently, time series data only supports natural units for time series data, i.e. MW for power and $/MWh for marginal costs. ```@repl market_bid_cost sys = System(100.0, [bus], [generator]) -set_variable_cost!(sys, generator, time_series_data) +set_variable_cost!(sys, generator, time_series_data, UnitSystem.NATURAL_ITEMS) ``` +**Note:** `set_variable_cost!` add curves to the `incremental_offer_curves` in the MarketBidCost. +Similarly, `set_incremental_variable_cost!` can be used to add curves to the `incremental_offer_curves`. +On the other hand, `set_decremental_variable_cost!` must be used to decremental curves (usually for storage or demand). +The creation of the TimeSeriesData is similar to Step 2, using `PiecewiseStepData` + ### Step 3b: Adding Service Bid TimeSeriesData to the device Similar to adding energy market bids, for adding bids for ancillary services, use From ee432111bdc3113adb7caa9c8e3424e4fc0c0d73 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Mon, 30 Sep 2024 16:57:53 -0600 Subject: [PATCH 21/25] add additional setter --- src/models/cost_function_timeseries.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index 2a1c2709e4..2e83a81cd2 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -429,7 +429,7 @@ Set the incremental variable cost bid for a `StaticInjection` device with a `Mar CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and not accepted for time series data. -- `power_units::UnitSystem`: Units to be used for data. Must be NATURAL_UNITS for +- `power_units::UnitSystem`: Units to be used for data. Must be NATURAL_UNITS. """ function set_variable_cost!( sys::System, @@ -462,6 +462,16 @@ function set_variable_cost!( return end +function set_variable_cost!( + sys::System, + component::StaticInjection, + data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, +) + @warn "Variable Cost UnitSystem not specificied for $(get_name(component)). set_variable_cost! assumes data is in UnitSystem.NATURAL_UNITS" + set_variable_cost!(sys, component, data, UnitSystem.NATURAL_UNITS) + return +end + """ Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. From f77c9fb35c731feddf8af05d02e78d8bffafb272 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 1 Oct 2024 09:33:35 -0700 Subject: [PATCH 22/25] add deprecated constructor --- src/models/supplemental_constructors.jl | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl index c53a206f8a..85e658a307 100644 --- a/src/models/supplemental_constructors.jl +++ b/src/models/supplemental_constructors.jl @@ -192,3 +192,67 @@ function EnergyReservoirStorage( internal = internal, ) end + +""" +Deprecated method for TwoTerminalHVDCLine +""" +function TwoTerminalHVDCLine( + name, + available, + active_power_flow, + arc, + active_power_limits_from, + active_power_limits_to, + reactive_power_limits_from, + reactive_power_limits_to, + loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}, + services, + ext, + internal, +) + new_loss = LinearCurve(loss.l0, loss.l1) + TwoTerminalHVDCLine( + name, + available, + active_power_flow, + arc, + active_power_limits_from, + active_power_limits_to, + reactive_power_limits_from, + reactive_power_limits_to, + new_loss, + services, + ext, + internal, + ) +end + +function TwoTerminalHVDCLine( + name, + available, + active_power_flow, + arc, + active_power_limits_from, + active_power_limits_to, + reactive_power_limits_from, + reactive_power_limits_to, + loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}, + services = Device[], + ext = Dict{String, Any}(), +) + new_loss = LinearCurve(loss.l0, loss.l1) + TwoTerminalHVDCLine( + name, + available, + active_power_flow, + arc, + active_power_limits_from, + active_power_limits_to, + reactive_power_limits_from, + reactive_power_limits_to, + new_loss, + services, + ext, + InfrastructureSystemsInternal(), + ) +end From b0034c039af4f11901af147491b670a8f37a72d5 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 1 Oct 2024 10:26:59 -0700 Subject: [PATCH 23/25] add deprecated method for IPC --- src/models/supplemental_constructors.jl | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl index 85e658a307..09dabccd36 100644 --- a/src/models/supplemental_constructors.jl +++ b/src/models/supplemental_constructors.jl @@ -256,3 +256,30 @@ function TwoTerminalHVDCLine( InfrastructureSystemsInternal(), ) end + +""" +Deprecated method for Interconnecting Converter +""" +function InterconnectingConverter(; + name, + available, + bus, + dc_bus, + active_power, + rating, + active_power_limits, + base_power, +) + InterconnectingConverter( + name, + available, + bus, + dc_bus, + active_power, + rating, + active_power_limits, + 0.0, + 0.0, + base_power, + ) +end From db29513ef89081f0eeb1c5fda40ba5f1d9742ce1 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 1 Oct 2024 11:04:05 -0700 Subject: [PATCH 24/25] update IPC struct --- src/descriptors/power_system_structs.json | 26 +++++++++-------- .../generated/InterconnectingConverter.jl | 28 +++++++++---------- src/models/supplemental_constructors.jl | 27 ------------------ 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index dcfdc90704..8a32459c02 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -2590,18 +2590,6 @@ "data_type": "MinMax", "needs_conversion": true }, - { - "name": "dc_current", - "comment": "DC current (A) on the converter", - "null_value": "0.0", - "data_type": "Float64" - }, - { - "name": "max_dc_current", - "comment": "Maximum stable dc current limits (A)", - "null_value": "0.0", - "data_type": "Float64" - }, { "name": "base_power", "comment": "Base power of the converter in MVA", @@ -2613,6 +2601,20 @@ }, "validation_action": "warn" }, + { + "name": "dc_current", + "comment": "DC current (A) on the converter", + "null_value": "0.0", + "data_type": "Float64", + "default": "0.0" + }, + { + "name": "max_dc_current", + "comment": "Maximum stable dc current limits (A)", + "null_value": "0.0", + "data_type": "Float64", + "default": "Inf" + }, { "name": "loss_function", "comment": "Linear or quadratic loss function with respect to the converter current", diff --git a/src/models/generated/InterconnectingConverter.jl b/src/models/generated/InterconnectingConverter.jl index 36afd58618..bf0a333a9e 100644 --- a/src/models/generated/InterconnectingConverter.jl +++ b/src/models/generated/InterconnectingConverter.jl @@ -13,9 +13,9 @@ This file is auto-generated. Do not edit. active_power::Float64 rating::Float64 active_power_limits::MinMax + base_power::Float64 dc_current::Float64 max_dc_current::Float64 - base_power::Float64 loss_function::Union{LinearCurve, QuadraticCurve} services::Vector{Service} dynamic_injector::Union{Nothing, DynamicInjection} @@ -33,9 +33,9 @@ Interconnecting Power Converter (IPC) for transforming power from an ACBus to a - `active_power::Float64`: Active power (MW) on the DC side, validation range: `active_power_limits` - `rating::Float64`: Maximum output power rating of the converter (MVA), validation range: `(0, nothing)` - `active_power_limits::MinMax`: Minimum and maximum stable active power levels (MW) -- `dc_current::Float64`: DC current (A) on the converter -- `max_dc_current::Float64`: Maximum stable dc current limits (A) - `base_power::Float64`: Base power of the converter in MVA, validation range: `(0, nothing)` +- `dc_current::Float64`: (default: `0.0`) DC current (A) on the converter +- `max_dc_current::Float64`: (default: `Inf`) Maximum stable dc current limits (A) - `loss_function::Union{LinearCurve, QuadraticCurve}`: (default: `LinearCurve(0.0)`) Linear or quadratic loss function with respect to the converter current - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to - `dynamic_injector::Union{Nothing, DynamicInjection}`: (default: `nothing`) corresponding dynamic injection device @@ -57,12 +57,12 @@ mutable struct InterconnectingConverter <: StaticInjection rating::Float64 "Minimum and maximum stable active power levels (MW)" active_power_limits::MinMax + "Base power of the converter in MVA" + base_power::Float64 "DC current (A) on the converter" dc_current::Float64 "Maximum stable dc current limits (A)" max_dc_current::Float64 - "Base power of the converter in MVA" - base_power::Float64 "Linear or quadratic loss function with respect to the converter current" loss_function::Union{LinearCurve, QuadraticCurve} "Services that this device contributes to" @@ -75,12 +75,12 @@ mutable struct InterconnectingConverter <: StaticInjection internal::InfrastructureSystemsInternal end -function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) +function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=Inf, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) end -function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, dc_current, max_dc_current, base_power, loss_function, services, dynamic_injector, ext, internal, ) +function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=Inf, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, internal, ) end # Constructor for demo purposes; non-functional. @@ -93,9 +93,9 @@ function InterconnectingConverter(::Nothing) active_power=0.0, rating=0.0, active_power_limits=(min=0.0, max=0.0), + base_power=0.0, dc_current=0.0, max_dc_current=0.0, - base_power=0.0, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, @@ -117,12 +117,12 @@ get_active_power(value::InterconnectingConverter) = get_value(value, value.activ get_rating(value::InterconnectingConverter) = get_value(value, value.rating) """Get [`InterconnectingConverter`](@ref) `active_power_limits`.""" get_active_power_limits(value::InterconnectingConverter) = get_value(value, value.active_power_limits) +"""Get [`InterconnectingConverter`](@ref) `base_power`.""" +get_base_power(value::InterconnectingConverter) = value.base_power """Get [`InterconnectingConverter`](@ref) `dc_current`.""" get_dc_current(value::InterconnectingConverter) = value.dc_current """Get [`InterconnectingConverter`](@ref) `max_dc_current`.""" get_max_dc_current(value::InterconnectingConverter) = value.max_dc_current -"""Get [`InterconnectingConverter`](@ref) `base_power`.""" -get_base_power(value::InterconnectingConverter) = value.base_power """Get [`InterconnectingConverter`](@ref) `loss_function`.""" get_loss_function(value::InterconnectingConverter) = value.loss_function """Get [`InterconnectingConverter`](@ref) `services`.""" @@ -146,12 +146,12 @@ set_active_power!(value::InterconnectingConverter, val) = value.active_power = s set_rating!(value::InterconnectingConverter, val) = value.rating = set_value(value, val) """Set [`InterconnectingConverter`](@ref) `active_power_limits`.""" set_active_power_limits!(value::InterconnectingConverter, val) = value.active_power_limits = set_value(value, val) +"""Set [`InterconnectingConverter`](@ref) `base_power`.""" +set_base_power!(value::InterconnectingConverter, val) = value.base_power = val """Set [`InterconnectingConverter`](@ref) `dc_current`.""" set_dc_current!(value::InterconnectingConverter, val) = value.dc_current = val """Set [`InterconnectingConverter`](@ref) `max_dc_current`.""" set_max_dc_current!(value::InterconnectingConverter, val) = value.max_dc_current = val -"""Set [`InterconnectingConverter`](@ref) `base_power`.""" -set_base_power!(value::InterconnectingConverter, val) = value.base_power = val """Set [`InterconnectingConverter`](@ref) `loss_function`.""" set_loss_function!(value::InterconnectingConverter, val) = value.loss_function = val """Set [`InterconnectingConverter`](@ref) `services`.""" diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl index 09dabccd36..85e658a307 100644 --- a/src/models/supplemental_constructors.jl +++ b/src/models/supplemental_constructors.jl @@ -256,30 +256,3 @@ function TwoTerminalHVDCLine( InfrastructureSystemsInternal(), ) end - -""" -Deprecated method for Interconnecting Converter -""" -function InterconnectingConverter(; - name, - available, - bus, - dc_bus, - active_power, - rating, - active_power_limits, - base_power, -) - InterconnectingConverter( - name, - available, - bus, - dc_bus, - active_power, - rating, - active_power_limits, - 0.0, - 0.0, - base_power, - ) -end From 4eb1bea7e59c367526244bd2ed752fd029a36b39 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Tue, 1 Oct 2024 11:28:44 -0700 Subject: [PATCH 25/25] update Inf to 1e8 --- src/descriptors/power_system_structs.json | 2 +- src/models/generated/InterconnectingConverter.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index 8a32459c02..bcc7ca38f0 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -2613,7 +2613,7 @@ "comment": "Maximum stable dc current limits (A)", "null_value": "0.0", "data_type": "Float64", - "default": "Inf" + "default": "1e8" }, { "name": "loss_function", diff --git a/src/models/generated/InterconnectingConverter.jl b/src/models/generated/InterconnectingConverter.jl index bf0a333a9e..f8e00bd7ca 100644 --- a/src/models/generated/InterconnectingConverter.jl +++ b/src/models/generated/InterconnectingConverter.jl @@ -35,7 +35,7 @@ Interconnecting Power Converter (IPC) for transforming power from an ACBus to a - `active_power_limits::MinMax`: Minimum and maximum stable active power levels (MW) - `base_power::Float64`: Base power of the converter in MVA, validation range: `(0, nothing)` - `dc_current::Float64`: (default: `0.0`) DC current (A) on the converter -- `max_dc_current::Float64`: (default: `Inf`) Maximum stable dc current limits (A) +- `max_dc_current::Float64`: (default: `1e8`) Maximum stable dc current limits (A) - `loss_function::Union{LinearCurve, QuadraticCurve}`: (default: `LinearCurve(0.0)`) Linear or quadratic loss function with respect to the converter current - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to - `dynamic_injector::Union{Nothing, DynamicInjection}`: (default: `nothing`) corresponding dynamic injection device @@ -75,11 +75,11 @@ mutable struct InterconnectingConverter <: StaticInjection internal::InfrastructureSystemsInternal end -function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=Inf, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) +function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=1e8, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) end -function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=Inf, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) +function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=1e8, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, internal, ) end