HydroTurbine + Reservoir for EnergyModel
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 PowerSystemsjulia> using PowerSimulationsjulia> using HydroPowerSimulationsjulia> using PowerSystemCaseBuilderjulia> using HiGHS # solver
Data
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 Assignedjulia> 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 = 0julia> 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.