Operations problems with PowerSimulations.jl
Originally Contributed by: Clayton Barrows
Introduction
PowerSimulations.jl supports the construction and solution of optimal power system scheduling problems (Operations Problems). Operations problems form the fundamental building blocks for sequential simulations. This example shows how to specify and customize a the mathematics that will be applied to the data with an ProblemTemplate
, build and execute an DecisionModel
, and access the results.
Load Packages
using PowerSystems
using PowerSimulations
using HydroPowerSimulations
using PowerSystemCaseBuilder
using HiGHS # solver
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 Documentation
sys = build_system(PSISystems, "modified_RTS_GMLC_DA_sys")
Property | Value |
---|---|
Name | |
Description | |
System Units Base | SYSTEM_BASE |
Base Power | 100.0 |
Base Frequency | 60.0 |
Num Components | 501 |
Type | Count |
---|---|
ACBus | 73 |
Arc | 109 |
Area | 3 |
FixedAdmittance | 3 |
HydroDispatch | 1 |
Line | 105 |
LoadZone | 21 |
PowerLoad | 51 |
RenewableDispatch | 29 |
RenewableNonDispatch | 31 |
TapTransformer | 15 |
ThermalStandard | 54 |
TwoTerminalHVDCLine | 1 |
VariableReserve{ReserveDown} | 1 |
VariableReserve{ReserveUp} | 4 |
owner_type | owner_category | time_series_type | time_series_category | initial_timestamp | resolution_ms | count |
---|---|---|---|---|---|---|
String | String | String | String | String | Int64 | Int64 |
Area | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 3 |
Area | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 3 |
FixedAdmittance | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 3 |
FixedAdmittance | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 3 |
HydroDispatch | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 2 |
HydroDispatch | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 2 |
PowerLoad | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 51 |
PowerLoad | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 51 |
RenewableDispatch | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 29 |
RenewableDispatch | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 29 |
RenewableNonDispatch | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 31 |
RenewableNonDispatch | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 31 |
VariableReserve | Component | DeterministicSingleTimeSeries | Forecast | 2020-01-01T00:00:00 | 3600000 | 5 |
VariableReserve | Component | SingleTimeSeries | StaticTimeSeries | 2020-01-01T00:00:00 | 3600000 | 5 |
Define a problem specification with an ProblemTemplate
You can create an empty template with:
template_uc = ProblemTemplate()
Network Model | CopperPlatePowerModel |
Slacks | false |
PTDF | false |
Duals | None |
Device Type | Formulation | Slacks |
---|
Now, you can add a DeviceModel
for each device type to create an assignment between PowerSystems device types and the subtypes of AbstractDeviceFormulation
. PowerSimulations has a variety of different AbstractDeviceFormulation
subtypes that can be applied to different PowerSystems device types, each dispatching to different methods for populating optimization problem objectives, variables, and constraints. Documentation on the formulation options for various devices can be found in the formulation library docs
Branch Formulations
Here is an example of relatively standard branch formulations. Other formulations allow for selective enforcement of transmission limits and greater control on transformer settings.
set_device_model!(template_uc, Line, StaticBranch)
set_device_model!(template_uc, Transformer2W, StaticBranch)
set_device_model!(template_uc, TapTransformer, StaticBranch)
Injection Device Formulations
Here we define template entries for all devices that inject or withdraw power on the network. For each device type, we can define a distinct AbstractDeviceFormulation
. In this case, we're defining a basic unit commitment model for thermal generators, curtailable renewable generators, and fixed dispatch (net-load reduction) formulations for HydroDispatch
and RenewableNonDispatch
devices.
set_device_model!(template_uc, ThermalStandard, ThermalStandardUnitCommitment)
set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch)
set_device_model!(template_uc, PowerLoad, StaticPowerLoad)
set_device_model!(template_uc, HydroDispatch, HydroDispatchRunOfRiver)
set_device_model!(template_uc, RenewableNonDispatch, FixedOutput)
Service Formulations
We have two VariableReserve
types, parameterized by their direction. So, similar to creating DeviceModel
s, we can create ServiceModel
s. The primary difference being that DeviceModel
objects define how constraints get created, while ServiceModel
objects define how constraints get modified.
set_service_model!(template_uc, VariableReserve{ReserveUp}, RangeReserve)
set_service_model!(template_uc, VariableReserve{ReserveDown}, RangeReserve)
Network Formulations
Finally, we can define the transmission network specification that we'd like to model. For simplicity, we'll choose a copper plate formulation. But there are dozens of specifications available through an integration with PowerModels.jl. Note that many formulations will require appropriate data and may be computationally intractable
set_network_model!(template_uc, NetworkModel(CopperPlatePowerModel))
DecisionModel
Now that we have a System
and an ProblemTemplate
, we can put the two together to create an DecisionModel
that we solve.
Optimizer
It's most convenient to define an optimizer instance upfront and pass it into the DecisionModel
constructor. For this example, we can use the free HiGHS solver with a relatively relaxed MIP gap (ratioGap
) setting to improve speed.
solver = optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.5)
MathOptInterface.OptimizerWithAttributes(HiGHS.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute, Any}[MathOptInterface.RawOptimizerAttribute("mip_rel_gap") => 0.5])
Build an DecisionModel
The construction of an DecisionModel
essentially applies an ProblemTemplate
to System
data to create a JuMP model.
problem = DecisionModel(template_uc, sys; optimizer = solver, horizon = Hour(24))
build!(problem; output_dir = mktempdir())
InfrastructureSystems.Optimization.ModelBuildStatusModule.ModelBuildStatus.BUILT = 0
The principal component of the DecisionModel
is the JuMP model. But you can serialize to a file using the following command:
serialize_optimization_model(problem, save_path)
Keep in mind that if the setting "storevariablenames" is set to False
then the file won't show the model's names.
Solve an DecisionModel
solve!(problem)
InfrastructureSystems.Simulation.RunStatusModule.RunStatus.SUCCESSFULLY_FINALIZED = 0
Results Inspection
PowerSimulations collects the DecisionModel
results into a OptimizationProblemResults
struct:
res = OptimizationProblemResults(problem)
Start: 2020-01-01T00:00:00
End: 2020-01-01T23:00:00
Resolution: 60 minutes
TimeDurationOn__ThermalStandard |
HydroEnergyOutput__HydroDispatch |
TimeDurationOff__ThermalStandard |
ProductionCostExpression__HydroDispatch |
FuelConsumptionExpression__ThermalStandard |
ProductionCostExpression__ThermalStandard |
ActivePowerBalance__System |
ProductionCostExpression__RenewableDispatch |
RequirementTimeSeriesParameter__VariableReserve__ReserveUp__Reg_Up |
ActivePowerTimeSeriesParameter__RenewableNonDispatch |
RequirementTimeSeriesParameter__VariableReserve__ReserveUp__Spin_Up_R1 |
ActivePowerTimeSeriesParameter__HydroDispatch |
RequirementTimeSeriesParameter__VariableReserve__ReserveUp__Spin_Up_R3 |
RequirementTimeSeriesParameter__VariableReserve__ReserveUp__Spin_Up_R2 |
RequirementTimeSeriesParameter__VariableReserve__ReserveDown__Reg_Down |
ActivePowerTimeSeriesParameter__PowerLoad |
ActivePowerTimeSeriesParameter__RenewableDispatch |
ActivePowerReserveVariable__VariableReserve__ReserveUp__Reg_Up |
ActivePowerVariable__ThermalStandard |
ActivePowerVariable__HydroDispatch |
StartVariable__ThermalStandard |
ActivePowerReserveVariable__VariableReserve__ReserveUp__Spin_Up_R3 |
ActivePowerVariable__RenewableDispatch |
StopVariable__ThermalStandard |
ActivePowerReserveVariable__VariableReserve__ReserveUp__Spin_Up_R1 |
ActivePowerReserveVariable__VariableReserve__ReserveDown__Reg_Down |
OnVariable__ThermalStandard |
ActivePowerReserveVariable__VariableReserve__ReserveUp__Spin_Up_R2 |
Optimizer Stats
The optimizer summary is included
get_optimizer_stats(res)
Row | detailed_stats | objective_value | termination_status | primal_status | dual_status | solver_solve_time | result_count | has_values | has_duals | objective_bound | relative_gap | dual_objective_value | solve_time | barrier_iterations | simplex_iterations | node_count | timed_solve_time | timed_calculate_aux_variables | timed_calculate_dual_variables | solve_bytes_alloc | sec_in_gc |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bool | Float64 | Int64 | Int64 | Int64 | Float64 | Int64 | Bool | Bool | Missing | Missing | Missing | Float64 | Missing | Missing | Missing | Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | false | 2.3571e6 | 1 | 1 | 0 | NaN | 1 | false | false | missing | missing | missing | 0.616616 | missing | missing | missing | 0.682337 | 0.458251 | 0.000435412 | 2.41018e7 | 0.0 |
Objective Function Value
get_objective_value(res)
2.357099085649291e6
Variable, Parameter, Auxillary Variable, Dual, and Expression Values
The solution value data frames for variables, parameters, auxillary variables, duals and expressions can be accessed using the read_
methods:
read_variables(res)
Dict{String, DataFrames.DataFrame} with 11 entries:
"ActivePowerReserveVaria… => 24×52 DataFrame…
"ActivePowerReserveVaria… => 24×52 DataFrame…
"StopVariable__ThermalSt… => 24×55 DataFrame…
"OnVariable__ThermalStan… => 24×55 DataFrame…
"ActivePowerVariable__Hy… => 24×2 DataFrame…
"ActivePowerReserveVaria… => 24×19 DataFrame…
"StartVariable__ThermalS… => 24×55 DataFrame…
"ActivePowerVariable__Th… => 24×55 DataFrame…
"ActivePowerVariable__Re… => 24×30 DataFrame…
"ActivePowerReserveVaria… => 24×18 DataFrame…
"ActivePowerReserveVaria… => 24×17 DataFrame…
Or, you can read a single parameter values for parameters that exist in the results.
list_parameter_names(res)
read_parameter(res, "ActivePowerTimeSeriesParameter__RenewableDispatch")
Row | DateTime | 122_WIND_1 | 324_PV_3 | 312_PV_1 | 102_PV_1 | 101_PV_1 | 324_PV_2 | 313_PV_2 | 104_PV_1 | 101_PV_2 | 309_WIND_1 | 310_PV_2 | 113_PV_1 | 317_WIND_1 | 314_PV_1 | 324_PV_1 | 103_PV_1 | 303_WIND_1 | 314_PV_2 | 102_PV_2 | 314_PV_3 | 320_PV_1 | 101_PV_3 | 319_PV_1 | 314_PV_4 | 310_PV_1 | 215_PV_1 | 313_PV_1 | 101_PV_4 | 119_PV_1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DateTime | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | 2020-01-01T00:00:00 | 713.2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 142.8 | 0.0 | 0.0 | 795.1 | 0.0 | 0.0 | 0.0 | 480.8 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 2020-01-01T01:00:00 | 712.8 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 139.1 | 0.0 | 0.0 | 794.4 | 0.0 | 0.0 | 0.0 | 634.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 | 2020-01-01T02:00:00 | 708.4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 145.3 | 0.0 | 0.0 | 773.6 | 0.0 | 0.0 | 0.0 | 487.3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
4 | 2020-01-01T03:00:00 | 710.7 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 144.8 | 0.0 | 0.0 | 767.3 | 0.0 | 0.0 | 0.0 | 432.7 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
5 | 2020-01-01T04:00:00 | 701.4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 137.1 | 0.0 | 0.0 | 752.2 | 0.0 | 0.0 | 0.0 | 407.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
6 | 2020-01-01T05:00:00 | 682.5 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 98.6 | 0.0 | 0.0 | 719.4 | 0.0 | 0.0 | 0.0 | 440.2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
7 | 2020-01-01T06:00:00 | 614.7 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 62.2 | 0.0 | 0.0 | 655.3 | 0.0 | 0.0 | 0.0 | 377.3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
8 | 2020-01-01T07:00:00 | 517.7 | 36.4 | 52.2 | 30.6 | 29.6 | 36.4 | 62.2 | 29.8 | 30.4 | 47.3 | 38.2 | 99.4 | 594.6 | 47.4 | 44.8 | 29.8 | 199.3 | 46.6 | 30.0 | 63.8 | 47.8 | 30.6 | 179.2 | 46.4 | 38.2 | 127.6 | 62.2 | 28.8 | 27.2 |
9 | 2020-01-01T08:00:00 | 426.6 | 63.4 | 97.4 | 36.8 | 34.8 | 63.4 | 96.0 | 35.0 | 37.0 | 48.9 | 61.4 | 126.2 | 579.1 | 61.2 | 65.2 | 58.4 | 110.6 | 67.0 | 36.4 | 100.0 | 61.4 | 37.4 | 248.0 | 67.0 | 61.4 | 164.6 | 95.6 | 35.4 | 56.6 |
10 | 2020-01-01T09:00:00 | 274.2 | 71.2 | 118.0 | 38.0 | 36.4 | 71.4 | 117.6 | 36.6 | 38.2 | 30.7 | 66.8 | 133.2 | 466.8 | 69.0 | 68.0 | 76.6 | 3.6 | 69.8 | 37.6 | 121.8 | 69.8 | 38.8 | 273.4 | 69.8 | 66.8 | 172.2 | 116.8 | 35.8 | 74.8 |
11 | 2020-01-01T10:00:00 | 93.0 | 72.2 | 132.0 | 37.8 | 36.4 | 72.2 | 130.8 | 36.6 | 38.0 | 27.4 | 70.2 | 134.2 | 301.4 | 70.2 | 70.2 | 89.8 | 2.4 | 72.2 | 37.4 | 129.0 | 70.4 | 38.6 | 277.8 | 72.2 | 70.4 | 173.2 | 128.4 | 37.4 | 88.2 |
12 | 2020-01-01T11:00:00 | 6.3 | 70.6 | 135.2 | 37.0 | 35.0 | 70.6 | 134.2 | 35.4 | 37.2 | 60.9 | 69.8 | 135.2 | 110.7 | 67.0 | 70.2 | 98.0 | 56.2 | 72.4 | 36.6 | 128.6 | 67.2 | 37.6 | 263.2 | 72.4 | 69.6 | 170.0 | 133.2 | 38.6 | 91.6 |
13 | 2020-01-01T12:00:00 | 3.8 | 67.4 | 131.0 | 36.2 | 35.4 | 67.4 | 129.6 | 35.6 | 36.4 | 20.4 | 67.8 | 133.0 | 78.9 | 67.2 | 68.2 | 94.0 | 91.5 | 70.2 | 35.8 | 128.2 | 67.4 | 37.0 | 267.4 | 70.2 | 67.6 | 169.0 | 128.8 | 37.8 | 90.6 |
14 | 2020-01-01T13:00:00 | 1.1 | 65.2 | 125.4 | 35.2 | 35.0 | 65.2 | 123.6 | 35.4 | 35.4 | 1.6 | 67.2 | 126.6 | 107.9 | 67.0 | 65.8 | 81.4 | 103.0 | 67.6 | 34.8 | 119.6 | 67.0 | 35.8 | 258.8 | 67.6 | 67.0 | 161.8 | 122.8 | 35.8 | 79.2 |
15 | 2020-01-01T14:00:00 | 0.0 | 60.2 | 109.6 | 31.0 | 31.0 | 60.4 | 108.0 | 31.4 | 31.2 | 0.0 | 63.2 | 110.2 | 22.3 | 59.4 | 62.6 | 59.4 | 39.7 | 64.2 | 30.8 | 107.0 | 59.4 | 31.6 | 227.8 | 64.2 | 62.8 | 143.2 | 106.8 | 31.4 | 59.0 |
16 | 2020-01-01T15:00:00 | 0.0 | 42.0 | 69.0 | 20.2 | 19.2 | 42.0 | 65.6 | 20.6 | 20.2 | 2.6 | 45.0 | 62.8 | 24.6 | 42.4 | 44.8 | 25.4 | 87.7 | 46.2 | 20.0 | 65.8 | 43.4 | 20.6 | 151.6 | 46.2 | 43.8 | 82.2 | 62.8 | 20.4 | 27.8 |
17 | 2020-01-01T16:00:00 | 0.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 37.9 | 0.0 | 0.0 | 10.8 | 0.0 | 0.0 | 0.0 | 92.3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
18 | 2020-01-01T17:00:00 | 276.3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 46.9 | 0.0 | 0.0 | 243.2 | 0.0 | 0.0 | 0.0 | 89.4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
19 | 2020-01-01T18:00:00 | 272.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 24.0 | 0.0 | 0.0 | 375.2 | 0.0 | 0.0 | 0.0 | 90.4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
20 | 2020-01-01T19:00:00 | 345.6 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 24.0 | 0.0 | 0.0 | 568.4 | 0.0 | 0.0 | 0.0 | 81.1 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
21 | 2020-01-01T20:00:00 | 411.7 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 27.4 | 0.0 | 0.0 | 636.1 | 0.0 | 0.0 | 0.0 | 172.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
22 | 2020-01-01T21:00:00 | 376.6 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 6.5 | 0.0 | 0.0 | 719.2 | 0.0 | 0.0 | 0.0 | 326.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
23 | 2020-01-01T22:00:00 | 561.3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.3 | 0.0 | 0.0 | 734.9 | 0.0 | 0.0 | 0.0 | 256.7 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
24 | 2020-01-01T23:00:00 | 568.4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.1 | 0.0 | 0.0 | 729.1 | 0.0 | 0.0 | 0.0 | 141.1 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
Plotting
Take a look at the plotting capabilities in PowerGraphics.jl