HydroTurbine + Reservoir for WaterModel
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 Ipopt ## 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_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: trueNote 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(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 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 = 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 Expressions Results |
| ActivePowerBalance__System |
| TotalHydroFlowRateReservoirIncoming__HydroReservoir |
| ProductionCostExpression__ThermalStandard |
| TotalHydroFlowRateReservoirOutgoing__HydroReservoir |
| PTDFBranchFlow__Line |
| ActivePowerBalance__ACBus |
| TotalHydroFlowRateTurbineOutgoing__HydroTurbine |
| ProductionCostExpression__HydroTurbine |
| PowerSimulations Problem Parameters Results |
| InflowTimeSeriesParameter__HydroReservoir |
| ActivePowerTimeSeriesParameter__PowerLoad |
| OutflowTimeSeriesParameter__HydroReservoir |
| PowerSimulations Problem Variables Results |
| ActivePowerVariable__ThermalStandard |
| ActivePowerVariable__HydroTurbine |
| WaterSpillageVariable__HydroReservoir |
| HydroReservoirVolumeVariable__HydroReservoir |
| HydroReservoirHeadVariable__HydroReservoir |
| HydroWaterShortageVariable__HydroReservoir |
| 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)| DateTime | Water_Turbine |
|---|---|
| Dates.DateTime | Float64? |
| 2024-01-01T00:00:00 | 53.73864962415137 |
| 2024-01-01T01:00:00 | 53.670429716797585 |
| 2024-01-01T02:00:00 | 53.60220981011208 |
| 2024-01-01T03:00:00 | 53.53398989997154 |
| 2024-01-01T04:00:00 | 53.46576999320434 |
| 2024-01-01T05:00:00 | 53.397550089842596 |
| 2024-01-01T06:00:00 | 53.329330183050295 |
| 2024-01-01T07:00:00 | 53.26111027681968 |
| 2024-01-01T08:00:00 | 53.19289037002722 |
| 2024-01-01T09:00:00 | 53.1246704632248 |
| ⋮ | ⋮ |
or the water flowing through the turbine (in m³/s):
var = read_expression(
res,
"TotalHydroFlowRateTurbineOutgoing__HydroTurbine";
table_format = TableFormat.WIDE,
)| DateTime | Water_Turbine |
|---|---|
| Dates.DateTime | Float64? |
| 2024-01-01T00:00:00 | 30.00000029600105 |
| 2024-01-01T01:00:00 | 30.00000029566334 |
| 2024-01-01T02:00:00 | 30.000000295702183 |
| 2024-01-01T03:00:00 | 30.00000029380468 |
| 2024-01-01T04:00:00 | 30.000000293797765 |
| 2024-01-01T05:00:00 | 30.000000295710564 |
| 2024-01-01T06:00:00 | 30.000000295702506 |
| 2024-01-01T07:00:00 | 30.000000296015703 |
| 2024-01-01T08:00:00 | 30.000000296017863 |
| 2024-01-01T09:00:00 | 30.000000296019778 |
| ⋮ | ⋮ |
and the head level of the reservoir:
hydraulic_head = read_variable(
res,
"HydroReservoirHeadVariable__HydroReservoir";
table_format = TableFormat.WIDE,
)| DateTime | Water_Reservoir |
|---|---|
| Dates.DateTime | Float64? |
| 2024-01-01T00:00:00 | 499.71819603781233 |
| 2024-01-01T01:00:00 | 499.48639207560876 |
| 2024-01-01T02:00:00 | 499.2545881133847 |
| 2024-01-01T03:00:00 | 499.02278415116234 |
| 2024-01-01T04:00:00 | 498.790980188924 |
| 2024-01-01T05:00:00 | 498.55917622664646 |
| 2024-01-01T06:00:00 | 498.3273722643444 |
| 2024-01-01T07:00:00 | 498.09556830201257 |
| 2024-01-01T08:00:00 | 497.86376433965063 |
| 2024-01-01T09:00:00 | 497.63196037725635 |
| ⋮ | ⋮ |
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.