Originally Contributed by: Rodrigo Henriquez and José Daniel Lara
This tutorial briefly introduces how to create a system using PowerSystems.jl
data
structures. The tutorial will guide you to create the JSON data file for the tutorial 1.
Start by calling PowerSystems.jl
:
using SIIPExamples
using PowerSystems
const PSY = PowerSystems
PowerSystems
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:
We called static components to those that are used to run a Power Flow problem.
Bus
elements, that define all the buses in the network.Branch
elements, that define all the branches elements (that connect two buses)
in the network.StaticInjection
elements, that define all the devices connected to buses that
can inject (or withdraw) power. These static devices, typically generators, in
PowerSimulationsDynamics
are used to solve the Power Flow problem that determines the
active and reactive power provided for each device.PowerLoad
elements, that define all the loads connected to buses that can
withdraw current. These are also used to solve the Power Flow.Source
elements, that define source components behind a reactance that can
inject or withdraw current.Float64
value.Float64
value.Dynamic components are those that define differential equations to run a transient simulation.
DynamicInjection
elements. These components must be attached to aStaticInjection
that connects the power flow solution to the dynamic formulation of such
device. DynamicInjection
can be DynamicGenerator
or DynamicInverter
, and its specific
formulation (i.e. differential equations) will depend on the specific components that define such device.
Lines
(of the Branch
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.
The following describes the system creation for the OMIB case.
To create the system you need to pass the location of the RAW file
file_dir = joinpath(
dirname(dirname(pathof(SIIPExamples))),
"script",
"4_PowerSimulationsDynamics_examples",
"Data",
)
omib_sys = System(joinpath(file_dir, "OMIB.raw"))
[ Info: The PSS(R)E parser currently supports buses, loads, shunts, generators, branches, transformers, and dc lines [ 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: the voltage setpoint on generator 1 does not match the value at bus 102 ┌ Info: Constructing System from Power Models │ data["name"] = "omib" └ data["source_type"] = "pti" [ Info: Reading bus data [ Info: Reading generator data [ Info: Reading branch data [ Info: Reading branch data [ Info: Reading DC Line data [ Info: Reading storage data ┌ Warning: There are no ElectricLoad Components in the System └ @ PowerSystems ~/.julia/packages/PowerSystems/N2l8o/src/utils/IO/system_checks.jl:56
Base Power: 100.0
Num components: 8
ConcreteType | SuperTypes | Count | |
---|---|---|---|
String | String | Int64 | |
1 | Arc | Topology <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 1 |
2 | Area | AggregationTopology <: Topology <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 1 |
3 | Bus | Topology <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 2 |
4 | Line | ACBranch <: Branch <: Device <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 2 |
5 | LoadZone | AggregationTopology <: Topology <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 1 |
6 | ThermalStandard | ThermalGen <: Generator <: StaticInjection <: Device <: Component <: InfrastructureSystemsComponent <: InfrastructureSystemsType <: Any | 1 |
Components with time series data: 0
Total StaticTimeSeries: 0
Total Forecasts: 0
Resolution: 0 seconds
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:
slack_bus = [b for b in get_components(Bus, omib_sys) if b.bustype == BusTypes.REF][1]
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
)
add_component!(omib_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:
get_components(Source, omib_sys)
get_components(Generator, omib_sys)
Generator Counts: ThermalStandard: 1
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
.
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:
Machine
machine_classic() = BaseMachine(
0.0, #R
0.2995, #Xd_p
0.7087, #eq_p
)
machine_classic (generic function with 1 method)
Shaft
shaft_damping() = SingleMass(
3.148, #H
2.0, #D
)
shaft_damping (generic function with 1 method)
AVR: No AVR
avr_none() = AVRFixed(0.0)
avr_none (generic function with 1 method)
TG: No TG
tg_none() = TGFixed(1.0) #efficiency
tg_none (generic function with 1 method)
PSS: No PSS
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.
static_gen = get_component(Generator, omib_sys, "generator-102-1")
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(),
)
generator-102-1 (DynamicGenerator{BaseMachine,SingleMass,AVRFixed,TGFixed,PSSFixed}): 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
The dynamic generator is added to the system by specifying the dynamic and static generator
add_component!(omib_sys, dyn_gen, static_gen)
┌ Warning: struct DynamicGenerator does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/lYELp/src/validation.jl:51
Then we can serialize our system data to a json file that can be later read as:
to_json(omib_sys, joinpath(file_dir, "omib_sys.json"), force = true)
[ Info: Serialized time series data to /Users/cbarrows/Documents/repos/SIIPExamples.jl/script/4_PowerSimulationsDynamics_examples/Data/omib_sys_time_series_storage.h5. [ Info: Serialized System to /Users/cbarrows/Documents/repos/SIIPExamples.jl/script/4_PowerSimulationsDynamics_examples/Data/omib_sys.json
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:
sys_file_dir = joinpath(file_dir, "ThreeBusInverter.raw")
threebus_sys = System(sys_file_dir)
slack_bus = [b for b in get_components(Bus, threebus_sys) if b.bustype == BusTypes.REF][1]
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
)
add_component!(threebus_sys, inf_source)
[ Info: The PSS(R)E parser currently supports buses, loads, shunts, generators, branches, transformers, and dc lines [ 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 generator data [ Info: Reading branch data ┌ Warning: Rate 250.0 MW for BUS 1-BUS 3-i_1 is larger than the max expected in the range of (min = 47.0, max = 52.0). └ @ PowerSystems ~/.julia/packages/PowerSystems/N2l8o/src/utils/IO/branchdata_checks.jl:148 ┌ Warning: Rate 250.0 MW for BUS 1-BUS 2-i_2 is larger than the max expected in the range of (min = 47.0, max = 52.0). └ @ PowerSystems ~/.julia/packages/PowerSystems/N2l8o/src/utils/IO/branchdata_checks.jl:148 ┌ Warning: Rate 250.0 MW for BUS 2-BUS 3-i_3 is larger than the max expected in the range of (min = 47.0, max = 52.0). └ @ PowerSystems ~/.julia/packages/PowerSystems/N2l8o/src/utils/IO/branchdata_checks.jl:148 [ Info: Reading branch data [ Info: Reading DC Line data [ Info: Reading storage data
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
.
We will create specific functions to create the components of the inverter as follows:
#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
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:
inner_control() = CurrentControl(
kpv = 0.59, #Voltage controller proportional gain
kiv = 736.0, #Voltage controller integral gain
kffv = 0.0, #Binary variable enabling the voltage feed-forward in output of 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:
dc_source_lv() = FixedDCSource(voltage = 600.0)
#Define a Frequency Estimator as a PLL based on Vikram Kaura and Vladimir Blaskoc 1997 paper:
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:
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.
Similarly we will construct a dynamic generator as follows: 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
)
machine_oneDoneQ (generic function with 1 method)
Shaft
shaft_no_damping() = SingleMass(
3.01, #H (M = 6.02 -> H = M/2)
0.0, #D
)
shaft_no_damping (generic function with 1 method)
AVR: Type I: Resembles a DC1 AVR
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
tg_none() = TGFixed(1.0) #efficiency
#No PSS
pss_none() = PSSFixed(0.0) #Vs
pss_none (generic function with 1 method)
Now we will construct the dynamic generator and inverter.
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 part
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
┌ Warning: struct DynamicGenerator does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/lYELp/src/validation.jl:51 ┌ Warning: struct DynamicInverter does not exist in validation configuration file, validation skipped └ @ InfrastructureSystems ~/.julia/packages/InfrastructureSystems/lYELp/src/validation.jl:51
to_json(threebus_sys, joinpath(file_dir, "threebus_sys.json"), force = true)
[ Info: Serialized time series data to /Users/cbarrows/Documents/repos/SIIPExamples.jl/script/4_PowerSimulationsDynamics_examples/Data/threebus_sys_time_series_storage.h5. [ Info: Serialized System to /Users/cbarrows/Documents/repos/SIIPExamples.jl/script/4_PowerSimulationsDynamics_examples/Data/threebus_sys.json
This notebook was generated using Literate.jl.