HydroTurbine + Reservoir for WaterModel

To follow along, you can download this tutorial as a Julia script (.jl) or Jupyter notebook (.ipynb).

Note

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 Ipopt ## solver

Data

Note

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_head")
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 inflow Deterministic 2024-01-01T00:00:00 1 hour 1 1 day 1 day 2
HydroReservoir Component outflow 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: Water_Reservoir:
   name: Water_Reservoir
   available: true
   storage_level_limits: (min = 463.5, max = 555.5)
   initial_level: 0.9
   spillage_limits: nothing
   inflow: 1.0
   outflow: 0.0
   level_targets: 1.0
   intake_elevation: 463.3
   head_to_volume_factor: InfrastructureSystems.LinearCurve(302376.2, 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.HEAD = 3
   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: true

Note that the reservoir has a level_data_type of HEAD, that implies its storage level limits data are in meters (above the sea level) and refer to the hydraulic head levels. That means that its available capacity lies with its hydraulic head being within 463.5 and 555.5 meters, and its intake elevation is at 463.3 meters. In addition note that the elevation of the turbine is on 317.12 meters above the sea level.

Decision Model

Setting up the formulations based on PowerSimulations.jl:

template = ProblemTemplate(NetworkModel(CopperPlatePowerModel))
set_device_model!(template, ThermalStandard, ThermalBasicDispatch)
set_device_model!(template, PowerLoad, StaticPowerLoad)

but, now we also include the HydroTurbine using HydroTurbineBilinearDispatch:

set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch)

This is a nonlinear model that to compute its output power requires the bilinear term head times water flow. For that purpose the non-convex Ipopt solver will be used to solve this problem.

In addition, we need to use the water model for the HydroReservoir via HydroWaterModelReservoir.

set_device_model!(template, HydroReservoir, HydroWaterModelReservoir)

With the template properly set-up, we construct, build and solve the optimization problem:

model = DecisionModel(template, sys; optimizer = Ipopt.Optimizer)
build!(model; output_dir = mktempdir())
solve!(model)
InfrastructureSystems.Simulation.RunStatusModule.RunStatus.SUCCESSFULLY_FINALIZED = 0

Exploring 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 Expressions Results
ShutDownCostExpression__ThermalStandard
ProductionCostExpression__ThermalStandard
ActivePowerBalance__System
ProductionCostExpression__HydroTurbine
FuelCostExpression__ThermalStandard
TotalHydroFlowRateReservoirOutgoing__HydroReservoir
StartUpCostExpression__ThermalStandard
VOMCostExpression__ThermalStandard
FixedCostExpression__ThermalStandard
TotalHydroFlowRateTurbineOutgoing__HydroTurbine
TotalHydroFlowRateReservoirIncoming__HydroReservoir
PowerSimulations Problem Parameters Results
OutflowTimeSeriesParameter__HydroReservoir
InflowTimeSeriesParameter__HydroReservoir
ActivePowerTimeSeriesParameter__PowerLoad
PowerSimulations Problem Variables Results
HydroReservoirVolumeVariable__HydroReservoir
WaterSpillageVariable__HydroReservoir
HydroWaterShortageVariable__HydroReservoir
HydroReservoirHeadVariable__HydroReservoir
ActivePowerVariable__HydroTurbine
ActivePowerVariable__ThermalStandard
HydroWaterSurplusVariable__HydroReservoir

Use read_variable to read in the dispatch variable results for the hydro:

var =
    read_variable(res, "ActivePowerVariable__HydroTurbine"; table_format = TableFormat.WIDE)
14 rows omitted
DateTime Water_Turbine
Dates.DateTime Float64?
2024-01-01T00:00:00 53.738649624161425
2024-01-01T01:00:00 53.67042971680335
2024-01-01T02:00:00 53.60220981011062
2024-01-01T03:00:00 53.533989899981705
2024-01-01T04:00:00 53.465769993207104
2024-01-01T05:00:00 53.39755008981849
2024-01-01T06:00:00 53.32933018301802
2024-01-01T07:00:00 53.26111027677606
2024-01-01T08:00:00 53.19289036997412
2024-01-01T09:00:00 53.12467046316194

or the water flowing through the turbine (in m³/s):

var = read_expression(
    res,
    "TotalHydroFlowRateTurbineOutgoing__HydroTurbine";
    table_format = TableFormat.WIDE,
)
14 rows omitted
DateTime Water_Turbine
Dates.DateTime Float64?
2024-01-01T00:00:00 30.00000029600999
2024-01-01T01:00:00 30.000000295673452
2024-01-01T02:00:00 30.000000295712
2024-01-01T03:00:00 30.000000293825078
2024-01-01T04:00:00 30.00000029381822
2024-01-01T05:00:00 30.000000295720326
2024-01-01T06:00:00 30.000000295712333
2024-01-01T07:00:00 30.000000296024176
2024-01-01T08:00:00 30.000000296026407
2024-01-01T09:00:00 30.000000296028624

and the head level of the reservoir:

hydraulic_head = read_variable(
    res,
    "HydroReservoirHeadVariable__HydroReservoir";
    table_format = TableFormat.WIDE,
)
14 rows omitted
DateTime Water_Reservoir
Dates.DateTime Float64?
2024-01-01T00:00:00 499.7181960377921
2024-01-01T01:00:00 499.48639207556687
2024-01-01T02:00:00 499.2545881133201
2024-01-01T03:00:00 499.0227841510732
2024-01-01T04:00:00 498.7909801888095
2024-01-01T05:00:00 498.5591762265055
2024-01-01T06:00:00 498.32737226417527
2024-01-01T07:00:00 498.0955683018132
2024-01-01T08:00:00 497.8637643394188
2024-01-01T09:00:00 497.63196037698947

Note that since the water outflow limit of the turbine is limited on 30 m³/s, the optimal solution decides to flow as much water as possible producing power around 190 MW with that flow and hydraulic head.