Use Subsystems

For certain applications, such as those that employ dispatch coordination methods or decomposition approaches, it is useful to split components into subsystems based on user-defined criteria. The System provides subsystem containers for this purpose. Each subsystem is defined by a name and can hold references to any number of components. For background on the System container, see System.

Create subsystems and add components

Load a System, then call add_subsystem! to register named subsystems:

using PowerSystems;
using PowerSystemCaseBuilder;
sys = build_system(PSISystems, "c_sys5_pjm")
add_subsystem!(sys, "1")
add_subsystem!(sys, "2")
[ Info: Loaded time series from storage file existing=/home/runner/.julia/packages/PowerSystemCaseBuilder/3SVVU/data/serialized_system/614e094ea985a55912fc1321256a49b755f9fc451c0f264f24d6d04af20e84d7/c_sys5_pjm_time_series_storage.h5 new=/tmp/jl_jEvzuI compression=CompressionSettings(false, CompressionTypes.DEFLATE = 1, 3, true)

Assign devices to subsystems using add_component_to_subsystem!:

g = get_component(ThermalStandard, sys, "Alta")
add_component_to_subsystem!(sys, "1", g)

g = get_component(ThermalStandard, sys, "Sundance")
add_component_to_subsystem!(sys, "2", g)

Retrieve components from a subsystem

Pass the subsystem_name keyword argument to get_components to filter by subsystem:

gens_1 = get_components(ThermalStandard, sys; subsystem_name = "1")
get_name.(gens_1)

gens_2 = get_components(ThermalStandard, sys; subsystem_name = "2")
get_name.(gens_2)
1-element Vector{String}:
 "Sundance"
Note

The get_name. command might look like field access, but it is actually Julia's broadcast syntax for applying get_name to each component in the collection. Direct field access using . is discouraged in Sienna, as it bypasses the built-in per-unit handling and can cause incorrect results.

Export a subsystem as a new System

from_subsystem produces a new, standalone System from the components assigned to a subsystem. This requires careful assignment of all dependencies — not just the devices themselves, but also any topology elements (buses, arcs) they reference.

from_subsystem(sys, "1"; runchecks = false)
System
Property Value
Name
Description
System Units Base SYSTEM_BASE
Base Power 100.0
Base Frequency 60.0
Num Components 1
Static Components
Type Count
ThermalStandard 1
Warning

The system above was created with runchecks=false and is technically invalid: the bus connected to the Alta generator is not part of subsystem "1". Without runchecks=false, this call would raise an error. Add the bus first, then re-run from_subsystem:

A valid exported System requires three additional components:

  • The generator's bus (nodeA) — every device must have its connected bus present in the subsystem.
  • A reference (slack) bus (nodeD) — at least one ACBus with bustype = ACBusTypes.REF must be present for the system to pass validation.
  • An ElectricLoad — a subsystem with no load components triggers a validation warning. Adding the PowerLoad connected to the slack bus satisfies this requirement.
g = get_component(ThermalStandard, sys, "Alta")
b = get_bus(g)
add_component_to_subsystem!(sys, "1", b)
ref_bus = get_component(ACBus, sys, "nodeD")
add_component_to_subsystem!(sys, "1", ref_bus)
load = first(get_components(x -> get_bus(x) === ref_bus, PowerLoad, sys))
add_component_to_subsystem!(sys, "1", load)
from_subsystem(sys, "1")
System
Property Value
Name
Description
System Units Base SYSTEM_BASE
Base Power 100.0
Base Frequency 60.0
Num Components 4
Static Components
Type Count
ACBus 2
PowerLoad 1
ThermalStandard 1
StaticTimeSeries Summary
owner_type owner_category name time_series_type initial_timestamp resolution count time_step_count
String String String String String Dates.CompoundPeriod Int64 Int64
PowerLoad Component max_active_power SingleTimeSeries 2024-01-01T00:00:00 1 hour 1 168

Advanced users can pass runchecks=false to skip topological validation. Only do this if you are confident you can validate the resulting system before using it for modeling.