Inverter Modeling simulation
To follow along, you can download this tutorial as a Julia script (.jl) or Jupyter notebook (.ipynb).
Originally Contributed by: José Daniel Lara
Introduction
This tutorial will introduce the modeling of an inverter with Virtual Inertia in a multi-machine model of the system. We will load the data directly from PSS/e dynamic files.
The tutorial uses a modified 14-bus system on which all the synchronous machines have been substituted by generators with ESAC1A AVR's and no Turbine Governors.
In the first portion of the tutorial we will simulate the system with the original data and cause a line trip between Buses 2 and 4. In the second part of the simulation, we will switch generator 6 with a battery using an inverter and perform the same fault.
Load the packages
using PowerSimulationsDynamics
using PowerSystemCaseBuilder
using PowerSystems
const PSY = PowerSystems
using PowerFlows
using Logging
using Sundials
using PlotsPowerSystemCaseBuilder.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
Create the system using PowerSystemCaseBuilder.jl:
sys = build_system(PSIDSystems, "14 Bus Base Case")| System | |
| Property | Value |
|---|---|
| Name | |
| Description | |
| System Units Base | SYSTEM_BASE |
| Base Power | 100.0 |
| Base Frequency | 60.0 |
| Num Components | 77 |
| Static Components | |
| Type | Count |
|---|---|
| ACBus | 14 |
| Arc | 20 |
| Area | 1 |
| Line | 16 |
| LoadZone | 1 |
| StandardLoad | 11 |
| TapTransformer | 3 |
| ThermalStandard | 5 |
| Transformer2W | 1 |
| Dynamic Components | |
| Type | Count |
|---|---|
| DynamicGenerator{RoundRotorQuadratic, SingleMass, ESAC1A, GasTG, PSSFixed} | 1 |
| DynamicGenerator{RoundRotorQuadratic, SingleMass, ESAC1A, TGFixed, PSSFixed} | 4 |
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.
Define Simulation Problem with a 20 second simulation period and the branch trip at t = 1.0:
sim = Simulation(
ResidualModel, #Type of model used
sys, #system
mktempdir(), #path for the simulation output
(0.0, 20.0), #time span
BranchTrip(1.0, Line, "BUS 02-BUS 04-i_1");
console_level = Logging.Info,
)| Simulation Summary | |
| Property | Value |
|---|---|
| Status | BUILT |
| Simulation Type | Residual Model |
| Initialized? | Yes |
| Multimachine system? | Yes |
| Time Span | (0.0, 20.0) |
| Number of States | 86 |
| Number of Perturbations | 1 |
Now that the system is initialized, we can verify the system states for potential issues.
show_states_initial_value(sim)Voltage Variables
====================
BUS 01
====================
Vm 1.06
θ 0.0
====================
BUS 02
====================
Vm 1.04
θ -0.0711
====================
BUS 03
====================
Vm 1.01
θ -0.1787
====================
BUS 04
====================
Vm 1.0129
θ -0.1458
====================
BUS 05
====================
Vm 1.0165
θ -0.1235
====================
BUS 06
====================
Vm 1.06
θ -0.1949
====================
BUS 07
====================
Vm 1.0438
θ -0.1812
====================
BUS 08
====================
Vm 1.08
θ -0.1656
====================
BUS 09
====================
Vm 1.0263
θ -0.2102
====================
BUS 10
====================
Vm 1.0245
θ -0.2125
====================
BUS 11
====================
Vm 1.0384
θ -0.2059
====================
BUS 12
====================
Vm 1.0436
θ -0.2105
====================
BUS 13
====================
Vm 1.0372
θ -0.2119
====================
BUS 14
====================
Vm 1.0126
θ -0.2291
====================
====================
Differential States
generator-3-1
====================
eq_p 1.0649
ed_p 0.1243
ψ_kd 0.9872
ψ_kq 0.2132
δ 0.034
ω 1.0
Vm 1.01
Vr1 0.006
Vr2 2.419
Ve 1.791
Vr3 -0.0726
====================
Differential States
generator-8-1
====================
eq_p 1.2657
ed_p 0.0462
ψ_kd 1.1584
ψ_kq 0.1748
δ 0.019
ω 1.0
Vm 1.08
Vr1 0.0097
Vr2 3.9162
Ve 2.8839
Vr3 -0.1175
====================
Differential States
generator-1-1
====================
eq_p 1.0604
ed_p -0.0111
ψ_kd 1.0563
ψ_kq 0.1134
δ 0.1684
ω 1.0
Vm 1.06
Vr1 0.0049
Vr2 1.951
Ve 1.4049
Vr3 -0.0585
x_g1 0.3144
x_g2 0.3144
x_g3 0.3144
====================
Differential States
generator-2-1
====================
eq_p 1.1038
ed_p 0.1491
ψ_kd 1.003
ψ_kq 0.2748
δ 0.1963
ω 1.0
Vm 1.04
Vr1 0.0071
Vr2 2.8613
Ve 2.1338
Vr3 -0.0858
====================
Differential States
generator-6-1
====================
eq_p 1.167
ed_p 0.0955
ψ_kd 1.08
ψ_kq 0.3084
δ 0.1387
ω 1.0
Vm 1.06
Vr1 0.0082
Vr2 3.2875
Ve 2.4472
Vr3 -0.0986
====================We execute the simulation with an additional tolerance for the solver set at 1e-8:
execute!(sim, IDA(); abstol = 1e-8)SIMULATION_FINALIZED::BUILD_STATUS = 6Using PowerSimulationsDynamics tools for exploring the results, we can plot all the voltage results for the buses:
result = read_results(sim)
p = plot();
for b in get_components(ACBus, sys)
voltage_series = get_voltage_magnitude_series(result, get_number(b))
plot!(
p,
voltage_series;
xlabel = "Time",
ylabel = "Voltage Magnitude [pu]",
label = "Bus - $(get_name(b))",
)
end
pWe can also explore the frequency of the different generators
p2 = plot();
for g in get_components(ThermalStandard, sys)
state_series = get_state_series(result, (get_name(g), :ω))
plot!(
p2,
state_series;
xlabel = "Time",
ylabel = "Speed [pu]",
label = "$(get_name(g)) - ω",
)
end
p2It is also possible to explore the small signal stability of this system we created.
res = small_signal_analysis(sim)The system is small signal stable
The eigenvalues can be explored
res.eigenvalues58-element Vector{ComplexF64}:
-1000.0000000000017 + 0.0im
-1000.0000000000011 + 0.0im
-1000.0000000000003 + 0.0im
-999.9999999999995 + 0.0im
-999.9999999999983 + 0.0im
-51.83226146210246 + 0.0im
-51.702828654896265 + 0.0im
-51.44178338585979 - 0.018280166539365712im
-51.44178338585979 + 0.018280166539365712im
-51.408038558577836 + 0.0im
⋮
-0.8294145253846161 - 0.04425340347913098im
-0.8294145253846161 + 0.04425340347913098im
-0.6364256141508065 + 0.0im
-0.5 + 0.0im
-0.46850110521490756 + 0.0im
-0.2825569450439846 + 0.0im
-0.22460226197094324 - 7.678563001298193im
-0.22460226197094324 + 7.678563001298193im
0.0 + 0.0imModifying the system and adding storage
Reload the system for this example:
sys = build_system(PSIDSystems, "14 Bus Base Case")
# We want to remove the generator 6 and the dynamic component attached to it.
thermal_gen = get_component(ThermalStandard, sys, "generator-6-1")
remove_component!(sys, get_dynamic_injector(thermal_gen))
remove_component!(sys, thermal_gen)
# We also change
# We can now define our storage device and add it to the system
storage = EnergyReservoirStorage(;
name = "Battery",
available = true,
bus = get_component(Bus, sys, "BUS 06"),
prime_mover_type = PrimeMovers.BA,
storage_technology_type = StorageTech.LIB,
storage_capacity = 100.0,
storage_level_limits = (min = 0.05, max = 1.0),
initial_storage_capacity_level = 0.5,
rating = 1.0,
active_power = 0.6,
input_active_power_limits = (min = 0.0, max = 1.0),
output_active_power_limits = (min = 0.0, max = 1.0),
efficiency = (in = 0.80, out = 0.90),
reactive_power = 0.16,
reactive_power_limits = (min = -1.0, max = 1.0),
base_power = 25.0,
)
add_component!(sys, storage)A good sanity check it running a power flow on the system to make sure all the components are properly scaled and that the system is properly balanced. We can use PowerSystems to perform this check. We can get the results back and perform a sanity check.
res = solve_power_flow(ACPowerFlow(), sys)
res["bus_results"]| bus_number | Vm | θ | P_gen | P_load | P_net | Q_gen | Q_load | Q_net |
|---|---|---|---|---|---|---|---|---|
| Int64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | Float64 |
| 1 | 1.06 | 0.0 | 204.63182906121756 | 0.0 | 204.63182906121756 | -0.6544532581482285 | 0.0 | -0.6544532581482285 |
| 2 | 1.04 | -0.0756785116259242 | 30.0 | 0.0 | 30.0 | 31.355533771378564 | 0.0 | 31.355533771378564 |
| 3 | 1.01 | -0.1872016807793526 | 19.999999999999996 | 0.0 | 19.999999999999996 | 23.524129532810857 | 0.0 | 23.524129532810857 |
| 4 | 1.0116017921881253 | -0.1538174488968855 | -5.551115123125783e-15 | 0.0 | -5.551115123125783e-15 | 0.0 | 0.0 | 0.0 |
| 5 | 1.0152809910946499 | -0.13077903016636822 | -1.3877787807814457e-15 | 0.0 | -1.3877787807814457e-15 | 0.0 | 0.0 | 0.0 |
| 6 | 1.06 | -0.20881729364765783 | 15.0 | 0.0 | 15.0 | 17.955334814367514 | 0.0 | 17.955334814367514 |
| 7 | 1.0425337653833038 | -0.19224293380273696 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 8 | 1.08 | -0.17659754115599705 | 10.0 | 0.0 | 10.0 | 23.049295610470786 | 0.0 | 23.049295610470786 |
| 9 | 1.024403703684157 | -0.22295412499156467 | 0.0 | 0.0 | 0.0 | 2.7755575615628914e-15 | 0.0 | 2.7755575615628914e-15 |
| 10 | 1.0226106582258314 | -0.2257490090663724 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
After verifying that the system works, we can define our inverter dynamics and add it to the battery that has already been stored in the system.
inverter = DynamicInverter(;
name = get_name(storage),
ω_ref = 1.0, # ω_ref,
converter = AverageConverter(; rated_voltage = 138.0, rated_current = 100.0),
outer_control = OuterControl(
VirtualInertia(; Ta = 2.0, kd = 400.0, kω = 20.0),
ReactivePowerDroop(; kq = 0.2, ωf = 1000.0),
),
inner_control = VoltageModeControl(;
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,
),
dc_source = FixedDCSource(; voltage = 600.0),
freq_estimator = 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
),
filter = LCLFilter(; lf = 0.08, rf = 0.003, cf = 0.074, lg = 0.2, rg = 0.01),
)
add_component!(sys, inverter, storage)These are the current system components:
sys| System | |
| Property | Value |
|---|---|
| Name | |
| Description | |
| System Units Base | SYSTEM_BASE |
| Base Power | 100.0 |
| Base Frequency | 60.0 |
| Num Components | 77 |
| Static Components | |
| Type | Count |
|---|---|
| ACBus | 14 |
| Arc | 20 |
| Area | 1 |
| EnergyReservoirStorage | 1 |
| Line | 16 |
| LoadZone | 1 |
| StandardLoad | 11 |
| TapTransformer | 3 |
| ThermalStandard | 4 |
| Transformer2W | 1 |
| Dynamic Components | |
| Type | Count |
|---|---|
| DynamicGenerator{RoundRotorQuadratic, SingleMass, ESAC1A, GasTG, PSSFixed} | 1 |
| DynamicGenerator{RoundRotorQuadratic, SingleMass, ESAC1A, TGFixed, PSSFixed} | 3 |
| DynamicInverter{AverageConverter, OuterControl, VoltageModeControl, FixedDCSource, KauraPLL, LCLFilter, Nothing} | 1 |
Define Simulation problem using the same parameters:
sim = Simulation(
ResidualModel, #Type of model used
sys, #system
mktempdir(), #path for the simulation output
(0.0, 20.0), #time span
BranchTrip(1.0, Line, "BUS 02-BUS 04-i_1");
console_level = Logging.Info,
)| Simulation Summary | |
| Property | Value |
|---|---|
| Status | BUILT |
| Simulation Type | Residual Model |
| Initialized? | Yes |
| Multimachine system? | Yes |
| Time Span | (0.0, 20.0) |
| Number of States | 94 |
| Number of Perturbations | 1 |
We can verify the small signal stability of the system before running the simulation:
res = small_signal_analysis(sim)The system is small signal stable
Exploring the eigenvalues:
res.eigenvalues66-element Vector{ComplexF64}:
-2268.0867822853024 - 6832.691928678376im
-2268.0867822853024 + 6832.691928678376im
-2094.489756467523 - 6527.127108333034im
-2094.489756467523 + 6527.127108333034im
-1615.3517316545517 - 290.9591007369999im
-1615.3517316545517 + 290.9591007369999im
-1000.0000000000019 + 0.0im
-1000.0000000000015 + 0.0im
-1000.0000000000007 + 0.0im
-1000.0000000000002 + 0.0im
⋮
-1.0079673594546996 + 0.0im
-0.9363180292979066 + 0.0im
-0.7915474210230488 + 0.0im
-0.6203962737229913 + 0.0im
-0.5 + 0.0im
-0.25257135557914234 + 0.0im
-0.2362820394352636 - 7.659994104964647im
-0.2362820394352636 + 7.659994104964647im
0.0 + 0.0imWe execute the simulation
execute!(sim, IDA(); abstol = 1e-6)SIMULATION_FINALIZED::BUILD_STATUS = 6Using PowerSimulationsDynamics tools for exploring the results, we can plot all the voltage results for the buses
result = read_results(sim)
p = plot();
for b in get_components(ACBus, sys)
voltage_series = get_voltage_magnitude_series(result, get_number(b))
plot!(
p,
voltage_series;
xlabel = "Time",
ylabel = "Voltage Magnitude [pu]",
label = "Bus - $(get_name(b))",
)
end
pWe can also explore the frequency of the different static generators and storage
p2 = plot();
for g in get_components(ThermalStandard, sys)
state_series = get_state_series(result, (get_name(g), :ω))
plot!(
p2,
state_series;
xlabel = "Time",
ylabel = "Speed [pu]",
label = "$(get_name(g)) - ω",
)
end
state_series = get_state_series(result, ("Battery", :ω_oc))
plot!(p2, state_series; xlabel = "Time", ylabel = "Speed [pu]", label = "Battery - ω_vsm");
p2