HydroTurbine + Reservoir for EnergyModel

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 HiGHS ## 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_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: true

Note 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(NetworkModel(CopperPlatePowerModel))
set_device_model!(template, ThermalStandard, ThermalBasicDispatch)
set_device_model!(template, PowerLoad, StaticPowerLoad)

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 = 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 Auxiliary variables Results
HydroEnergyOutput__HydroTurbine
PowerSimulations Problem Expressions Results
ShutDownCostExpression__ThermalStandard
ActivePowerBalance__System
ProductionCostExpression__HydroTurbine
FixedCostExpression__ThermalStandard
VOMCostExpression__ThermalStandard
ProductionCostExpression__ThermalStandard
FuelCostExpression__ThermalStandard
StartUpCostExpression__ThermalStandard
PowerSimulations Problem Parameters Results
ActivePowerTimeSeriesParameter__PowerLoad
InflowTimeSeriesParameter__HydroReservoir
PowerSimulations Problem Variables Results
HydroEnergySurplusVariable__HydroReservoir
WaterSpillageVariable__HydroReservoir
HydroEnergyShortageVariable__HydroReservoir
ActivePowerVariable__ThermalStandard
ActivePowerVariable__HydroTurbine
EnergyVariable__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 HydroEnergyReservoir_turbine
Dates.DateTime Float64?
2024-01-01T00:00:00 235.6660648000009
2024-01-01T01:00:00 92.11534019999999
2024-01-01T02:00:00 70.22808049999992
2024-01-01T03:00:00 43.44897050000007
2024-01-01T04:00:00 369.71978359999963
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.18920649999995
2024-01-01T09:00:00 230.27531129999997

or the energy capacity of the reservoir

energy =
    read_variable(res, "EnergyVariable__HydroReservoir"; table_format = TableFormat.WIDE)
14 rows omitted
DateTime HydroEnergyReservoir__reservoir
Dates.DateTime Float64?
2024-01-01T00:00:00 2390.053935199999
2024-01-01T01:00:00 2452.612194999999
2024-01-01T02:00:00 2473.8169144999993
2024-01-01T03:00:00 2521.038743999999
2024-01-01T04:00:00 2240.4657603999995
2024-01-01T05:00:00 2233.4115589999997
2024-01-01T06:00:00 2203.3044557999992
2024-01-01T07:00:00 2222.6688539999996
2024-01-01T08:00:00 2129.5308474999993
2024-01-01T09:00:00 2148.4095361999994

Note that since we have ignored the energy targets in the reservoir model, the optimal solution decides to deplete the reservoir.