Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renovate time series functionality for MarketBidCost #1103

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/PowerSystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export OperationalCost, MarketBidCost, LoadCost, StorageCost
export HydroGenerationCost, RenewableGenerationCost, ThermalGenerationCost
export get_function_data, get_initial_input, get_value_curve, get_power_units
export get_fuel_cost, set_fuel_cost!
export is_market_bid_curve, make_market_bid_curve

export Generator
export HydroGen
Expand Down
12 changes: 4 additions & 8 deletions src/models/cost_function_timeseries.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# MarketBidCost has two variable costs, here we mean the incremental one
get_generation_variable_cost(cost::MarketBidCost) = get_incremental_offer_curves(cost)
# get_generation_variable_cost(cost::OperationalCost) = get_variable_cost(cost)

function _validate_time_series_variable_cost(
time_series_data::IS.TimeSeriesData;
desired_type::Type = PiecewiseStepData,
Expand Down Expand Up @@ -39,7 +35,7 @@ function get_variable_cost(
end
data = IS.get_time_series_array(component, ts, start_time; len = len)
time_stamps = TimeSeries.timestamp(data)
return data
return TimeSeries.TimeArray(time_stamps, map(make_market_bid_curve, TimeSeries.values(data)))
end
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved

"""
Expand All @@ -53,11 +49,11 @@ Returns variable cost bids time-series data for MarketBidCost.
"""
function get_variable_cost(
device::StaticInjection,
cost::OperationalCost;
cost::MarketBidCost;
start_time::Union{Nothing, Dates.DateTime} = nothing,
len::Union{Nothing, Int} = nothing,
)
time_series_key = get_generation_variable_cost(cost)
time_series_key = get_incremental_offer_curves(cost)
if isnothing(time_series_key)
error(
"Cost component is empty, please use `set_variable_cost!` to add variable cost forecast.",
Expand Down Expand Up @@ -123,7 +119,7 @@ function get_services_bid(
start_time::Union{Nothing, Dates.DateTime} = nothing,
len::Union{Nothing, Int} = nothing,
)
variable_ts_key = get_generation_variable_cost(cost)
variable_ts_key = get_incremental_offer_curves(cost)
raw_data = get_time_series(
variable_ts_key.time_series_type,
device,
Expand Down
36 changes: 36 additions & 0 deletions src/models/cost_functions/MarketBidCost.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,39 @@ set_decremental_offer_curves!(value::MarketBidCost, val) =
"""Set [`MarketBidCost`](@ref) `ancillary_service_offers`."""
set_ancillary_service_offers!(value::MarketBidCost, val) =
value.ancillary_service_offers = val

# 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
function is_market_bid_curve(curve::ProductionVariableCost)
(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))))
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
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.
"""
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)
end

"""
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"))
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
cc = CostCurve(IncrementalCurve(data, NaN), power_units)
@assert is_market_bid_curve(cc)
return cc
end
9 changes: 4 additions & 5 deletions src/models/cost_functions/ValueCurves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,11 @@ end
get_initial_input(curve::Union{IncrementalCurve, AverageRateCurve}) = curve.initial_input

# BASE METHODS
Base.:(==)(a::InputOutputCurve, b::InputOutputCurve) =
(get_function_data(a) == get_function_data(b))
Base.:(==)(a::T, b::T) where T <: ValueCurve = IS.double_equals_from_fields(a, b)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved

Base.:(==)(a::T, b::T) where {T <: Union{IncrementalCurve, AverageRateCurve}} =
(get_function_data(a) == get_function_data(b)) &&
(get_initial_input(a) == get_initial_input(b))
Base.isequal(a::T, b::T) where T <: ValueCurve = IS.isequal_from_fields(a, b)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved

Base.hash(a::ValueCurve) = IS.hash_from_fields(a)

"Get an `InputOutputCurve` representing `f(x) = 0`"
Base.zero(::Union{InputOutputCurve, Type{InputOutputCurve}}) =
Expand Down
6 changes: 6 additions & 0 deletions src/models/cost_functions/variable_cost.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ get_initial_input(cost::ProductionVariableCost) = get_initial_input(get_value_cu
"Calculate the convexity of the underlying data"
is_convex(cost::ProductionVariableCost) = is_convex(get_value_curve(cost))

Base.:(==)(a::T, b::T) where T <: ProductionVariableCost = IS.double_equals_from_fields(a, b)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved

Base.isequal(a::T, b::T) where T <: ProductionVariableCost = IS.isequal_from_fields(a, b)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved

Base.hash(a::ProductionVariableCost) = IS.hash_from_fields(a)

"""
Direct representation of the variable operation cost of a power plant in currency. Composed
of a [`ValueCurve`][@ref] that may represent input-output, incremental, or average rate
Expand Down
41 changes: 23 additions & 18 deletions test/test_cost_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,19 @@ end
UnitSystem.DEVICE_BASE
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])
@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])
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
end

test_costs = Dict(
QuadraticFunctionData =>
repeat([QuadraticFunctionData(999.0, 2.0, 1.0)], 24),
PiecewiseLinearData =>
repeat(
[PiecewiseStepData([1.0, 2.0, 3.0], [4.0, 6.0])],
24,
),
CostCurve{QuadraticCurve} =>
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
repeat([CostCurve(QuadraticCurve(999.0, 2.0, 1.0))], 24),
CostCurve{PiecewiseIncrementalCurve} =>
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
repeat([make_market_bid_curve([2.0, 3.0], [4.0, 6.0])], 24),
Float64 =>
collect(11.0:34.0),
)
Expand All @@ -155,12 +160,12 @@ test_costs = Dict(
horizon = 24
service_data = Dict(initial_time => rand(horizon))
data_quadratic =
SortedDict(initial_time => test_costs[QuadraticFunctionData])
SortedDict(initial_time => test_costs[CostCurve{QuadraticCurve}])
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("variable_cost", data_quadratic, resolution)
forecast = IS.Deterministic("variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_quadratic)), resolution)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
@test_throws TypeError set_variable_cost!(sys, generator, forecast)
for s in generator.services
forecast = IS.Deterministic(get_name(s), service_data, resolution)
Expand All @@ -173,25 +178,25 @@ end
resolution = Dates.Hour(1)
name = "test"
horizon = 24
data_pwl = SortedDict(initial_time => test_costs[PiecewiseLinearData])
data_pwl = SortedDict(initial_time => test_costs[CostCurve{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 = IS.Deterministic("variable_cost", data_pwl, resolution)
forecast = IS.Deterministic("variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
set_variable_cost!(sys, generator, forecast)
for s in generator.services
forecast = IS.Deterministic(get_name(s), service_data, resolution)
forecast = IS.Deterministic(get_name(s), Dict(k => get_function_data.(v) for (k, v) in pairs(service_data)), resolution)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
set_service_bid!(sys, generator, s, forecast)
end

cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time)
@test first(TimeSeries.values(cost_forecast)) == first(data_pwl[initial_time])
@test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time]))

for s in generator.services
service_cost = get_services_bid(generator, market_bid, s; start_time = initial_time)
@test first(TimeSeries.values(service_cost)) == first(service_data[initial_time])
@test isequal(first(TimeSeries.values(service_cost)), first(service_data[initial_time]))
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
end
end

Expand All @@ -207,15 +212,15 @@ end
other_time = initial_time + resolution
name = "test"
horizon = 24
data_pwl = SortedDict(initial_time => test_costs[PiecewiseLinearData],
other_time => test_costs[PiecewiseLinearData])
data_pwl = SortedDict(initial_time => test_costs[CostCurve{PiecewiseIncrementalCurve}],
other_time => test_costs[CostCurve{PiecewiseIncrementalCurve}])
sys = System(100.0)
reserve = ReserveDemandCurve{ReserveUp}(nothing)
add_component!(sys, reserve)
forecast = IS.Deterministic("variable_cost", data_pwl, resolution)
forecast = IS.Deterministic("variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution)
GabrielKS marked this conversation as resolved.
Show resolved Hide resolved
set_variable_cost!(sys, reserve, forecast)
cost_forecast = get_variable_cost(reserve; start_time = initial_time)
@test first(TimeSeries.values(cost_forecast)) == first(data_pwl[initial_time])
@test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time]))
end

@testset "Test fuel cost (scalar and time series)" begin
Expand Down
Loading