HydroTurbine + Reservoir for EnergyModel

Note

HydroPowerSimulations.jl is an extension library of PowerSimulations.jl for modeling hydro units. Users are encouraged to review the single-step tutorial in PowerSimulations.jl before this tutorial.

Load packages

julia> using PowerSystems
julia> using PowerSimulations
julia> using HydroPowerSimulations
julia> using PowerSystemCaseBuilder
julia> 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 build_system. For more details visit PowerSystemCaseBuilder README

julia> sys = build_system(PSITestSystems, "c_sys5_hy_turbine_energy")┌ Info: Building new system c_sys5_hy_turbine_energy from raw data
  sys_descriptor.raw_data = "/home/runner/.julia/artifacts/edcb5940e84a802a86ad4f2223214d33121ac044/PowerSystemsTestData-4.0.2/psy_data/data_5bus_pu.jl"
[ Info: Serialized time series data to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/4e67b70ea6977dbe21c7731d72cdc1494adf072a7f3f08d921db740cf264ce79/c_sys5_hy_turbine_energy_time_series_storage.h5.
[ Info: Serialized System to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/4e67b70ea6977dbe21c7731d72cdc1494adf072a7f3f08d921db740cf264ce79/c_sys5_hy_turbine_energy.json
[ Info: Serialized System metadata to /home/runner/.julia/packages/PowerSystemCaseBuilder/zW01F/data/serialized_system/4e67b70ea6977dbe21c7731d72cdc1494adf072a7f3f08d921db740cf264ce79/c_sys5_hy_turbine_energy_metadata.json
              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  init ⋯
│ String          String          String            String            Stri ⋯
├────────────────┼────────────────┼──────────────────┼──────────────────┼───────
│ HydroReservoir │ Component      │ hydro_budget     │ Deterministic    │ 2024 ⋯
│ HydroReservoir │ Component      │ inflow           │ Deterministic    │ 2024 ⋯
│ HydroReservoir │ Component      │ storage_target   │ Deterministic    │ 2024 ⋯
│ HydroTurbine   │ Component      │ max_active_power │ Deterministic    │ 2024 ⋯
│ PowerLoad      │ Component      │ max_active_power │ Deterministic    │ 2024 ⋯
└────────────────┴────────────────┴──────────────────┴──────────────────┴───────
                                                               6 columns omitted

With a single PowerSystems.HydroTurbine connected downstream to a PowerSystems.HydroReservoir:

julia> hy = only(get_components(HydroTurbine, sys))HydroTurbine: HydroEnergyReservoir_turbine:
   name: HydroEnergyReservoir_turbine
   available: true
   bus: ACBus: nodeC
   active_power: 0.0
   reactive_power: 0.0
   rating: 7.0
   active_power_limits: (min = 0.0, max = 7.0)
   reactive_power_limits: (min = 0.0, max = 7.0)
   base_power: 100.0
   operation_cost: PowerSystems.HydroGenerationCost composed of variable: InfrastructureSystems.CostCurve{InfrastructureSystems.LinearCurve}
   powerhouse_elevation: 0.0
   ramp_limits: nothing
   time_limits: nothing
   outflow_limits: nothing
   efficiency: 1.0
   turbine_type: PowerSystems.HydroTurbineTypeModule.HydroTurbineType.UNKNOWN = 0
   conversion_factor: 1.0
   prime_mover_type: PowerSystems.PrimeMoversModule.PrimeMovers.HY = 16
   travel_time: nothing
   services: 0-element Vector{PowerSystems.Service}
   dynamic_injector: nothing
   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
julia> res = 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:

julia> template = ProblemTemplate(PTDFPowerModel)                     Network Model
┌────────────────────┬─────────────────────────────────┐
│ Network Model      │ PowerSimulations.PTDFPowerModel │
│ Slacks             │ false                           │
│ PTDF               │ false                           │
│ Duals              │ None                            │
│ HVDC Network Model │ None                            │
└────────────────────┴─────────────────────────────────┘

            Device Models
┌─────────────┬─────────────┬────────┐
│ Device Type  Formulation  Slacks │
└─────────────┴─────────────┴────────┘
julia> set_device_model!(template, ThermalStandard, ThermalBasicDispatch)
julia> set_device_model!(template, PowerLoad, StaticPowerLoad)
julia> set_device_model!(template, Line, StaticBranch)

but, now we also include the HydroTurbine using HydroTurbineEnergyDispatch:

julia> 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:

julia> reservoir_model = DeviceModel(
           HydroReservoir,
           HydroEnergyModelReservoir;
           attributes = Dict{String, Any}(
               "energy_target" => false,
               "hydro_budget" => false,
           ),
       )
                            Device Model
┌─────────────────────────────┬───────────────────────────┬────────┐
│ Device Type                  Formulation                Slacks │
├─────────────────────────────┼───────────────────────────┼────────┤
│ PowerSystems.HydroReservoir │ HydroEnergyModelReservoir │ false  │
└─────────────────────────────┴───────────────────────────┴────────┘

       Attributes
┌───────────────┬───────┐
│ Name           Value │
├───────────────┼───────┤
│ energy_target │ false │
│ hydro_budget  │ false │
└───────────────┴───────┘

                  Time Series Names
┌─────────────────────────────────┬──────────────────┐
│ Parameter Name                   Time Series Name │
├─────────────────────────────────┼──────────────────┤
│ EnergyBudgetTimeSeriesParameter │ hydro_budget     │
│ InflowTimeSeriesParameter       │ inflow           │
│ EnergyTargetTimeSeriesParameter │ storage_target   │
└─────────────────────────────────┴──────────────────┘

No FeedForwards Assigned
julia> set_device_model!(template, reservoir_model)

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

julia> model = DecisionModel(template, sys; optimizer = HiGHS.Optimizer)[ Info: Overriding time_series_cache_size because time series is stored in memory
                     Network Model
┌────────────────────┬─────────────────────────────────┐
│ Network Model      │ PowerSimulations.PTDFPowerModel │
│ Slacks             │ false                           │
│ PTDF               │ false                           │
│ Duals              │ None                            │
│ HVDC Network Model │ None                            │
└────────────────────┴─────────────────────────────────┘

                                 Device Models
┌──────────────────────────────┬───────────────────────────────────────┬────────
│ Device Type                   Formulation                            Slack ⋯
├──────────────────────────────┼───────────────────────────────────────┼────────
│ PowerSystems.ThermalStandard │ PowerSimulations.ThermalBasicDispatch │ false ⋯
│ PowerSystems.HydroReservoir  │ HydroEnergyModelReservoir             │ false ⋯
│ PowerSystems.PowerLoad       │ PowerSimulations.StaticPowerLoad      │ false ⋯
│ PowerSystems.HydroTurbine    │ HydroTurbineEnergyDispatch            │ false ⋯
└──────────────────────────────┴───────────────────────────────────────┴────────
                                                                1 column omitted

                        Branch Models
┌───────────────────┬───────────────────────────────┬────────┐
│ Branch Type        Formulation                    Slacks │
├───────────────────┼───────────────────────────────┼────────┤
│ PowerSystems.Line │ PowerSimulations.StaticBranch │ false  │
└───────────────────┴───────────────────────────────┴────────┘
julia> build!(model; output_dir = mktempdir())InfrastructureSystems.Optimization.ModelBuildStatusModule.ModelBuildStatus.BUILT = 0
julia> solve!(model)InfrastructureSystems.Simulation.RunStatusModule.RunStatus.SUCCESSFULLY_FINALIZED = 0

Exploring Results

Results can be explored using:

julia> 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
┌───────────────────────────────────────────┐
│ ProductionCostExpression__HydroTurbine    │
│ PTDFBranchFlow__Line                      │
│ ActivePowerBalance__ACBus                 │
│ ProductionCostExpression__ThermalStandard │
│ ActivePowerBalance__System                │
└───────────────────────────────────────────┘

 PowerSimulations Problem Parameters Results
┌───────────────────────────────────────────┐
│ ActivePowerTimeSeriesParameter__PowerLoad │
│ InflowTimeSeriesParameter__HydroReservoir │
└───────────────────────────────────────────┘

  PowerSimulations Problem Variables Results
┌─────────────────────────────────────────────┐
│ ActivePowerVariable__ThermalStandard        │
│ ActivePowerVariable__HydroTurbine           │
│ EnergyVariable__HydroReservoir              │
│ HydroEnergySurplusVariable__HydroReservoir  │
│ HydroEnergyShortageVariable__HydroReservoir │
│ WaterSpillageVariable__HydroReservoir       │
└─────────────────────────────────────────────┘

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

julia> var =
           read_variable(res, "ActivePowerVariable__HydroTurbine"; table_format = TableFormat.WIDE)24×2 DataFrame
 Row  DateTime             HydroEnergyReservoir_turbine 
      DateTime             Union{Missing, Float64}      
─────┼───────────────────────────────────────────────────
   1 │ 2024-01-01T00:00:00                      235.666
   2 │ 2024-01-01T01:00:00                      490.435
   3 │ 2024-01-01T02:00:00                       70.2281
   4 │ 2024-01-01T03:00:00                       43.449
   5 │ 2024-01-01T04:00:00                      649.142
   6 │ 2024-01-01T05:00:00                       58.8662
   7 │ 2024-01-01T06:00:00                       88.0143
   8 │ 2024-01-01T07:00:00                      126.928
  ⋮  │          ⋮                        ⋮
  18 │ 2024-01-01T17:00:00                      330.088
  19 │ 2024-01-01T18:00:00                      393.03
  20 │ 2024-01-01T19:00:00                      387.851
  21 │ 2024-01-01T20:00:00                      355.0
  22 │ 2024-01-01T21:00:00                      301.586
  23 │ 2024-01-01T22:00:00                      256.825
  24 │ 2024-01-01T23:00:00                      185.532
                                           9 rows omitted

or the energy capacity of the reservoir

julia> energy =
           read_variable(res, "EnergyVariable__HydroReservoir"; table_format = TableFormat.WIDE)24×2 DataFrame
 Row  DateTime             HydroEnergyReservoir__reservoir 
      DateTime             Union{Missing, Float64}         
─────┼──────────────────────────────────────────────────────
   1 │ 2024-01-01T00:00:00                        2390.05
   2 │ 2024-01-01T01:00:00                        2054.29
   3 │ 2024-01-01T02:00:00                        2075.5
   4 │ 2024-01-01T03:00:00                        2122.72
   5 │ 2024-01-01T04:00:00                        1562.72
   6 │ 2024-01-01T05:00:00                        1555.67
   7 │ 2024-01-01T06:00:00                        1525.56
   8 │ 2024-01-01T07:00:00                        1544.93
  ⋮  │          ⋮                          ⋮
  18 │ 2024-01-01T17:00:00                         350.611
  19 │ 2024-01-01T18:00:00                         209.783
  20 │ 2024-01-01T19:00:00                         114.517
  21 │ 2024-01-01T20:00:00                          70.3882
  22 │ 2024-01-01T21:00:00                          53.7675
  23 │ 2024-01-01T22:00:00                         109.338
  24 │ 2024-01-01T23:00:00                           0.0
                                              9 rows omitted

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