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(UUID("2e217f3e-9f58-4d10-b7d5-083e01c790d8"), 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 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.

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_ITEMS)ERROR: type DataType has no field 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 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> data = Dict(Dates.DateTime("2020-01-01") => [650.3, 750.0])Dict{Dates.DateTime, Vector{Float64}} with 1 entry: DateTime("2020-01-01T00:00:00") => [650.3, 750.0]
julia> time_series_data = Deterministic(; name = get_name(service), data = data, resolution = Dates.Hour(1), )Deterministic("example_reserve", DataStructures.SortedDict(Dates.DateTime("2020-01-01T00:00:00") => [650.3, 750.0]), Dates.Hour(1), nothing, InfrastructureSystems.InfrastructureSystemsInternal(UUID("da28e487-e22e-4c41-858a-8fe159a5b26b"), nothing, nothing, nothing))
julia> set_service_bid!(sys, generator, service, time_series_data)ERROR: MethodError: no method matching set_service_bid!(::System, ::ThermalStandard, ::VariableReserve{ReserveUp}, ::Deterministic) Closest candidates are: set_service_bid!(::System, ::StaticInjection, ::Service, ::TimeSeriesData, ::UnitSystem) @ PowerSystems ~/work/PowerSystems.jl/PowerSystems.jl/src/models/cost_function_timeseries.jl:676