Creating and Handling Data for Dynamic Simulations
Originally Contributed by: Rodrigo Henriquez and José Daniel Lara
Introduction
This tutorial briefly introduces how to create a system using PowerSystems.jl
data structures. For more details visit PowerSystems.jl
Documentation
Start by calling PowerSystems.jl
and PowerSystemCaseBuilder.jl
:
julia> using PowerSystems
julia> using PowerSystemCaseBuilder
julia> const PSY = PowerSystems;
PowerSystemCaseBuilder.jl
is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function build_system
. For more details visit PowerSystemCaseBuilder Documentation
System description
Next we need to define the different elements required to run a simulation. To run a simulation in PowerSimulationsDynamics
, it is required to define a System
that contains the following components:
Static Components
We called static components to those that are used to run a Power Flow problem.
- Vector of
Bus
elements, that define all the buses in the network. - Vector of
Branch
elements, that define all the branches elements (that connect two buses) in the network. - Vector of
StaticInjection
elements, that define all the devices connected to buses that can inject (or withdraw) power. These static devices, typically generators, inPowerSimulationsDynamics
are used to solve the Power Flow problem that determines the active and reactive power provided for each device. - Vector of
PowerLoad
elements, that define all the loads connected to buses that can withdraw current. These are also used to solve the Power Flow. - Vector of
Source
elements, that define source components behind a reactance that can inject or withdraw current. - The base of power used to define per unit values, in MVA as a
Float64
value. - The base frequency used in the system, in Hz as a
Float64
value.
Dynamic Components
Dynamic components are those that define differential equations to run a transient simulation.
- Vector of
DynamicInjection
elements. These components must be attached to aStaticInjection
that connects the power flow solution to the dynamic formulation of such device.DynamicInjection
can beDynamicGenerator
orDynamicInverter
, and its specific formulation (i.e. differential equations) will depend on the specific components that define such device. - (Optional) Selecting which of the
Lines
(of theBranch
vector) elements must be modeled ofDynamicLines
elements, that can be used to model lines with differential equations.
To start we will define the data structures for the network.
Three Bus case manual data creation
The following describes the system creation for this dynamic simulation case.
Static System creation
To create the system you need to load data using PowerSystemCaseBuilder.jl
. This system was originally created from following raw file.
julia> sys = build_system(PSIDSystems, "3 Bus Inverter Base"; force_build=true)
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 │ └─────────────────┴───────┘
This system does not have an injection device in bus 1 (the reference bus). We can add a source with small impedance directly as follows:
julia> slack_bus = [b for b in get_components(ACBus, sys) if get_bustype(b) == ACBusTypes.REF][1]
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
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 )
Source: InfBus: name: InfBus available: true bus: ACBus: BUS 1 active_power: 0.0 reactive_power: 0.0 R_th: 0.0 X_th: 5.0e-6 internal_voltage: 1.0 internal_angle: 0.0 base_power: 100.0 dynamic_injector: nothing services: 0-element Vector{Service} ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
julia> add_component!(sys, inf_source)
We just added a infinite source with $X_{th} = 5\cdot 10^{-6}$ pu. The system can be explored directly using functions like:
julia> show_components(sys, Source)
Source ┌────────┬───────────┐ │ name │ available │ ├────────┼───────────┤ │ InfBus │ true │ └────────┴───────────┘
julia> show_components(sys, ThermalStandard)
ThermalStandard ┌─────────────────┬───────────┐ │ name │ available │ ├─────────────────┼───────────┤ │ generator-102-1 │ true │ │ generator-103-1 │ true │ └─────────────────┴───────────┘
By exploring those it can be seen that the generators are named as: generator-bus_number-id
. Then, the generator attached at bus 2 is named generator-102-1
.
Dynamic Injections
We are now interested in attaching to the system the dynamic component that will be modeling our dynamic generator.
Dynamic generator devices are composed by 5 components, namely, machine
, shaft
, avr
, tg
and pss
. So we will be adding functions to create all of its components and the generator itself:
julia> # *Machine* machine_classic() = BaseMachine( 0.0, #R 0.2995, #Xd_p 0.7087, #eq_p ) # *Shaft*
machine_classic (generic function with 1 method)
julia> shaft_damping() = SingleMass( 3.148, #H 2.0, #D ) # *AVR: No AVR*
shaft_damping (generic function with 1 method)
julia> avr_none() = AVRFixed(0.0) # *TG: No TG*
avr_none (generic function with 1 method)
julia> tg_none() = TGFixed(1.0) #efficiency # *PSS: No PSS*
tg_none (generic function with 1 method)
julia> pss_none() = PSSFixed(0.0)
pss_none (generic function with 1 method)
The next lines receives a static generator name, and creates a DynamicGenerator
based on that specific static generator, with the specific components defined previously. This is a classic machine model without AVR, Turbine Governor and PSS.
julia> static_gen = get_component(Generator, 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
julia> dyn_gen = DynamicGenerator( name = get_name(static_gen), ω_ref = 1.0, machine = machine_classic(), shaft = shaft_damping(), avr = avr_none(), prime_mover = tg_none(), pss = pss_none(), )
DynamicGenerator: generator-102-1: name: generator-102-1 ω_ref: 1.0 machine: BaseMachine shaft: SingleMass avr: AVRFixed prime_mover: TGFixed pss: PSSFixed base_power: 100.0 n_states: 2 states: [:δ, :ω] ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
The dynamic generator is added to the system by specifying the dynamic and static generator
julia> add_component!(sys, dyn_gen, static_gen)
Then we can serialize our system data to a json file that can be later read as:
julia> to_json(sys, joinpath(file_dir, "modified_sys.json"), force = true)
Dynamic Lines case: Data creation
We will now create a three bus system with one inverter and one generator. In order to do so, we will parse the following ThreebusInverter.raw
network:
julia> threebus_sys = build_system(PSIDSystems, "3 Bus Inverter Base")
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 │ └─────────────────┴───────┘
julia> slack_bus = first(get_components(x -> get_bustype(x) == BusTypes.REF, Bus, threebus_sys))
ERROR: UndefVarError: `BusTypes` not defined in `Main` Suggestion: check for spelling errors or missing imports.
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 )
Source: InfBus: name: InfBus available: true bus: ACBus: BUS 1 active_power: 0.0 reactive_power: 0.0 R_th: 0.0 X_th: 5.0e-6 internal_voltage: 1.0 internal_angle: 0.0 base_power: 100.0 dynamic_injector: nothing services: 0-element Vector{Service} ext: Dict{String, Any}() internal: InfrastructureSystems.InfrastructureSystemsInternal has_supplemental_attributes: false has_time_series: false
julia> add_component!(threebus_sys, inf_source)
ERROR: ArgumentError: ACBus: BUS 1 is not attached to the system
We will connect a One-d-one-q machine at bus 102, and a Virtual Synchronous Generator Inverter at bus 103. An inverter is composed by a converter
, outer control
, inner control
, dc source
, frequency estimator
and a filter
.
Dynamic Inverter definition
We will create specific functions to create the components of the inverter as follows:
julia> #Define converter as an AverageConverter converter_high_power() = AverageConverter( rated_voltage = 138.0, rated_current = 100.0 ) #Define Outer Control as a composition of Virtual Inertia + Reactive Power Droop
converter_high_power (generic function with 1 method)
julia> outer_control() = OuterControl( VirtualInertia(Ta = 2.0, kd = 400.0, kω = 20.0), ReactivePowerDroop(kq = 0.2, ωf = 1000.0), ) #Define an Inner Control as a Voltage+Current Controler with Virtual Impedance:
outer_control (generic function with 1 method)
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 ) #Define DC Source as a FixedSource:
inner_control (generic function with 1 method)
julia> dc_source_lv() = FixedDCSource(voltage = 600.0) #Define a Frequency Estimator as a PLL #based on Vikram Kaura and Vladimir Blaskoc 1997 paper:
dc_source_lv (generic function with 1 method)
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 ) #Define an LCL filter:
pll (generic function with 1 method)
julia> filt() = LCLFilter(lf = 0.08, rf = 0.003, cf = 0.074, lg = 0.2, rg = 0.01)
filt (generic function with 1 method)
We will construct the inverter later by specifying to which static device is assigned.
Dynamic Generator definition
Similarly we will construct a dynamic generator as follows:
julia> # Create the machine machine_oneDoneQ() = OneDOneQMachine( 0.0, #R 1.3125, #Xd 1.2578, #Xq 0.1813, #Xd_p 0.25, #Xq_p 5.89, #Td0_p 0.6, #Tq0_p ) # Shaft
machine_oneDoneQ (generic function with 1 method)
julia> shaft_no_damping() = SingleMass( 3.01, #H (M = 6.02 -> H = M/2) 0.0, #D ) # AVR: Type I: Resembles a DC1 AVR
shaft_no_damping (generic function with 1 method)
julia> avr_type1() = AVRTypeI( 20.0, #Ka - Gain 0.01, #Ke 0.063, #Kf 0.2, #Ta 0.314, #Te 0.35, #Tf 0.001, #Tr (min = -5.0, max = 5.0), 0.0039, #Ae - 1st ceiling coefficient 1.555, #Be - 2nd ceiling coefficient ) #No TG
avr_type1 (generic function with 1 method)
julia> tg_none() = TGFixed(1.0) #efficiency #No PSS
tg_none (generic function with 1 method)
julia> pss_none() = PSSFixed(0.0) #Vs
pss_none (generic function with 1 method)
Now we will construct the dynamic generator and inverter.
Add the components to the system
julia> for g in get_components(Generator, threebus_sys) #Find the generator at bus 102 if get_number(get_bus(g)) == 102 #Create the dynamic generator case_gen = DynamicGenerator( get_name(g), 1.0, # ω_ref, machine_oneDoneQ(), #machine shaft_no_damping(), #shaft avr_type1(), #avr tg_none(), #tg pss_none(), #pss ) #Attach the dynamic generator to the system by # specifying the dynamic and static components add_component!(threebus_sys, case_gen, g) #Find the generator at bus 103 elseif get_number(get_bus(g)) == 103 #Create the dynamic inverter case_inv = DynamicInverter( get_name(g), 1.0, # ω_ref, converter_high_power(), #converter outer_control(), #outer control inner_control(), #inner control voltage source dc_source_lv(), #dc source pll(), #pll filt(), #filter ) #Attach the dynamic inverter to the system add_component!(threebus_sys, case_inv, g) end end
Save the system in a JSON file
julia> to_json(threebus_sys, joinpath(file_dir, "threebus_sys.json"), force = true)