Use Context Managers for Efficient Bulk Operations
PowerSystems.jl provides several "context manager" functions that help you perform bulk operations more efficiently and safely. These functions temporarily change system settings or optimize batch operations, then automatically restore the original state when complete.
Context managers in PowerSystems.jl follow a pattern similar to Logging.with_logger in Julia. They accept a function (typically as a do block) that executes with modified settings, ensuring cleanup even if errors occur.
Available Context Managers
PowerSystems.jl provides three main context managers:
with_units_base- Temporarily change unit system for getting/setting component databegin_supplemental_attributes_update- Optimize bulk addition/removal of supplemental attributesbegin_time_series_update- Optimize bulk addition of time series data
Using with_units_base
The with_units_base function temporarily changes the unit system for a System or Component, executes your code, then automatically restores the original unit system. This is useful when you need to retrieve or set values in a specific unit system without permanently changing the system's configuration.
You can specify the unit system using either the UnitSystem enum (e.g., UnitSystem.NATURAL_UNITS) or a string (e.g., "NATURAL_UNITS"). Both forms are supported and equivalent.
Example: Getting Component Data in Natural Units
using PowerSystems
using PowerSystemCaseBuilder
# Load a system
sys = build_system(PSISystems, "c_sys5_pjm")
gen = first(get_components(ThermalStandard, sys))
# Get active power in natural units (MW) regardless of system's unit base
active_power_mw = with_units_base(sys, UnitSystem.NATURAL_UNITS) do
get_active_power(gen)
end
# The system's unit base is automatically restored after the block520.0Example: Setting Multiple Component Values in Natural Units
# Temporarily change units to add/modify multiple components in natural units
with_units_base(sys, "NATURAL_UNITS") do
for gen in get_components(ThermalStandard, sys)
# Set values in MW, MVA, etc.
set_active_power!(gen, 150.0) # MW
set_rating!(gen, 200.0) # MVA
end
end
# System automatically returns to original unit baseComponent-Level Context Manager
You can also use with_units_base on individual components:
active_power_mw = with_units_base(gen, UnitSystem.NATURAL_UNITS) do
get_active_power(gen)
end150.0The with_units_base context manager is particularly useful when you need to work with data in natural units (MW, MVA, etc.) while keeping your system configured in per-unit for optimization or simulation purposes.
Using begin_supplemental_attributes_update
The begin_supplemental_attributes_update function optimizes performance when adding or removing many supplemental attributes. It batches operations together, reducing overhead from repeated index updates.
If an error occurs during the update, all changes are automatically reverted, ensuring data consistency.
begin_supplemental_attributes_update(sys) do
for gen in get_components(ThermalStandard, sys)
outage = GeometricDistributionForcedOutage(;
mean_time_to_recovery = 8.0,
outage_transition_probability = 0.001,
)
add_supplemental_attribute!(sys, gen, outage)
end
endWithout the context manager, each individual call to add_supplemental_attribute! updates internal indexes separately, which can be slow when adding many attributes. For a complete worked example, see Attach supplemental data to components.
Using begin_time_series_update
The begin_time_series_update function optimizes performance when adding many time series arrays by keeping the HDF5 file open and batching SQLite database operations. This reduces the overhead of repeatedly opening/closing files and performing individual database transactions.
If an error occurs during the update, changes are automatically reverted.
This context manager is not necessary for in-memory time series stores, only for HDF5-backed storage.
Example: Adding Multiple Time Series
using PowerSystems
using Dates
# Create time series data
resolution = Dates.Hour(1)
data = Dict(
DateTime("2020-01-01T00:00:00") => ones(24),
DateTime("2020-01-02T00:00:00") => ones(24) * 1.1,
)
# Get components
generators = collect(get_components(ThermalStandard, sys))
# Use context manager for efficient bulk addition
begin_time_series_update(sys) do
for (i, gen) in enumerate(generators)
forecast = Deterministic(
"max_active_power",
data,
resolution;
scaling_factor_multiplier = get_max_active_power,
)
add_time_series!(sys, gen, forecast)
end
endWhen adding thousands of time series arrays, using begin_time_series_update can provide significant performance improvements by reducing file I/O and database transaction overhead.
Nesting Context Managers
Context managers can be nested if needed:
with_units_base(sys, "NATURAL_UNITS") do
begin_time_series_update(sys) do
# Add time series with natural unit scaling factors
for gen in get_components(Generator, sys)
# ... add time series ...
end
end
endSee Also
- Per-unit Conventions - Learn more about unit systems
- Supplemental attributes — why contextual data is separate from components
- Attach supplemental data to components — bulk attachment with
begin_supplemental_attributes_update - Working with Time Series Data - Tutorial on time series handling
- Improve Performance with Time Series Data - Additional time series performance tips