Add a Market Bid
A MarketBidCost
is an OperationalCost
data structure that allows the user to run a production 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 a Single Incremental Energy bids to 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:
julia> using PowerSystems, Dates
julia> 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)
CostCurve: value_curve: PiecewiseIncrementalCurve where initial value is 10.0, derivative function f is: f(x) = 25.0 for x in [0.0, 100.0) 26.0 for x in [100.0, 105.0) 28.0 for x in [105.0, 120.0) 30.0 for x in [120.0, 130.0) power_units: UnitSystem.NATURAL_UNITS = 2 vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
Then a device with MarketBidCost can be directly instantiated using:
julia> using PowerSystems, Dates
julia> bus = ACBus(1, "nodeE", "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing)
ACBus: nodeE: number: 1 name: nodeE bustype: ACBusTypes.REF = 3 angle: 0.0 magnitude: 1.0 voltage_limits: (min = 0.9, max = 1.05) base_voltage: 230.0 area: nothing load_zone: nothing ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
julia> 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, )
ThermalStandard: Brighton: name: Brighton available: true status: true bus: ACBus: nodeE active_power: 600.0 reactive_power: 150.0 rating: 75.0 active_power_limits: (min = 0.0, max = 600.0) reactive_power_limits: (min = -450.0, max = 450.0) ramp_limits: (up = 500.0, down = 300.0) operation_cost: MarketBidCost composed of incremental_offer_curves: CostCurve{PiecewiseIncrementalCurve} base_power: 100.0 time_limits: (up = 0.015, down = 0.015) must_run: false prime_mover_type: PrimeMovers.ST = 20 fuel: ThermalFuels.COAL = 1 services: 0-element Vector{Service} time_at_status: 10000.0 dynamic_injector: nothing ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
Similarly, a decremental offer curve can also be created directly using the same helper method:
julia> using PowerSystems, Dates
julia> 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)
CostCurve: value_curve: PiecewiseIncrementalCurve where initial value is 50.0, derivative function f is: f(x) = 30.0 for x in [0.0, 100.0) 28.0 for x in [100.0, 105.0) 26.0 for x in [105.0, 120.0) 25.0 for x in [120.0, 130.0) power_units: UnitSystem.NATURAL_UNITS = 2 vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.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
, 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.
The code below shows an example how we can create a thermal device with MarketBidCost.
julia> using PowerSystems, Dates
julia> bus = ACBus(1, "nodeE", "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing)
ACBus: nodeE: number: 1 name: nodeE bustype: ACBusTypes.REF = 3 angle: 0.0 magnitude: 1.0 voltage_limits: (min = 0.9, max = 1.05) base_voltage: 230.0 area: nothing load_zone: nothing ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
julia> 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, ), base_power = 100.0, )
ThermalStandard: Brighton: name: Brighton available: true status: true bus: ACBus: nodeE active_power: 600.0 reactive_power: 150.0 rating: 75.0 active_power_limits: (min = 0.0, max = 600.0) reactive_power_limits: (min = -450.0, max = 450.0) ramp_limits: (up = 500.0, down = 300.0) operation_cost: base_power: 100.0 time_limits: (up = 0.015, down = 0.015) must_run: false prime_mover_type: PrimeMovers.ST = 20 fuel: ThermalFuels.COAL = 1 services: 0-element Vector{Service} time_at_status: 10000.0 dynamic_injector: nothing ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
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 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.
julia> initial_time = Dates.DateTime("2020-01-01")
2020-01-01T00:00:00
julia> psd1 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [2.901, 5.8272, 8.941])
PiecewiseStepData representing step (piecewise constant) function f(x) = 2.901 for x in [5.0, 7.33) 5.8272 for x in [7.33, 9.67) 8.941 for x in [9.67, 12.0)
julia> psd2 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [3.001, 6.0072, 9.001])
PiecewiseStepData representing step (piecewise constant) function f(x) = 3.001 for x in [5.0, 7.33) 6.0072 for x in [7.33, 9.67) 9.001 for x in [9.67, 12.0)
julia> data = Dict( initial_time => [ psd1, psd2, ], )
Dict{Dates.DateTime, Vector{PiecewiseStepData}} with 1 entry: DateTime("2020-01-01T00:00:00") => [PiecewiseStepData([5.0, 7.33, 9.67, 12.0]…
julia> time_series_data = Deterministic(; name = "variable_cost", data = data, resolution = Dates.Hour(1), )
Deterministic("variable_cost", DataStructures.SortedDict{Dates.DateTime, Vector{PiecewiseStepData}, Base.Order.ForwardOrdering}(Dates.DateTime("2020-01-01T00:00:00") => [PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [2.901, 5.8272, 8.941]), PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [3.001, 6.0072, 9.001])]), Dates.Hour(1), nothing, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("fdfc3d30-5d5a-475d-a124-8206366954bc"), nothing, nothing, nothing))
Step 3a: Adding Energy Bid TimeSeriesData to the device
To add energy market bids time-series to the MarketBidCost
, use set_variable_cost!
. The arguments for set_variable_cost!
are:
sys::System
: PowerSystem Systemcomponent::StaticInjection
: Static injection devicetime_series_data::TimeSeriesData
: TimeSeriesDatapower_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.
julia> sys = System(100.0, [bus], [generator])
┌ Warning: There are no ElectricLoad Components in the System └ @ PowerSystems ~/work/PowerSystems.jl/PowerSystems.jl/src/utils/IO/system_checks.jl:59 System ┌───────────────────┬─────────────┐ │ Property │ Value │ ├───────────────────┼─────────────┤ │ Name │ │ │ Description │ │ │ System Units Base │ SYSTEM_BASE │ │ Base Power │ 100.0 │ │ Base Frequency │ 60.0 │ │ Num Components │ 2 │ └───────────────────┴─────────────┘ Static Components ┌─────────────────┬───────┐ │ Type │ Count │ ├─────────────────┼───────┤ │ ACBus │ 1 │ │ ThermalStandard │ 1 │ └─────────────────┴───────┘
julia> set_variable_cost!(sys, generator, time_series_data, UnitSystem.NATURAL_UNITS)
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 set_service_bid!
.
julia> service = VariableReserve{ReserveUp}("example_reserve", true, 0.6, 2.0)
VariableReserve: example_reserve: name: example_reserve available: true time_frame: 0.6 requirement: 2.0 sustained_time: 3600.0 max_output_fraction: 1.0 max_participation_factor: 1.0 deployed_fraction: 0.0 ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
julia> add_service!(sys, service, get_component(ThermalStandard, sys, "Brighton"))
julia> psd3 = PiecewiseStepData([0.0, 10.0], [650.3])
PiecewiseStepData representing step (piecewise constant) function f(x) = 650.3 for x in [0.0, 10.0)
julia> psd4 = PiecewiseStepData([0.0, 10.0], [750.0])
PiecewiseStepData representing step (piecewise constant) function f(x) = 750.0 for x in [0.0, 10.0)
julia> data = Dict(Dates.DateTime("2020-01-01") => [psd3, psd4])
Dict{Dates.DateTime, Vector{PiecewiseStepData}} with 1 entry: DateTime("2020-01-01T00:00:00") => [PiecewiseStepData([0.0, 10.0], [650.3]), …
julia> time_series_data = Deterministic(; name = get_name(service), data = data, resolution = Dates.Hour(1), )
Deterministic("example_reserve", DataStructures.SortedDict{Dates.DateTime, Vector{PiecewiseStepData}, Base.Order.ForwardOrdering}(Dates.DateTime("2020-01-01T00:00:00") => [PiecewiseStepData([0.0, 10.0], [650.3]), PiecewiseStepData([0.0, 10.0], [750.0])]), Dates.Hour(1), nothing, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("e9292665-43b0-463c-9e91-e1d2c62e38aa"), nothing, nothing, nothing))
julia> set_service_bid!(sys, generator, service, time_series_data, UnitSystem.NATURAL_UNITS)