Skip to content
3 changes: 3 additions & 0 deletions src/PowerSystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ export get_time_series_array
export get_time_series_resolutions
export supports_time_series
export supports_supplemental_attributes
export supports_active_power
export supports_reactive_power
export supports_voltage_control
export get_time_series_timestamps
export get_time_series_values
export get_time_series_counts
Expand Down
15 changes: 15 additions & 0 deletions src/models/static_models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ function get_services(device::Device)
return Vector{Service}()
end

"""
Return `true` if the device has active power as a controllable parameter.
"""
supports_active_power(::StaticInjection) = true

"""
Return `true` if the device has reactive power as a controllable parameter.
"""
supports_reactive_power(::StaticInjection) = true

"""
Return `true` if the device can control voltage at its connected bus.
"""
supports_voltage_control(::StaticInjection) = false

get_dynamic_injector(d::StaticInjection) = nothing

function get_frequency_droop(static_injector::StaticInjection)
Expand Down
32 changes: 32 additions & 0 deletions src/models/supplemental_accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,35 @@ end
function supports_services(::AreaInterchange)
return true
end

# supports_active_power overrides for types without controllable active power
supports_active_power(::SynchronousCondenser) = false
supports_active_power(::FACTSControlDevice) = false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This should also depend on the control mode of the FACTSControlDevice. if the control mode of the facts is NML, this should return true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 18b43f6. supports_active_power(::FACTSControlDevice) is now instance-based — returns true for NML (both Series and Shunt links operating), false for BYP (series bypassed, shunt acts as STATCOM for reactive only), OOS, and nothing.

supports_active_power(::FixedAdmittance) = false
supports_active_power(::SwitchedAdmittance) = false

# supports_reactive_power overrides for types without controllable reactive power
supports_reactive_power(::InterconnectingConverter) = false
supports_reactive_power(::FixedAdmittance) = false
supports_reactive_power(::SwitchedAdmittance) = false
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this device can control reactive power

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d1d6190. Removed the supports_reactive_power(::SwitchedAdmittance) = false override so it now falls through to the default true.


# FACTSControlDevice reactive power and voltage control depend on control_mode.
# control_mode is nothing for uninitialized devices (e.g. FACTSControlDevice(nothing)).
_facts_is_active(d::FACTSControlDevice) =
(mode = get_control_mode(d); !isnothing(mode) && mode != FACTSOperationModes.OOS)

supports_reactive_power(d::FACTSControlDevice) = _facts_is_active(d)

# supports_voltage_control overrides for types that can control voltage
supports_voltage_control(::Generator) = true
supports_voltage_control(::Source) = true
supports_voltage_control(::Storage) = true
supports_voltage_control(::StaticInjectionSubsystem) = true

supports_voltage_control(d::FACTSControlDevice) = _facts_is_active(d)

function supports_voltage_control(d::SynchronousCondenser)
bustype = get_bustype(get_bus(d))
return !isnothing(bustype) &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot the bus can't never be nothing change this check to be based on the bustype only

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 09b672a. Removed the !isnothing(bustype) guard — now checks only bustype ∈ (ACBusTypes.PV, ACBusTypes.REF, ACBusTypes.SLACK). Also removed the SynchronousCondenser(nothing) test case and added a REF bus test instead.

bustype ∈ (ACBusTypes.PV, ACBusTypes.REF, ACBusTypes.SLACK)
end
89 changes: 89 additions & 0 deletions test/test_devices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,92 @@ end
get_component(ShiftablePowerLoad, sys2, "ShiftableLoadBus4"),
).max == 0.10
end

@testset "Test static injection traits" begin
# supports_active_power
@test supports_active_power(ThermalStandard(nothing)) == true
@test supports_active_power(ThermalMultiStart(nothing)) == true
@test supports_active_power(RenewableDispatch(nothing)) == true
@test supports_active_power(RenewableNonDispatch(nothing)) == true
@test supports_active_power(HydroDispatch(nothing)) == true
@test supports_active_power(HydroTurbine(nothing)) == true
@test supports_active_power(HydroPumpTurbine(nothing)) == true
@test supports_active_power(Source(nothing)) == true
@test supports_active_power(InterconnectingConverter(nothing)) == true
@test supports_active_power(EnergyReservoirStorage(nothing)) == true
@test supports_active_power(PowerLoad(nothing)) == true
@test supports_active_power(StandardLoad(nothing)) == true
@test supports_active_power(ExponentialLoad(nothing)) == true
@test supports_active_power(InterruptiblePowerLoad(nothing)) == true
@test supports_active_power(ShiftablePowerLoad(nothing)) == true
@test supports_active_power(HybridSystem(nothing)) == true
@test supports_active_power(SynchronousCondenser(nothing)) == false
@test supports_active_power(FACTSControlDevice(nothing)) == false
@test supports_active_power(FixedAdmittance(nothing)) == false
@test supports_active_power(SwitchedAdmittance(nothing)) == false

