Link hydro reservoirs to turbines

This how-to shows how to link HydroReservoir components to HydroTurbine and HydroPumpTurbine units. For background on the two supported topologies, see Hydro reservoir topology.

Penstock grouping via HydroPowerPlant is a separate supplemental-attribute workflow; see Group generators into plants § Group hydro units by penstock.

using PowerSystems
import PowerSystems as PSY

# Create a system
sys = System(100.0)
set_units_base_system!(sys, "NATURAL_UNITS")

# Create and add a bus
bus = ACBus(;
    number = 1,
    name = "bus1",
    available = true,
    bustype = ACBusTypes.PV,
    angle = 0.0,
    magnitude = 1.0,
    voltage_limits = (min = 0.9, max = 1.1),
    base_voltage = 230.0,
    area = nothing,
    load_zone = nothing,
)
add_component!(sys, bus)

# Create a HydroTurbine
turbine = HydroTurbine(;
    name = "Turbine1",
    available = true,
    bus = bus,
    active_power = 50.0,
    reactive_power = 10.0,
    rating = 100.0,
    base_power = 100.0,
    active_power_limits = (min = 10.0, max = 100.0),
    reactive_power_limits = (min = -50.0, max = 50.0),
    powerhouse_elevation = 500.0,  # meters above sea level
    efficiency = 0.9,
    conversion_factor = 1.0,
    outflow_limits = (min = 0.0, max = 1000.0),  # m³/s
    travel_time = 0.5,  # hours
)
add_component!(sys, turbine)

# Create a HydroReservoir
reservoir = HydroReservoir(;
    name = "Reservoir1",
    available = true,
    storage_level_limits = (min = 1000.0, max = 10000.0),  # m³
    initial_level = 0.8,  # 80% of max
    spillage_limits = (min = 0.0, max = 500.0),
    inflow = 100.0,  # m³/h
    outflow = 50.0,  # m³/h
    level_targets = 0.7,
    intake_elevation = 600.0,  # meters above sea level
    head_to_volume_factor = LinearCurve(1.0),
)
add_component!(sys, reservoir)

# Link the turbine to the reservoir as a downstream turbine
set_downstream_turbine!(reservoir, turbine)

# Verify the connection
@assert has_downstream_turbine(reservoir, turbine)
@assert length(get_connected_head_reservoirs(sys, turbine)) == 1
[ Info: Unit System changed to UnitSystem.NATURAL_UNITS = 2

Reusing the system from the previous example, create additional turbines and link all of them to the reservoir at once. Note that the second constructor omits the optional fields shown in the first example.

turbines = HydroUnit[turbine]  # start with the turbine from the previous example
for i in 2:5
    new_turbine = HydroTurbine(;
        name = "Turbine$i",
        available = true,
        bus = bus,
        active_power = 20.0,
        reactive_power = 5.0,
        rating = 50.0,
        base_power = 100.0,
        active_power_limits = (min = 5.0, max = 50.0),
        reactive_power_limits = nothing,
        powerhouse_elevation = 500.0 + i * 10.0,  # Different elevations
        efficiency = 0.85 + i * 0.02,
    )
    add_component!(sys, new_turbine)
    push!(turbines, new_turbine)
end

# Link all turbines at once (replaces any previously linked turbines)
set_downstream_turbines!(reservoir, turbines)

# Verify connections
@assert has_downstream_turbine(reservoir)
@assert length(get_downstream_turbines(reservoir)) == 5

Model pumped storage with head and tail reservoirs

The pumped-storage example below continues with the same system and bus.

# Create a HydroPumpTurbine
pump_turbine = HydroPumpTurbine(;
    name = "PumpTurbine1",
    available = true,
    bus = bus,
    active_power = 50.0,
    reactive_power = 10.0,
    rating = 200.0,
    active_power_limits = (min = 20.0, max = 200.0),  # Generation mode
    reactive_power_limits = (min = -100.0, max = 100.0),
    active_power_limits_pump = (min = 30.0, max = 180.0),  # Pumping mode
    outflow_limits = (min = 0.0, max = 500.0),
    powerhouse_elevation = 400.0,
    base_power = 100.0,
    ramp_limits = (up = 20.0, down = 20.0),
    time_limits = nothing,
    status = PSY.PumpHydroStatusModule.PumpHydroStatus.OFF,
    time_at_status = 0.0,
    efficiency = (turbine = 0.9, pump = 0.85),
    transition_time = (turbine = 0.25, pump = 0.25),  # hours
    minimum_time = (turbine = 1.0, pump = 1.0),  # hours
    conversion_factor = 1.0,
)
add_component!(sys, pump_turbine)

# Create head (upper) reservoir
head_reservoir = HydroReservoir(;
    name = "HeadReservoir",
    available = true,
    storage_level_limits = (min = 5000.0, max = 50000.0),
    initial_level = 0.6,
    spillage_limits = nothing,
    inflow = 200.0,
    outflow = 100.0,
    level_targets = 0.5,
    intake_elevation = 800.0,
    head_to_volume_factor = LinearCurve(1.0),
)
add_component!(sys, head_reservoir)

# Create tail (lower) reservoir
tail_reservoir = HydroReservoir(;
    name = "TailReservoir",
    available = true,
    storage_level_limits = (min = 3000.0, max = 30000.0),
    initial_level = 0.4,
    spillage_limits = nothing,
    inflow = 50.0,
    outflow = 100.0,
    level_targets = 0.5,
    intake_elevation = 200.0,
    head_to_volume_factor = LinearCurve(1.0),
)
add_component!(sys, tail_reservoir)

# Link reservoirs to pump-turbine
# Head reservoir feeds into the turbine (downstream)
set_downstream_turbine!(head_reservoir, pump_turbine)

# Tail reservoir receives flow from the turbine (upstream)
set_upstream_turbine!(tail_reservoir, pump_turbine)

# Verify connections
@assert has_downstream_turbine(head_reservoir, pump_turbine)
@assert has_upstream_turbine(tail_reservoir, pump_turbine)
@assert length(get_connected_head_reservoirs(sys, pump_turbine)) == 1
@assert length(get_connected_tail_reservoirs(sys, pump_turbine)) == 1

See also