HydroTurbine + Reservoir for EnergyModel
To follow along, you can download this tutorial as a Julia script (.jl) or Jupyter notebook (.ipynb).
HydroPowerSimulations.jl is an extension library of PowerSimulations.jl for modeling hydro units. Users are encouraged to review the tutorial in PowerSimulations.jl on Running a Single-Step Problem before this tutorial.
Load packages
using PowerSystems
using PowerSimulations
using HydroPowerSimulations
using PowerSystemCaseBuilder
using HiGHS ## solverData
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 PowerSystemCaseBuilder.build_system.
sys = build_system(PSITestSystems, "c_sys5_hy_turbine_energy")| System | |
| Property | Value |
|---|---|
| Name | |
| Description | |
| System Units Base | SYSTEM_BASE |
| Base Power | 100.0 |
| Base Frequency | 60.0 |
| Num Components | 27 |
| Static Components | |
| Type | Count |
|---|---|
| ACBus | 5 |
| Arc | 6 |
| HydroReservoir | 1 |
| HydroTurbine | 1 |
| Line | 6 |
| PowerLoad | 3 |
| ThermalStandard | 5 |
| Forecast Summary | |||||||||
| owner_type | owner_category | name | time_series_type | initial_timestamp | resolution | count | horizon | interval | window_count |
|---|---|---|---|---|---|---|---|---|---|
| String | String | String | String | String | Dates.CompoundPeriod | Int64 | Dates.CompoundPeriod | Dates.CompoundPeriod | Int64 |
| HydroReservoir | Component | hydro_budget | Deterministic | 2024-01-01T00:00:00 | 1 hour | 1 | 1 day | 1 day | 2 |
| HydroReservoir | Component | inflow | Deterministic | 2024-01-01T00:00:00 | 1 hour | 1 | 1 day | 1 day | 2 |
| HydroReservoir | Component | storage_target | Deterministic | 2024-01-01T00:00:00 | 1 hour | 1 | 1 day | 1 day | 2 |
| HydroTurbine | Component | max_active_power | Deterministic | 2024-01-01T00:00:00 | 1 hour | 1 | 1 day | 1 day | 2 |
| PowerLoad | Component | max_active_power | Deterministic | 2024-01-01T00:00:00 | 1 hour | 3 | 1 day | 1 day | 2 |
With a single PowerSystems.HydroTurbine connected downstream to a PowerSystems.HydroReservoir:
hy = only(get_components(HydroTurbine, sys))
reservoir = only(get_components(HydroReservoir, sys))HydroReservoir: HydroEnergyReservoir__reservoir:
name: HydroEnergyReservoir__reservoir
available: true
storage_level_limits: (min = 0.0, max = 5000.0)
initial_level: 0.5
spillage_limits: nothing
inflow: 4.0
outflow: 0.0
level_targets: 1.0
intake_elevation: 0.0
head_to_volume_factor: InfrastructureSystems.LinearCurve(0.0, 0.0)
upstream_turbines: 0-element Vector{PowerSystems.HydroUnit}
downstream_turbines: 1-element Vector{PowerSystems.HydroUnit}
upstream_reservoirs: 0-element Vector{PowerSystems.Device}
operation_cost:
level_data_type: PowerSystems.ReservoirDataTypeModule.ReservoirDataType.ENERGY = 4
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: trueNote that the reservoir has a level_data_type of ENERGY, that implies its storage level limits data are in MWh. That means that its maximum capacity is 5000 MWh, and its initial energy capacity is $0.5 \cdot 5000 = 2500$ MWh.
Decision Model
Setting up the formulations based on PowerSimulations.jl:
template = ProblemTemplate(PTDFPowerModel)
set_device_model!(template, ThermalStandard, ThermalBasicDispatch)
set_device_model!(template, PowerLoad, StaticPowerLoad)
set_device_model!(template, Line, StaticBranch)but, now we also include the HydroTurbine using HydroTurbineEnergyDispatch:
set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch)and we need to use the energy model for the HydroReservoir via HydroEnergyModelReservoir. For this example we will ignore end targets of hydro budgets, but they can be included by setting up the attributes to true. It is not recommended to set both energy_target and hydro_budget to true simultaneously since it may create an infeasible problem:
reservoir_model = DeviceModel(
HydroReservoir,
HydroEnergyModelReservoir;
attributes = Dict{String, Any}(
"energy_target" => false,
"hydro_budget" => false,
),
)
set_device_model!(template, reservoir_model)With the template properly set-up, we construct, build and solve the optimization problem:
model = DecisionModel(template, sys; optimizer = HiGHS.Optimizer)
build!(model; output_dir = mktempdir())
solve!(model)InfrastructureSystems.Simulation.RunStatusModule.RunStatus.SUCCESSFULLY_FINALIZED = 0Exploring Results
Results can be explored using:
res = OptimizationProblemResults(model)Start: 2024-01-01T00:00:00
End: 2024-01-01T23:00:00
Resolution: 60 minutes
| PowerSimulations Problem Auxiliary variables Results |
| HydroEnergyOutput__HydroTurbine |
| PowerSimulations Problem Expressions Results |
| ActivePowerBalance__System |
| ProductionCostExpression__ThermalStandard |
| PTDFBranchFlow__Line |
| ActivePowerBalance__ACBus |
| ProductionCostExpression__HydroTurbine |
| PowerSimulations Problem Parameters Results |
| InflowTimeSeriesParameter__HydroReservoir |
| ActivePowerTimeSeriesParameter__PowerLoad |
| PowerSimulations Problem Variables Results |
| ActivePowerVariable__ThermalStandard |
| ActivePowerVariable__HydroTurbine |
| HydroEnergyShortageVariable__HydroReservoir |
| WaterSpillageVariable__HydroReservoir |
| EnergyVariable__HydroReservoir |
| HydroEnergySurplusVariable__HydroReservoir |
Use read_variable to read in the dispatch variable results for the hydro:
var =
read_variable(res, "ActivePowerVariable__HydroTurbine"; table_format = TableFormat.WIDE)| DateTime | HydroEnergyReservoir_turbine |
|---|---|
| Dates.DateTime | Float64? |
| 2024-01-01T00:00:00 | 235.66606480000002 |
| 2024-01-01T01:00:00 | 490.4349203000006 |
| 2024-01-01T02:00:00 | 70.22808049999992 |
| 2024-01-01T03:00:00 | 43.44897050000007 |
| 2024-01-01T04:00:00 | 649.1417221 |
| 2024-01-01T05:00:00 | 58.86620140000005 |
| 2024-01-01T06:00:00 | 88.01430319999994 |
| 2024-01-01T07:00:00 | 126.92800179999999 |
| 2024-01-01T08:00:00 | 176.18920650000013 |
| 2024-01-01T09:00:00 | 230.27531129999997 |
| ⋮ | ⋮ |
or the energy capacity of the reservoir
energy =
read_variable(res, "EnergyVariable__HydroReservoir"; table_format = TableFormat.WIDE)| DateTime | HydroEnergyReservoir__reservoir |
|---|---|
| Dates.DateTime | Float64? |
| 2024-01-01T00:00:00 | 2390.0539352 |
| 2024-01-01T01:00:00 | 2054.2926148999995 |
| 2024-01-01T02:00:00 | 2075.4973344 |
| 2024-01-01T03:00:00 | 2122.7191639 |
| 2024-01-01T04:00:00 | 1562.7242417999996 |
| 2024-01-01T05:00:00 | 1555.6700403999996 |
| 2024-01-01T06:00:00 | 1525.5629371999996 |
| 2024-01-01T07:00:00 | 1544.9273353999995 |
| 2024-01-01T08:00:00 | 1451.7893288999994 |
| 2024-01-01T09:00:00 | 1470.6680175999995 |
| ⋮ | ⋮ |
Note that since we have ignored the energy targets in the reservoir model, the optimal solution decides to deplete the reservoir.