# supports_reactive_power
@test supports_reactive_power(ThermalStandard(nothing)) == true
@test supports_reactive_power(RenewableDispatch(nothing)) == true
@test supports_reactive_power(Source(nothing)) == true
@test supports_reactive_power(SynchronousCondenser(nothing)) == true
@test supports_reactive_power(PowerLoad(nothing)) == true
@test supports_reactive_power(EnergyReservoirStorage(nothing)) == true
@test supports_reactive_power(HybridSystem(nothing)) == true
@test supports_reactive_power(InterconnectingConverter(nothing)) == false
@test supports_reactive_power(FixedAdmittance(nothing)) == false
@test supports_reactive_power(SwitchedAdmittance(nothing)) == false

# FACTSControlDevice reactive power depends on control_mode
@test supports_reactive_power(FACTSControlDevice(nothing)) == false
facts_nml = FACTSControlDevice(nothing)
set_control_mode!(facts_nml, FACTSOperationModes.NML)
@test supports_reactive_power(facts_nml) == true
facts_byp = FACTSControlDevice(nothing)
set_control_mode!(facts_byp, FACTSOperationModes.BYP)
@test supports_reactive_power(facts_byp) == true
facts_oos = FACTSControlDevice(nothing)
set_control_mode!(facts_oos, FACTSOperationModes.OOS)
@test supports_reactive_power(facts_oos) == false

# supports_voltage_control
@test supports_voltage_control(ThermalStandard(nothing)) == true
@test supports_voltage_control(ThermalMultiStart(nothing)) == true
@test supports_voltage_control(RenewableDispatch(nothing)) == true
@test supports_voltage_control(RenewableNonDispatch(nothing)) == true
@test supports_voltage_control(HydroDispatch(nothing)) == true
@test supports_voltage_control(Source(nothing)) == true
@test supports_voltage_control(EnergyReservoirStorage(nothing)) == true
@test supports_voltage_control(HybridSystem(nothing)) == true
@test supports_voltage_control(PowerLoad(nothing)) == false
@test supports_voltage_control(StandardLoad(nothing)) == false
@test supports_voltage_control(ExponentialLoad(nothing)) == false
@test supports_voltage_control(InterruptiblePowerLoad(nothing)) == false
@test supports_voltage_control(ShiftablePowerLoad(nothing)) == false
@test supports_voltage_control(InterconnectingConverter(nothing)) == false
@test supports_voltage_control(FixedAdmittance(nothing)) == false
@test supports_voltage_control(SwitchedAdmittance(nothing)) == false

# FACTSControlDevice voltage control depends on control_mode
@test supports_voltage_control(FACTSControlDevice(nothing)) == false
@test supports_voltage_control(facts_nml) == true
@test supports_voltage_control(facts_byp) == true
@test supports_voltage_control(facts_oos) == false

# SynchronousCondenser voltage control depends on bus type
@test supports_voltage_control(SynchronousCondenser(nothing)) == false
sc_pv = SynchronousCondenser(nothing)
sc_pv.bus = ACBus(;
number = 1, name = "pv_bus", available = true, bustype = ACBusTypes.PV,
angle = 0.0, magnitude = 1.0, voltage_limits = (min = 0.9, max = 1.1),
base_voltage = 230.0,
)
@test supports_voltage_control(sc_pv) == true
sc_pq = SynchronousCondenser(nothing)
sc_pq.bus = ACBus(;
number = 2, name = "pq_bus", available = true, bustype = ACBusTypes.PQ,
angle = 0.0, magnitude = 1.0, voltage_limits = (min = 0.9, max = 1.1),
base_voltage = 230.0,
)
@test supports_voltage_control(sc_pq) == false
end
Loading