Adding Data for Dynamic Simulations
In this tutorial, we are going to add dynamic data to a power System
, including a dynamic generator suitable for phasor-type simulations, as well as a dynamic inverter and dynamic lines necessary for more complex EMT (electro-magnetic transient) simulations.
To run a dynamic simulation in Sienna\Dyn using PowerSimulationsDynamics.jl
, two data layers are required:
- A base layer of static components, which includes the data needed to run a power flow problem
- An additional layer of dynamic components, which define differential equations to run a transient simulation
We'll define these two layers sequentially.
Defining the Static Data Layer
Instead of defining the static data in the System
manually, we will load an existing three-bus system using PowerSystemCaseBuilder.jl
to use as a starting point.
Start by importing these packages:
julia> using PowerSystems
julia> using PowerSystemCaseBuilder
julia> const PSY = PowerSystems;
To create the system, load pre-existing data for a 3-bus system using PowerSystemCaseBuilder.jl
:
julia> threebus_sys = build_system(PSIDSystems, "3 Bus Inverter Base")
┌ Info: Building new system 3 Bus Inverter Base from raw data └ sys_descriptor.raw_data = "/home/runner/.julia/artifacts/afb608473cf4d5eb22147856de1a1a651f36d40b/PowerSystemsTestData-3.1/psid_tests/data_examples" [ Info: The PSS(R)E parser currently supports buses, loads, shunts, generators, branches, transformers, and dc lines [ Info: The PSS(R)E parser currently supports buses, loads, shunts, generators, branches, transformers, and dc lines [ Info: Parsing PSS(R)E Bus data into a PowerModels Dict... [ Info: Parsing PSS(R)E Load data into a PowerModels Dict... [ Info: Parsing PSS(R)E Shunt data into a PowerModels Dict... [ Info: Parsing PSS(R)E Generator data into a PowerModels Dict... [ Info: Parsing PSS(R)E Branch data into a PowerModels Dict... [ Info: Parsing PSS(R)E Transformer data into a PowerModels Dict... [ Info: Parsing PSS(R)E Two-Terminal and VSC DC line data into a PowerModels Dict... ┌ Warning: This PSS(R)E parser currently doesn't support Storage data parsing... └ @ PowerSystems ~/work/PowerSystems.jl/PowerSystems.jl/src/parsers/pm_io/psse.jl:998 ┌ Warning: This PSS(R)E parser currently doesn't support Switch data parsing... └ @ PowerSystems ~/work/PowerSystems.jl/PowerSystems.jl/src/parsers/pm_io/psse.jl:1004 [ Info: angmin and angmax values are 0, widening these values on branch 1 to +/- 60.0 deg. [ Info: angmin and angmax values are 0, widening these values on branch 2 to +/- 60.0 deg. [ Info: angmin and angmax values are 0, widening these values on branch 3 to +/- 60.0 deg. ┌ Info: Constructing System from Power Models │ data["name"] = "threebusinverter" └ data["source_type"] = "pti" [ Info: Reading bus data [ Info: Reading Load data in PowerModels dict to populate System ... [ Info: Reading LoadZones data in PowerModels dict to populate System ... [ Info: Reading generator data [ Info: Reading branch data [ Info: Reading shunt data [ Info: Reading DC Line data [ Info: Reading storage data [ Info: Serialized System to /home/runner/.julia/packages/PowerSystemCaseBuilder/uZO8H/data/serialized_system/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/3 Bus Inverter Base.json [ Info: Serialized System metadata to /home/runner/.julia/packages/PowerSystemCaseBuilder/uZO8H/data/serialized_system/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/3 Bus Inverter Base_metadata.json System ┌───────────────────┬─────────────┐ │ Property │ Value │ ├───────────────────┼─────────────┤ │ Name │ │ │ Description │ │ │ System Units Base │ SYSTEM_BASE │ │ Base Power │ 100.0 │ │ Base Frequency │ 60.0 │ │ Num Components │ 16 │ └───────────────────┴─────────────┘ Static Components ┌─────────────────┬───────┐ │ Type │ Count │ ├─────────────────┼───────┤ │ ACBus │ 3 │ │ Arc │ 3 │ │ Area │ 1 │ │ Line │ 3 │ │ LoadZone │ 1 │ │ StandardLoad │ 3 │ │ ThermalStandard │ 2 │ └─────────────────┴───────┘
See that there is a table of "Static Components", but no "Dynamic" data yet.
Let's view the generators in the system with show_components
, including which bus they are connected at:
julia> show_components(ThermalStandard, threebus_sys, [:bus])
ThermalStandard ┌─────────────────┬───────────┬──────────────┐ │ name │ available │ bus │ ├─────────────────┼───────────┼──────────────┤ │ generator-102-1 │ true │ ACBus: BUS 2 │ │ generator-103-1 │ true │ ACBus: BUS 3 │ └─────────────────┴───────────┴──────────────┘
Notice that there are generators connected at Buses 2 and 3, but not Bus 1.
Now, we are going to add the data needed to run an EMT simulation. We will add an infinite voltage source to Bus 1, which is the last component we need to complete the static data layer. Then, we will a dynamic generator or inverter model to the two generators, as well as adding dynamic lines.
Add an Infinite Voltage Source
Add a infinite voltage source with small impedance to Bus 1 (the reference bus). First, retrieve the reference bus using get_components
:
julia> slack_bus = first(get_components(x -> get_bustype(x) == ACBusTypes.REF, Bus, threebus_sys))
ACBus: BUS 1: number: 101 name: BUS 1 bustype: ACBusTypes.REF = 3 angle: 0.0 magnitude: 1.02 voltage_limits: (min = 0.9, max = 1.1) base_voltage: 138.0 area: Area: 1 load_zone: LoadZone: 1 ext: Dict{String, Any}() InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: false
Notice we filtered by the bus type to get the bus(es) we wanted.
Next, manually define a Source
:
julia> inf_source = Source(; name = "InfBus", #name available = true, #availability active_power = 0.0, reactive_power = 0.0, bus = slack_bus, #bus R_th = 0.0, #Rth X_th = 5e-6, #Xth );
And add it to the system:
julia> add_component!(threebus_sys, inf_source)
This completes the first layer of static data, using components similar to those we added manually in the Create and Explore a Power System
tutorial.
Adding a Dynamic Generator
Now, we will connect a classic machine model to the generator at bus 102. Dynamic generator devices are composed by 5 components: a Machine, Shaft, Automatic Voltage Regulator (AVR), Power System Stabilizer (PSS), and Prime Mover and Turbine Governor. For each of those 5 components, we will select a specific model that defines the data and differential equations for that component, and then use those 5 components to define the complete dynamic generator.
When defining dynamic data, by convention PowerSystems.jl
assumes that all data is in DEVICE_BASE
.
First, define a Machine that describes the the stator electro-magnetic dynamics:
julia> # Create the machine machine_oneDoneQ = OneDOneQMachine(; R = 0.0, Xd = 1.3125, Xq = 1.2578, Xd_p = 0.1813, Xq_p = 0.25, Td0_p = 5.89, Tq0_p = 0.6, )
OneDOneQMachine(0.0, 1.3125, 1.2578, 0.1813, 0.25, 5.89, 0.6, Dict{String, Any}(), [:eq_p, :ed_p], 2, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("4b32385c-541c-40c9-be13-423b44d26b61"), nothing, nothing, nothing))
Notice that we selected a specific model, OneDOneQMachine
, with the parameters tailored to a One-d-one-q dynamic machine model.
Next, define a specific Shaft model, SingleMass
that describes the rotor electro-mechanical dynamics:
julia> # Shaft shaft_no_damping = SingleMass(; H = 3.01, #(M = 6.02 -> H = M/2) D = 0.0, )
SingleMass(3.01, 0.0, Dict{String, Any}(), [:δ, :ω], 2, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("36dc5495-d3a5-4caa-a7fe-8854d19d804e"), nothing, nothing, nothing))
Represent the electromotive dynamics of the AVR controller using a specific Automatic Voltage Regulator model, AVRTypeI
:
julia> # AVR: Type I: Resembles a DC1 AVR avr_type1 = AVRTypeI(; Ka = 20.0, Ke = 0.01, Kf = 0.063, Ta = 0.2, Te = 0.314, Tf = 0.35, Tr = 0.001, Va_lim = (min = -5.0, max = 5.0), Ae = 0.0039, #1st ceiling coefficient Be = 1.555, #2nd ceiling coefficient )
AVRTypeI(20.0, 0.01, 0.063, 0.2, 0.314, 0.35, 0.001, (min = -5.0, max = 5.0), 0.0039, 1.555, 1.0, Dict{String, Any}(), [:Vf, :Vr1, :Vr2, :Vm], 4, StateTypes[StateTypes.Differential = 1, StateTypes.Differential = 1, StateTypes.Differential = 1, StateTypes.Differential = 1], InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("a6367cba-a4a0-420c-a729-b189f5327e60"), nothing, nothing, nothing))
Define a fixed efficiency Prime Mover and Turbine Governor with TGFixed
:
julia> #No TG tg_none = TGFixed(; efficiency = 1.0) #efficiency
TGFixed(1.0, 1.0, Dict{String, Any}(), Symbol[], 0, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("d65efb63-fe79-4134-adff-b5e0980f1462"), nothing, nothing, nothing))
See that we are modeling a machine that does not include a Turbine Governor (or PSS below), but you must define components for them to build a complete machine model.
Similarly, define a PSS using PSSFixed
, which is used to describe the stabilization signal for the AVR:
julia> #No PSS pss_none = PSSFixed(; V_pss = 0.0)
PSSFixed(0.0, Dict{String, Any}(), Symbol[], 0, InfrastructureSystems.InfrastructureSystemsInternal(Base.UUID("ef10231a-2e11-4a51-bb3d-f86a931033e1"), nothing, nothing, nothing))
Now, we are ready to add a dynamic generator to the static generator at bus 102. First, let's get that static generator:
julia> static_gen = get_component(Generator, threebus_sys, "generator-102-1")
ThermalStandard: generator-102-1: name: generator-102-1 available: true status: true bus: ACBus: BUS 2 active_power: 0.7 reactive_power: 0.0 rating: 3.333526661060325 active_power_limits: (min = 0.0, max = 3.18) reactive_power_limits: (min = -1.0, max = 1.0) ramp_limits: (up = 3.18, down = 3.18) operation_cost: ThermalGenerationCost composed of variable: CostCurve{QuadraticCurve} base_power: 100.0 time_limits: nothing must_run: false prime_mover_type: PrimeMovers.OT = 19 fuel: ThermalFuels.OTHER = 14 services: 0-element Vector{Service} time_at_status: 10000.0 dynamic_injector: nothing ext: Dict{String, Any}("z_source" => (r = 0.0, x = 1.0)) InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: false
Notice that its dynamic_injector
field is currently nothing
.
Use its name and the 5 components above to define its DynamicGenerator
model:
julia> dynamic_gen = DynamicGenerator(; name = get_name(static_gen), ω_ref = 1.0, # frequency reference set-point machine = machine_oneDoneQ, shaft = shaft_no_damping, avr = avr_type1, prime_mover = tg_none, pss = pss_none, )
DynamicGenerator: generator-102-1: name: generator-102-1 ω_ref: 1.0 machine: OneDOneQMachine shaft: SingleMass avr: AVRTypeI prime_mover: TGFixed pss: PSSFixed base_power: 100.0 n_states: 8 states: [:eq_p, :ed_p, :δ, :ω, :Vf, :Vr1, :Vr2, :Vm] ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
See that the specific component models that we selected and defined above were used to specify the states needed to model this generator in a dynamic simulation.
Finally, use the dynamic version of add_component!
to add this data to the System
:
julia> add_component!(threebus_sys, dynamic_gen, static_gen)
┌ Warning: struct DynamicGenerator does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/0MQiO/src/validation.jl:51
Notice that unlike static components, which are just added to the System
, this dynamic component is added to a specific static component within the System
.
To define identical dynamic devices for multiple generators at once, define the pieces of the generator model as functions, such as:
avr_type1() = AVRTypeI(...
When called in the DynamicGenerator
constructor, this will create a new AVR for each generator, so they are different in memory. Later, if you decide to modify the AVR parameters for a specific generator, it will not modify the AVR in another generator.
Recall that you can print the system to see a summary of its data:
julia> threebus_sys
System ┌───────────────────┬─────────────┐ │ Property │ Value │ ├───────────────────┼─────────────┤ │ Name │ │ │ Description │ │ │ System Units Base │ SYSTEM_BASE │ │ Base Power │ 100.0 │ │ Base Frequency │ 60.0 │ │ Num Components │ 18 │ └───────────────────┴─────────────┘ Static Components ┌─────────────────┬───────┐ │ Type │ Count │ ├─────────────────┼───────┤ │ ACBus │ 3 │ │ Arc │ 3 │ │ Area │ 1 │ │ Line │ 3 │ │ LoadZone │ 1 │ │ Source │ 1 │ │ StandardLoad │ 3 │ │ ThermalStandard │ 2 │ └─────────────────┴───────┘ Dynamic Components ┌────────────────────────────────────────────────────────────────────────────┬── │ Type │ ⋯ ├────────────────────────────────────────────────────────────────────────────┼── │ DynamicGenerator{OneDOneQMachine, SingleMass, AVRTypeI, TGFixed, PSSFixed} │ ⋯ └────────────────────────────────────────────────────────────────────────────┴── 1 column omitted
See that a new table has been added: "Dynamic Components."
Also, print the static generator to double-check the dynamic layer has been added:
julia> static_gen
ThermalStandard: generator-102-1: name: generator-102-1 available: true status: true bus: ACBus: BUS 2 active_power: 0.7 reactive_power: 0.0 rating: 3.333526661060325 active_power_limits: (min = 0.0, max = 3.18) reactive_power_limits: (min = -1.0, max = 1.0) ramp_limits: (up = 3.18, down = 3.18) operation_cost: ThermalGenerationCost composed of variable: CostCurve{QuadraticCurve} base_power: 100.0 time_limits: nothing must_run: false prime_mover_type: PrimeMovers.OT = 19 fuel: ThermalFuels.OTHER = 14 services: 0-element Vector{Service} time_at_status: 10000.0 dynamic_injector: DynamicGenerator: generator-102-1 ext: Dict{String, Any}("z_source" => (r = 0.0, x = 1.0)) InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: false
Verify that dynamic_injector
now contains our dynamic generator model.
Up to this point, you have added the dynamic data necessary to do a phaser-type simulation, which focuses on machine behavior. Now we will also add dynamic inverters and lines to enable EMT simulations.
Adding a Dynamic Inverter
Next we will connect a Virtual Synchronous Generator Inverter at bus 103.
An inverter is composed of Converter, OuterControl, InnerControl, DCSource, FrequencyEstimator, and Filter components:
As we did for the generator, we will define each of these six components with a specific model, which defines its differential equations.
First, define an AverageConverter
as the specific model for the Converter component:
julia> converter_high_power() = AverageConverter(; rated_voltage = 138.0, rated_current = 100.0, )
converter_high_power (generic function with 1 method)
Recall from the tip above that we can define these components as functions instead of objects for reusability across multiple generators, and notice that that is what we have done here.
Define OuterControl using Virtual Inertia for the active power control and ReactivePowerDroop for the reactive power control:
julia> outer_control() = OuterControl( VirtualInertia(; Ta = 2.0, kd = 400.0, kω = 20.0), ReactivePowerDroop(; kq = 0.2, ωf = 1000.0), )
outer_control (generic function with 1 method)
Define an InnerControl as a Voltage+Current Controller with Virtual Impedance, using VoltageModeControl
:
julia> inner_control() = VoltageModeControl(; kpv = 0.59, #Voltage controller proportional gain kiv = 736.0, #Voltage controller integral gain kffv = 0.0, #Binary variable enabling voltage feed-forward in current controllers rv = 0.0, #Virtual resistance in pu lv = 0.2, #Virtual inductance in pu kpc = 1.27, #Current controller proportional gain kic = 14.3, #Current controller integral gain kffi = 0.0, #Binary variable enabling the current feed-forward in output of current controllers ωad = 50.0, #Active damping low pass filter cut-off frequency kad = 0.2, #Active damping gain )
inner_control (generic function with 1 method)
Define a FixedDCSource
for the DCSource:
julia> dc_source_lv() = FixedDCSource(; voltage = 600.0)
dc_source_lv (generic function with 1 method)
Define a FrequencyEstimator as a phase-locked loop (PLL) using KauraPLL
:
julia> pll() = KauraPLL(; ω_lp = 500.0, #Cut-off frequency for LowPass filter of PLL filter. kp_pll = 0.084, #PLL proportional gain ki_pll = 4.69, #PLL integral gain )
pll (generic function with 1 method)
Finally, define an LCLFilter
for the Filter:
julia> filt() = LCLFilter(; lf = 0.08, rf = 0.003, cf = 0.074, lg = 0.2, rg = 0.01, )
filt (generic function with 1 method)
Now, use those six functions to define a complete dynamic inverter by getting the static component at bus 103:
julia> gen_103 = get_component(Generator, threebus_sys, "generator-103-1");
using it and our six functions to define a DynamicInverter
:
julia> dynamic_inv = DynamicInverter(; name = get_name(gen_103), ω_ref = 1.0, # frequency reference set-point converter = converter_high_power(), outer_control = outer_control(), inner_control = inner_control(), dc_source = dc_source_lv(), freq_estimator = pll(), filter = filt(), )
DynamicInverter: generator-103-1: name: generator-103-1 ω_ref: 1.0 converter: AverageConverter outer_control: OuterControl{VirtualInertia, ReactivePowerDroop} inner_control: VoltageModeControl dc_source: FixedDCSource freq_estimator: KauraPLL filter: LCLFilter limiter: nothing base_power: 100.0 n_states: 19 states: [:θ_oc, :ω_oc, :q_oc, :ξd_ic, :ξq_ic, :γd_ic, :γq_ic, :ϕd_ic, :ϕq_ic, :vd_pll, :vq_pll, :ε_pll, :θ_pll, :ir_cnv, :ii_cnv, :vr_filter, :vi_filter, :ir_filter, :ii_filter] ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
and adding it to the System
:
julia> add_component!(threebus_sys, dynamic_inv, gen_103)
┌ Warning: struct DynamicInverter does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/0MQiO/src/validation.jl:51
Both generators have now been updated with dynamic data. Let's complete the System
updates by adding dynamic lines.
Adding Dynamic Lines
A System
must have at least two buses and one branch to run a dynamic simulation in PowerSimulationsDynamics.jl
.
Let's review the AC branches currently in the system:
julia> get_components(ACBranch, threebus_sys)
ACBranch Counts: Line: 3
Notice that we have three static Line
components.
Let's also print the first line to review its format:
julia> first(get_components(Line, threebus_sys))
Line: BUS 1-BUS 3-i_1: name: BUS 1-BUS 3-i_1 available: true active_power_flow: 0.0 reactive_power_flow: 0.0 arc: Arc: BUS 1 -> BUS 3 r: 0.01 x: 0.12 b: (from = 0.1, to = 0.1) rating: 2.5 angle_limits: (min = -1.0472, max = 1.0472) g: (from = 0.0, to = 0.0) services: 0-element Vector{Service} ext: Dict{String, Any}() InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: false
See that these components do not have the fields for dynamic modeling, such as fields for different states.
Let's update that by cycling through these lines and using DynamicBranch
to extend each static line with the necessary fields:
julia> for l in get_components(Line, threebus_sys) # create a dynamic branch dyn_branch = DynamicBranch(l) # add dynamic branch to the system, replacing the static branch add_component!(threebus_sys, dyn_branch) end
┌ Warning: struct DynamicBranch does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/0MQiO/src/validation.jl:51 ┌ Warning: struct DynamicBranch does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/0MQiO/src/validation.jl:51 ┌ Warning: struct DynamicBranch does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/0MQiO/src/validation.jl:51
Take a look at the AC branches in the system again:
julia> branches = get_components(ACBranch, threebus_sys)
ACBranch Counts: DynamicBranch: 3
Notice that now there are 3 DynamicBranch
components instead the Line
components.
Let's take a look by printing first one:
julia> first(branches)
DynamicBranch: BUS 1-BUS 3-i_1: branch: Line: BUS 1-BUS 3-i_1 n_states: 2 states: [:Il_R, :Il_I] InfrastructureSystems.SystemUnitsSettings: base_value: 100.0 unit_system: UnitSystem.SYSTEM_BASE = 0 has_supplemental_attributes: false has_time_series: false
Observe that this is a wrapper around the static data, with the additional states data for dynamic modeling.
Finally, let's print the System
again to summarize our additions:
julia> threebus_sys
System ┌───────────────────┬─────────────┐ │ Property │ Value │ ├───────────────────┼─────────────┤ │ Name │ │ │ Description │ │ │ System Units Base │ SYSTEM_BASE │ │ Base Power │ 100.0 │ │ Base Frequency │ 60.0 │ │ Num Components │ 19 │ └───────────────────┴─────────────┘ Static Components ┌─────────────────┬───────┐ │ Type │ Count │ ├─────────────────┼───────┤ │ ACBus │ 3 │ │ Arc │ 3 │ │ Area │ 1 │ │ DynamicBranch │ 3 │ │ LoadZone │ 1 │ │ Source │ 1 │ │ StandardLoad │ 3 │ │ ThermalStandard │ 2 │ └─────────────────┴───────┘ Dynamic Components ┌─────────────────────────────────────────────────────────────────────────────── │ Type ⋯ ├─────────────────────────────────────────────────────────────────────────────── │ DynamicGenerator{OneDOneQMachine, SingleMass, AVRTypeI, TGFixed, PSSFixed} ⋯ │ DynamicInverter{AverageConverter, OuterControl{VirtualInertia, ReactivePower ⋯ └─────────────────────────────────────────────────────────────────────────────── 2 columns omitted
Verify that the additions were successful, with an added voltage Source
, DynamicBranch
es replacing the static Lines
, and two new dynamic components with the generator and inverter models.
Next Steps
In this tutorial, you have updated a static system with a second dynamic data layer. The data you added can enable a phasor-based simulation using the dynamic generator, or a more complex EMT simulation with the additional dynamic inverter and dynamic lines.
Next, you might like to:
- Read more about the static and dynamic data layers and the dynamic data format in Dynamic Devices.
- Review the specific sub-system models available in
PowerSystems.jl
for Machine, Shaft, AVR, PSS, Prime Mover and Turbine Governor, Converter, OuterControl, InnerControl, DCSource, FrequencyEstimator, and Filter components - Explore
PowerSimulationsDynamics.jl
for dynamics modeling in Sienna\Dyn