Operation Problem with HydroPowerSimulations.jl using Market Bid Cost
HydroPowerSimulations.jl is an extension library of PowerSimulations.jl for modeling hydro units. Users are encouraged to review the single-step tutorial in PowerSimulations.jl before this tutorial.
Load packages
julia> using PowerSystemsjulia> using PowerSimulationsjulia> using HydroPowerSimulationsjulia> using PowerSystemCaseBuilderjulia> using HiGHS # solverjulia> using TimeSeriesjulia> using Dates
Data
PowerSystemCaseBuilder.jl is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function build_system. For more details visit PowerSystemCaseBuilder README
julia> sys = build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true)┌ Info: Building new system c_sys5_hy from raw data └ sys_descriptor.raw_data = "/home/runner/.julia/artifacts/edcb5940e84a802a86ad4f2223214d33121ac044/PowerSystemsTestData-4.0.2/psy_data/data_5bus_pu.jl" [ Info: Serialized time series data to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/6446c7fb15cee0aebc8ad32e3f600bcdd6f06aab49ede9c3c454114303f80311/c_sys5_hy_time_series_storage.h5. [ Info: Serialized System to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/6446c7fb15cee0aebc8ad32e3f600bcdd6f06aab49ede9c3c454114303f80311/c_sys5_hy.json [ Info: Serialized System metadata to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/6446c7fb15cee0aebc8ad32e3f600bcdd6f06aab49ede9c3c454114303f80311/c_sys5_hy_metadata.json System ┌───────────────────┬─────────────┐ │ Property │ Value │ ├───────────────────┼─────────────┤ │ Name │ │ │ Description │ │ │ System Units Base │ SYSTEM_BASE │ │ Base Power │ 100.0 │ │ Base Frequency │ 60.0 │ │ Num Components │ 26 │ └───────────────────┴─────────────┘ Static Components ┌─────────────────┬───────┐ │ Type │ Count │ ├─────────────────┼───────┤ │ ACBus │ 5 │ │ Arc │ 6 │ │ HydroDispatch │ 1 │ │ Line │ 6 │ │ PowerLoad │ 3 │ │ ThermalStandard │ 5 │ └─────────────────┴───────┘ StaticTimeSeries Summary ┌───────────────┬────────────────┬──────────────────┬──────────────────┬──────── │ owner_type │ owner_category │ name │ time_series_type │ initi ⋯ │ String │ String │ String │ String │ Strin ⋯ ├───────────────┼────────────────┼──────────────────┼──────────────────┼──────── │ HydroDispatch │ Component │ max_active_power │ SingleTimeSeries │ 2024- ⋯ │ PowerLoad │ Component │ max_active_power │ SingleTimeSeries │ 2024- ⋯ └───────────────┴────────────────┴──────────────────┴──────────────────┴──────── 4 columns omitted
Add a time-varying fuel cost for a thermal unit
We will modify the cheapest unit Brighton to have time-varying fuel cost. First we add a PowerSystems.FuelCurve:
julia> brighton = get_component(ThermalStandard, sys, "Brighton")ThermalStandard: Brighton: name: Brighton available: true status: true bus: ACBus: nodeE active_power: 6.0 reactive_power: 1.5 rating: 0.75 active_power_limits: (min = 0.0, max = 6.0) reactive_power_limits: (min = -4.5, max = 4.5) ramp_limits: (up = 0.11249999999999999, down = 0.11249999999999999) operation_cost: PowerSystems.ThermalGenerationCost composed of variable: InfrastructureSystems.CostCurve{InfrastructureSystems.LinearCurve} base_power: 100.0 time_limits: (up = 5.0, down = 3.0) must_run: false prime_mover_type: PowerSystems.PrimeMoversModule.PrimeMovers.ST = 20 fuel: PowerSystems.ThermalFuelsModule.ThermalFuels.COAL = 1 services: 0-element Vector{PowerSystems.Service} time_at_status: 10000.0 dynamic_injector: nothing ext: Dict{String, Any}() InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: InfrastructureSystems.UnitSystemModule.UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: falsejulia> old_thermal_cost = get_operation_cost(brighton)PowerSystems.ThermalGenerationCost: variable: CostCurve: value_curve: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 10.0 x + 0.0 power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2 vom_cost: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 fixed: 0.0 start_up: 1.5 shut_down: 0.75julia> new_fuel_curve = FuelCurve(; value_curve = LinearCurve(8.0), # Typical plant of 8 MMBTU/MWh heat rate. Piecewise heat rates can be used if needed. power_units = UnitSystem.NATURAL_UNITS, fuel_cost = 1.0, # $/MMBTU default fuel cost to start )FuelCurve: value_curve: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 8.0 x + 0.0 power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2 fuel_cost: 1.0 startup_fuel_offtake: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 vom_cost: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0julia> new_thermal_cost = ThermalGenerationCost(; variable = new_fuel_curve, fixed = old_thermal_cost.fixed, start_up = old_thermal_cost.start_up, shut_down = old_thermal_cost.shut_down, )PowerSystems.ThermalGenerationCost: variable: FuelCurve: value_curve: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 8.0 x + 0.0 power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2 fuel_cost: 1.0 startup_fuel_offtake: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 vom_cost: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 fixed: 0.0 start_up: 1.5 shut_down: 0.75julia> set_operation_cost!(brighton, new_thermal_cost)PowerSystems.ThermalGenerationCost: variable: FuelCurve: value_curve: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 8.0 x + 0.0 power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2 fuel_cost: 1.0 startup_fuel_offtake: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 vom_cost: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 fixed: 0.0 start_up: 1.5 shut_down: 0.75
Now we create a timeseries with random fuel prices. We first grab an existing timeseries:
julia> existing_ts = get_time_series_array( SingleTimeSeries, first(get_components(PowerLoad, sys)), "max_active_power", )48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00 ┌─────────────────────┬─────────┐ │ │ A │ ├─────────────────────┼─────────┤ │ 2024-01-01T00:00:00 │ 2.37819 │ │ 2024-01-01T01:00:00 │ 2.1696 │ │ 2024-01-01T02:00:00 │ 2.13286 │ │ 2024-01-01T03:00:00 │ 2.03302 │ │ 2024-01-01T04:00:00 │ 2.00475 │ │ 2024-01-01T05:00:00 │ 2.01501 │ │ 2024-01-01T06:00:00 │ 2.06283 │ │ 2024-01-01T07:00:00 │ 2.13546 │ │ ⋮ │ ⋮ │ │ 2024-01-02T17:00:00 │ 2.93332 │ │ 2024-01-02T18:00:00 │ 3.03353 │ │ 2024-01-02T19:00:00 │ 3.10266 │ │ 2024-01-02T20:00:00 │ 3.20308 │ │ 2024-01-02T21:00:00 │ 3.05404 │ │ 2024-01-02T22:00:00 │ 2.76541 │ │ 2024-01-02T23:00:00 │ 2.65748 │ └─────────────────────┴─────────┘ 33 rows omittedjulia> tstamps = timestamp(existing_ts)48-element Vector{Dates.DateTime}: 2024-01-01T00:00:00 2024-01-01T01:00:00 2024-01-01T02:00:00 2024-01-01T03:00:00 2024-01-01T04:00:00 2024-01-01T05:00:00 2024-01-01T06:00:00 2024-01-01T07:00:00 2024-01-01T08:00:00 2024-01-01T09:00:00 ⋮ 2024-01-02T15:00:00 2024-01-02T16:00:00 2024-01-02T17:00:00 2024-01-02T18:00:00 2024-01-02T19:00:00 2024-01-02T20:00:00 2024-01-02T21:00:00 2024-01-02T22:00:00 2024-01-02T23:00:00
And add the timeseries with the set_fuel_cost! method.
julia> fuel_cost_values = rand(length(tstamps)) .+ 1.0 # Random fuel cost between 1.0 and 2.0 $/MMBTU48-element Vector{Float64}: 1.3785953776354374 1.2107644960753656 1.9337920106658992 1.336390774754887 1.744292027840621 1.0451438385329488 1.2326450914952156 1.4569684803092529 1.8636071171491397 1.5341504408307534 ⋮ 1.8209072626779816 1.7806516529248064 1.6198708324713829 1.2525130887454519 1.2497307026440283 1.4667076142303146 1.4422511094309123 1.2878671209062524 1.4965707685292138julia> fuel_cost_tarray = TimeArray(tstamps, fuel_cost_values)48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00 ┌─────────────────────┬─────────┐ │ │ A │ ├─────────────────────┼─────────┤ │ 2024-01-01T00:00:00 │ 1.3786 │ │ 2024-01-01T01:00:00 │ 1.21076 │ │ 2024-01-01T02:00:00 │ 1.93379 │ │ 2024-01-01T03:00:00 │ 1.33639 │ │ 2024-01-01T04:00:00 │ 1.74429 │ │ 2024-01-01T05:00:00 │ 1.04514 │ │ 2024-01-01T06:00:00 │ 1.23265 │ │ 2024-01-01T07:00:00 │ 1.45697 │ │ ⋮ │ ⋮ │ │ 2024-01-02T17:00:00 │ 1.61987 │ │ 2024-01-02T18:00:00 │ 1.25251 │ │ 2024-01-02T19:00:00 │ 1.24973 │ │ 2024-01-02T20:00:00 │ 1.46671 │ │ 2024-01-02T21:00:00 │ 1.44225 │ │ 2024-01-02T22:00:00 │ 1.28787 │ │ 2024-01-02T23:00:00 │ 1.49657 │ └─────────────────────┴─────────┘ 33 rows omittedjulia> fuel_cost_ts = SingleTimeSeries(; name = "fuel_cost", data = fuel_cost_tarray)InfrastructureSystems.SingleTimeSeries("fuel_cost", 48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00, Dates.Millisecond(3600000), nothing, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("55fc02f5-0027-4851-9a36-ab642f3a661f"), nothing, nothing, nothing))julia> set_fuel_cost!(sys, brighton, fuel_cost_ts)FuelCurve: value_curve: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 8.0 x + 0.0 power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2 fuel_cost: InfrastructureSystems.StaticTimeSeriesKey(InfrastructureSystems.SingleTimeSeries, "fuel_cost", Dates.DateTime("2024-01-01T00:00:00"), Dates.Millisecond(3600000), 48, Dict{String, Any}()) startup_fuel_offtake: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0 vom_cost: InfrastructureSystems.LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
Add a market bid cost to the hydro unit
We again grab the timestamps from an existing time series:
julia> existing_ts = get_time_series_array( SingleTimeSeries, first(get_components(PowerLoad, sys)), "max_active_power", )48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00 ┌─────────────────────┬─────────┐ │ │ A │ ├─────────────────────┼─────────┤ │ 2024-01-01T00:00:00 │ 2.37819 │ │ 2024-01-01T01:00:00 │ 2.1696 │ │ 2024-01-01T02:00:00 │ 2.13286 │ │ 2024-01-01T03:00:00 │ 2.03302 │ │ 2024-01-01T04:00:00 │ 2.00475 │ │ 2024-01-01T05:00:00 │ 2.01501 │ │ 2024-01-01T06:00:00 │ 2.06283 │ │ 2024-01-01T07:00:00 │ 2.13546 │ │ ⋮ │ ⋮ │ │ 2024-01-02T17:00:00 │ 2.93332 │ │ 2024-01-02T18:00:00 │ 3.03353 │ │ 2024-01-02T19:00:00 │ 3.10266 │ │ 2024-01-02T20:00:00 │ 3.20308 │ │ 2024-01-02T21:00:00 │ 3.05404 │ │ 2024-01-02T22:00:00 │ 2.76541 │ │ 2024-01-02T23:00:00 │ 2.65748 │ └─────────────────────┴─────────┘ 33 rows omittedjulia> tstamps = timestamp(existing_ts)48-element Vector{Dates.DateTime}: 2024-01-01T00:00:00 2024-01-01T01:00:00 2024-01-01T02:00:00 2024-01-01T03:00:00 2024-01-01T04:00:00 2024-01-01T05:00:00 2024-01-01T06:00:00 2024-01-01T07:00:00 2024-01-01T08:00:00 2024-01-01T09:00:00 ⋮ 2024-01-02T15:00:00 2024-01-02T16:00:00 2024-01-02T17:00:00 2024-01-02T18:00:00 2024-01-02T19:00:00 2024-01-02T20:00:00 2024-01-02T21:00:00 2024-01-02T22:00:00 2024-01-02T23:00:00
And we add an empty market bid cost to the hydro:
julia> hy = get_component(HydroDispatch, sys, "HydroDispatch")HydroDispatch: HydroDispatch: name: HydroDispatch available: true bus: ACBus: nodeB active_power: 0.0 reactive_power: 0.0 rating: 6.0 prime_mover_type: PowerSystems.PrimeMoversModule.PrimeMovers.HY = 16 active_power_limits: (min = 0.0, max = 6.0) reactive_power_limits: (min = 0.0, max = 6.0) ramp_limits: nothing time_limits: nothing base_power: 100.0 status: false time_at_status: 10000.0 operation_cost: PowerSystems.HydroGenerationCost composed of variable: InfrastructureSystems.CostCurve{InfrastructureSystems.LinearCurve} services: 0-element Vector{PowerSystems.Service} dynamic_injector: nothing ext: Dict{String, Any}() InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: InfrastructureSystems.UnitSystemModule.UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: truejulia> # Create an empty market bid and set it hydro_cost = MarketBidCost(; no_load_cost = 0.0, start_up = (hot = 0.0, warm = 0.0, cold = 0.0), shut_down = 0.0, )PowerSystems.MarketBidCost: no_load_cost: 0.0 start_up: (hot = 0.0, warm = 0.0, cold = 0.0) shut_down: 0.0 incremental_offer_curves: nothing decremental_offer_curves: nothing incremental_initial_input: nothing decremental_initial_input: nothing ancillary_service_offers: PowerSystems.Service[]julia> set_operation_cost!(hy, hydro_cost)PowerSystems.MarketBidCost: no_load_cost: 0.0 start_up: (hot = 0.0, warm = 0.0, cold = 0.0) shut_down: 0.0 incremental_offer_curves: nothing decremental_offer_curves: nothing incremental_initial_input: nothing decremental_initial_input: nothing ancillary_service_offers: PowerSystems.Service[]
Now we create a time-varying piecewise linear bid cost:
julia> psd1 = PiecewiseStepData([0.0, 600.0], [5.0])InfrastructureSystems.PiecewiseStepData representing step (piecewise constant) function f(x) = 5.0 for x in [0.0, 600.0)julia> psd2 = PiecewiseStepData([0.0, 300.0, 600.0], [10.0, 20.0])InfrastructureSystems.PiecewiseStepData representing step (piecewise constant) function f(x) = 10.0 for x in [0.0, 300.0) 20.0 for x in [300.0, 600.0)julia> psd3 = PiecewiseStepData([0.0, 600.0], [500.0])InfrastructureSystems.PiecewiseStepData representing step (piecewise constant) function f(x) = 500.0 for x in [0.0, 600.0)julia> # Cheap the first 10 hours, moderate next 4 hours, expensive last 34 hours total_step_data = vcat([psd1 for x in 1:10], [psd2 for x in 1:4], [psd3 for x in 1:34])48-element Vector{InfrastructureSystems.PiecewiseStepData}: InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [5.0]) ⋮ InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0]) InfrastructureSystems.PiecewiseStepData([0.0, 600.0], [500.0])julia> mbid_tarray = TimeArray(tstamps, total_step_data)48×1 TimeSeries.TimeArray{InfrastructureSystems.PiecewiseStepData, 1, Dates.DateTime, Vector{InfrastructureSystems.PiecewiseStepData}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00 ┌─────────────────────┬──────────────────────────────────────────┐ │ │ A │ ├─────────────────────┼──────────────────────────────────────────┤ │ 2024-01-01T00:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T01:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T02:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T03:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T04:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T05:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T06:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ 2024-01-01T07:00:00 │ PiecewiseStepData([0.0, 600.0], [5.0]) │ │ ⋮ │ ⋮ │ │ 2024-01-02T17:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T18:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T19:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T20:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T21:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T22:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ │ 2024-01-02T23:00:00 │ PiecewiseStepData([0.0, 600.0], [500.0]) │ └─────────────────────┴──────────────────────────────────────────┘ 33 rows omittedjulia> ts_mbid = SingleTimeSeries(; name = "variable_cost", data = mbid_tarray)InfrastructureSystems.SingleTimeSeries("variable_cost", 48×1 TimeSeries.TimeArray{InfrastructureSystems.PiecewiseStepData, 1, Dates.DateTime, Vector{InfrastructureSystems.PiecewiseStepData}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00, Dates.Millisecond(3600000), nothing, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("d1fc9c94-1e67-4beb-85c9-5c2b29e19943"), nothing, nothing, nothing))julia> set_variable_cost!(sys, hy, ts_mbid, UnitSystem.NATURAL_UNITS)
It is also needed to create the initial input time series for market bid. That is the cost at 0 power at each time step. We will use zero for this example.
julia> zero_input = zeros(length(tstamps))48-element Vector{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ⋮ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0julia> zero_tarray = TimeArray(tstamps, zero_input)48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00 ┌─────────────────────┬─────┐ │ │ A │ ├─────────────────────┼─────┤ │ 2024-01-01T00:00:00 │ 0.0 │ │ 2024-01-01T01:00:00 │ 0.0 │ │ 2024-01-01T02:00:00 │ 0.0 │ │ 2024-01-01T03:00:00 │ 0.0 │ │ 2024-01-01T04:00:00 │ 0.0 │ │ 2024-01-01T05:00:00 │ 0.0 │ │ 2024-01-01T06:00:00 │ 0.0 │ │ 2024-01-01T07:00:00 │ 0.0 │ │ ⋮ │ ⋮ │ │ 2024-01-02T17:00:00 │ 0.0 │ │ 2024-01-02T18:00:00 │ 0.0 │ │ 2024-01-02T19:00:00 │ 0.0 │ │ 2024-01-02T20:00:00 │ 0.0 │ │ 2024-01-02T21:00:00 │ 0.0 │ │ 2024-01-02T22:00:00 │ 0.0 │ │ 2024-01-02T23:00:00 │ 0.0 │ └─────────────────────┴─────┘ 33 rows omittedjulia> ts_zero = SingleTimeSeries(; name = "variable_cost_initial_input", data = zero_tarray)InfrastructureSystems.SingleTimeSeries("variable_cost_initial_input", 48×1 TimeSeries.TimeArray{Float64, 1, Dates.DateTime, Vector{Float64}} 2024-01-01T00:00:00 to 2024-01-02T23:00:00, Dates.Millisecond(3600000), nothing, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("ef68b848-20b2-461d-9d84-a3af268118a2"), nothing, nothing, nothing))julia> set_incremental_initial_input!(sys, hy, ts_zero)InfrastructureSystems.StaticTimeSeriesKey(InfrastructureSystems.SingleTimeSeries, "variable_cost_initial_input", Dates.DateTime("2024-01-01T00:00:00"), Dates.Millisecond(3600000), 48, Dict{String, Any}())
Running the single-stage problem
We first transform the single time series to a 24 hour forecast
julia> transform_single_time_series!(sys, Hour(24), Hour(24))And create the necessary templates for the system:
julia> template = ProblemTemplate( NetworkModel( CopperPlatePowerModel; use_slacks = true, duals = [CopperPlateBalanceConstraint], ), )Network Model ┌────────────────────┬───────────────────────────────────────────────┐ │ Network Model │ PowerSimulations.CopperPlatePowerModel │ │ Slacks │ true │ │ PTDF │ false │ │ Duals │ PowerSimulations.CopperPlateBalanceConstraint │ │ HVDC Network Model │ None │ └────────────────────┴───────────────────────────────────────────────┘ Device Models ┌─────────────┬─────────────┬────────┐ │ Device Type │ Formulation │ Slacks │ └─────────────┴─────────────┴────────┘julia> set_device_model!(template, ThermalStandard, ThermalBasicUnitCommitment)julia> set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver)julia> set_device_model!(template, PowerLoad, StaticPowerLoad)
And then running the decision model:
julia> model = DecisionModel( template, sys; name = "UC_MBCost", optimizer = optimizer_with_attributes( HiGHS.Optimizer, ), store_variable_names = true, optimizer_solve_log_print = false, )[ Info: Overriding time_series_cache_size because time series is stored in memory Network Model ┌────────────────────┬───────────────────────────────────────────────┐ │ Network Model │ PowerSimulations.CopperPlatePowerModel │ │ Slacks │ true │ │ PTDF │ false │ │ Duals │ PowerSimulations.CopperPlateBalanceConstraint │ │ HVDC Network Model │ None │ └────────────────────┴───────────────────────────────────────────────┘ Device Models ┌──────────────────────────────┬─────────────────────────────────────────────┬── │ Device Type │ Formulation │ ⋯ ├──────────────────────────────┼─────────────────────────────────────────────┼── │ PowerSystems.ThermalStandard │ PowerSimulations.ThermalBasicUnitCommitment │ ⋯ │ PowerSystems.PowerLoad │ PowerSimulations.StaticPowerLoad │ ⋯ │ PowerSystems.HydroDispatch │ HydroDispatchRunOfRiver │ ⋯ └──────────────────────────────┴─────────────────────────────────────────────┴── 1 column omittedjulia> build!(model; output_dir = mktempdir())InfrastructureSystems.Optimization.ModelBuildStatusModule.ModelBuildStatus.BUILT = 0julia> solve!(model)InfrastructureSystems.Simulation.RunStatusModule.RunStatus.SUCCESSFULLY_FINALIZED = 0
And exploring results we confirm that the hydro is not dispatched when is more expensive, while the dual of the CopperPlate constraint showcase that the system become more expensive at those hours.
julia> res = OptimizationProblemResults(model)Start: 2024-01-01T00:00:00 End: 2024-01-01T23:00:00 Resolution: 60 minutes PowerSimulations Problem Auxiliary variables Results ┌──────────────────────────────────┐ │ TimeDurationOff__ThermalStandard │ │ TimeDurationOn__ThermalStandard │ │ HydroEnergyOutput__HydroDispatch │ └──────────────────────────────────┘ PowerSimulations Problem Expressions Results ┌────────────────────────────────────────────┐ │ ProductionCostExpression__ThermalStandard │ │ ActivePowerBalance__System │ │ ProductionCostExpression__HydroDispatch │ │ FuelConsumptionExpression__ThermalStandard │ └────────────────────────────────────────────┘ PowerSimulations Problem Duals Results ┌──────────────────────────────────────┐ │ CopperPlateBalanceConstraint__System │ └──────────────────────────────────────┘ PowerSimulations Problem Parameters Results ┌──────────────────────────────────────────────────────────────┐ │ ActivePowerTimeSeriesParameter__HydroDispatch │ │ IncrementalPiecewiseLinearSlopeParameter__HydroDispatch │ │ FuelCostParameter__ThermalStandard │ │ ActivePowerTimeSeriesParameter__PowerLoad │ │ IncrementalPiecewiseLinearBreakpointParameter__HydroDispatch │ └──────────────────────────────────────────────────────────────┘ PowerSimulations Problem Variables Results ┌──────────────────────────────────────┐ │ SystemBalanceSlackUp__System │ │ ActivePowerVariable__ThermalStandard │ │ StartVariable__ThermalStandard │ │ OnVariable__ThermalStandard │ │ SystemBalanceSlackDown__System │ │ StopVariable__ThermalStandard │ │ ActivePowerVariable__HydroDispatch │ └──────────────────────────────────────┘julia> hy_p = read_variable( res, "ActivePowerVariable__HydroDispatch"; table_format = TableFormat.WIDE, );julia> show(hy_p; allrows = true)24×2 DataFrame Row │ DateTime HydroDispatch │ DateTime Float64? ─────┼──────────────────────────────────── 1 │ 2024-01-01T00:00:00 188.58 2 │ 2024-01-01T01:00:00 232.01 3 │ 2024-01-01T02:00:00 137.149 4 │ 2024-01-01T03:00:00 136.006 5 │ 2024-01-01T04:00:00 133.72 6 │ 2024-01-01T05:00:00 77.718 7 │ 2024-01-01T06:00:00 86.8608 8 │ 2024-01-01T07:00:00 219.439 9 │ 2024-01-01T08:00:00 124.577 10 │ 2024-01-01T09:00:00 373.731 11 │ 2024-01-01T10:00:00 402.304 12 │ 2024-01-01T11:00:00 405.733 13 │ 2024-01-01T12:00:00 285.644 14 │ 2024-01-01T13:00:00 244.583 15 │ 2024-01-01T14:00:00 0.0 16 │ 2024-01-01T15:00:00 0.0 17 │ 2024-01-01T16:00:00 0.0 18 │ 2024-01-01T17:00:00 0.0 19 │ 2024-01-01T18:00:00 0.0 20 │ 2024-01-01T19:00:00 0.0 21 │ 2024-01-01T20:00:00 0.0 22 │ 2024-01-01T21:00:00 0.0 23 │ 2024-01-01T22:00:00 0.0 24 │ 2024-01-01T23:00:00 0.0
julia> dual_price = read_dual(res, "CopperPlateBalanceConstraint__System"; table_format = TableFormat.WIDE);julia> show(dual_price; allrows = true)24×2 DataFrame Row │ DateTime 4 │ DateTime Float64? ─────┼─────────────────────────────── 1 │ 2024-01-01T00:00:00 1500.0 2 │ 2024-01-01T01:00:00 968.612 3 │ 2024-01-01T02:00:00 1547.03 4 │ 2024-01-01T03:00:00 1069.11 5 │ 2024-01-01T04:00:00 1395.43 6 │ 2024-01-01T05:00:00 836.115 7 │ 2024-01-01T06:00:00 1400.0 8 │ 2024-01-01T07:00:00 1165.57 9 │ 2024-01-01T08:00:00 1500.0 10 │ 2024-01-01T09:00:00 1227.32 11 │ 2024-01-01T10:00:00 1013.29 12 │ 2024-01-01T11:00:00 1376.34 13 │ 2024-01-01T12:00:00 1000.0 14 │ 2024-01-01T13:00:00 1400.0 15 │ 2024-01-01T14:00:00 3000.0 16 │ 2024-01-01T15:00:00 3000.0 17 │ 2024-01-01T16:00:00 3000.0 18 │ 2024-01-01T17:00:00 3000.0 19 │ 2024-01-01T18:00:00 3000.0 20 │ 2024-01-01T19:00:00 3000.0 21 │ 2024-01-01T20:00:00 3000.0 22 │ 2024-01-01T21:00:00 3000.0 23 │ 2024-01-01T22:00:00 3000.0 24 │ 2024-01-01T23:00:00 1500.0