API Reference

GridSearchSys

PowerSystemsExperiments.GridSearchSysType
mutable struct GridSearchSys
    base::System
    header::Vector{AbstractString}
    sysdict::Dict{Vector{Any}, Function}
    results_header::Vector{String}
    results_getters::Vector{Function}
    df::DataFrame
    chunksize::Union{Int, Float64}
    hfile::String
end

Struct to hold all the systems to iterate through. Create a GridSearchSys with the constructor, whose signature matches the PowerSystemsExperiments.makeSystems method. This will start you off with combinations of injectors. Then you can add sweeps with add_generic_sweep! Use execute_sims! to run simulations for all the systems.

length and size are implemented to give the number of systems and (number of systems, number of columns in header).

Attributes

  • base is the base system. It may be modified, but you should always be able to make any of the actual systems by simply adding to the base.
  • header is a list of the column titles for the dataframe. It should be human-readable.
  • sysdict stores the systems in the format config::Vector => System.
  • results_header is a list of column titles for results.
  • results_getters is a list of functions taking in a GridSearchSystem, Simulation, SmallSignalOutput, and String (error message) which returns results corresponding to results_header.
  • df is a DataFrame which will hold the results.
  • chunksize is the number of rows to hold in memory at a time (before saving to file).
  • hfile is a string of function definitions. This allows us to load the serialized data even if user-defined functions aren't present in a new environment.
source
PowerSystemsExperiments.GridSearchSysMethod
GridSearchSys(
    sys::System, 
    injectors::Union{AbstractArray{DynamicInjection}, AbstractArray{DynamicInjection, 2}}, 
    busgroups::Union{AbstractArray{Vector{String}}, AbstractArray{String}, Nothing} = nothing
    ;
    include_default_results::Bool=true
)

constructor for GridSearchSys with the same behavior as makeSystems.

If include_default_results, automatically adds the columns error, sim, sm, and dt to allow results getter methods to be used after saving. This is very useful if you need all that data afterwards, but it drastically increases the amount of data produced. If you produce too much data, the sims will still run fine, but it might be impossible to load it all back at once (if it doesn't fit in your RAM). TLDR; if you're producing way too much data, try setting include_default_results=false and just calling add_result! only before running the sims.

By default, injector configuration will be represented as a "injector at {bus name or bus names joined by ', '}" for each bus or busgroup.

Args:

  • sys::System : the base system to work off of
  • injectors (array of DynamicInjection): injectors to use. if one dimensional, all configurations of the given injectors will be returned. If 2d, each row of injectors will represent output configuration.
  • busgroups (array of String or array of Vector{String}, optional): if just array of strings, represents list of buses to consider. If array of Vector{String}, each vector of bus names will be grouped together and always receive the same injector type. If not passed, just considers all buses with Generators attached.
  • include_default_results (Bool, optional): defaults to true. whether to include the sim, sm, dt, and error columns. Saves disk space to set to false at the cost of not being able to use add_result! after running sims.

Returns:

  • GridSearchSys: properly initialized GridSearchSys with all the right injectors and the default columns error, sim, sm, and dt.
source
Base.lengthMethod
length(gss::GridSearchSys)

number of total systems in the gridsearch

source
Base.showMethod
Base.show(io::IO, gss::GridSearchSys)

pretty printing for GridSearchSys objects.

source
Base.sizeMethod
size(gss::GridSearchSys)

returns tuple of (number of systems in gridsearch, number of columns in header)

source
PowerSystemsExperiments.add_generic_sweep!Method
add_generic_sweep!(gss::GridSearchSys, title::String, adder::Function, params::Vector)

add arbitrary sweeps to a GridSearch. uses adder to set a parameter of a system to each of the values in params.

  • title is the name of the variable this sweep changes.
  • adder: Fn(System, T) -> System. Modifying the system in-place is OK.
  • params: Vector<T>
source
PowerSystemsExperiments.add_lines_sweep!Function
add_lines_sweep!(gss::GridSearchSys, lineParams::Vector{LineModelParams}, linemodelAdders::Dict{String, Function}=Dict("statpi"=>create_statpi_system, "dynpi"=>create_dynpi_system, "mssb"=>create_MSSB_system))

adds a sweep over line models and parameters. lineParams should be a vector of LineModelParams structs, and linemodelAdders should be a dictionary of {model name (human readable) => func!(system, LineModelParams)} with all the model types to add.

This is based around the TLModels.jl package, so linemodelAdders could for example have the pair "statpi" => create_statpi_system.

source
PowerSystemsExperiments.add_result!Method
add_result!(gss::GridSearchSys, title::AbstractString, getter::Function)

Add a column to the output dataframe with a result to store. If gss.df isn't empty, computes the result and adds it as a column.

getter must have the following signature: (GridSearchSys, Simulation, SmallSignalOutput, String)->(Any)

Any of the inputs might be missing.

source
PowerSystemsExperiments.add_result!Method
add_result!(gss::GridSearchSys, titles::Vector{T}, getter::Function) where T <: AbstractString

add multiple columns to the output dataframe with results to store. If gss.df isn't empty, computes the result and adds it as a column.

getter must have the following signature: (GridSearchSys, Simulation, SmallSignalOutput, String)->(Vector{Any})

the output vector must be the same length as titles (the column titles), and any of the inputs might be missing.

source
PowerSystemsExperiments.add_zipe_sweep!Method
add_zipe_sweep!(gss::GridSearchSys, standardLoadFunction::Union{Function, Missing}, zipe_params::Vector{LoadParams})
Deprecation Warning

This function is deprecated; just use add_generic_sweep!

Adds a ZIPE load sweep to a GridSearchSys. Pass in a standard load to base the ZIPE load off of, and a vector of LoadParams structs to test.

The standard load should be a function which takes in a system and returns an appropriate load. if missing, won't add any standard loads.

source
PowerSystemsExperiments.execute_sims!Method
execute_sims!(
    gss::GridSearchSys, 
    change::PSID.Perturbation; 
    
    tspan::Tuple{Float64, Float64}=(0.48, 0.55), 
    tstops::Vector{Float64}=[0.5]
    dtmax=0.0005, 
    run_transient::Bool=true, 
    log_path::String="sims",
    ida_opts::Dict{Symbol, Any} = Dict(:linear_solver=>:Dense, :max_convergence_failures=>5),
)

run simulations on all of the systems in the gridsearch and store the results in a DataFrame. Fully parallelized.

Args

  • gss::GridSearchSys : the systems
  • change::Perturbation : perturbation to apply to the system
  • tspan::Tuple{Float64, Float64} : time interval (in seconds) to simulate.
  • tstops::Vector{Float64} : tstops argument for DifferentialEquations.jl solver - places to force a solver step.
  • dtmax::Float64 : max timestep for solver (make sure λh is in the feasible region for the solver)
  • run_transient::Bool : whether or not to run the transient simulations.
  • log_path : folder where outputs will be saved (when chunksize is reached).
  • ida_opts : options for IDA integrator. Defaults are usually OK.

Whenever the number of rows in gss.df reaches gss.chunksize, results will be saved to file then deleted from gss.df in order to limit total memory usage.

If gss.chunksize is finite, the final dataframe will be saved. This way all results will be saved, even the last chunk or if chunksize was not reached.

To not save anything to file and keep the results in gss.df, make sure to set_chunksize(gss, Inf).

source
PowerSystemsExperiments.get_permutationsMethod
get_permutations(iterable::Iterable, k::Int)

gets all permutations of iterable of length k with replacement.

Args

  • iterable : some iterable (with well-defined length and indexing)
  • k::Int : desired permutation length

Returns

  • Base.Generator: Generator of vectors of length k of combinations of elements of iterable

Examples

julia> collect(get_permutations([1, 2], 2))
4-element Vector{Vector{Int64}}:
 [1, 1]
 [1, 2]
 [2, 1]
 [2, 2]
source
PowerSystemsExperiments.makeSystemsMethod
makeSystems(sys::System, injectors::Union{AbstractArray{DynamicInjection}, AbstractArray{DynamicInjection, 2}}, busgroups::Union{AbstractArray{Vector{String}}, AbstractArray{String}, Nothing}=nothing)

makes all configurations of the given system and injectors, or specific given configurations.

Deprecation Warning

This function is deprecated; just use GridSearchSys, which wraps this.

Args:

  • sys::System : the base system to work off of
  • injectors (array of DynamicInjection): injectors to use. if one dimensional, all configurations of the given injectors will be returned. If 2d, each row of injectors will represent output configuration.
  • busgroups (array of String or array of Vector{String}, optional): if just array of strings, represents list of buses to consider. If array of Vector{String}, each vector of bus names will be grouped together and always receive the same injector type. If not passed, just considers all buses with Generators attached.

Returns:

  • Dict{Vector{String}, System}: dictionary of {generator names (ordered) => system}. contains all variations.
source
PowerSystemsExperiments.runSimFunction
runSim(system, change=BranchTrip(0.5, ACBranch, "Bus 5-Bus 4-i_1"), model=ResidualModel, tspan=(0., 5.), solver=IDA(linear_solver=:LapackDense, max_convergence_failures=5), dtmax=0.001, run_transient=true, log_path::String=mktempdir())

little wrapper to run simulations

Args:

  • system: the system to simulate
  • change: perturbation to apply for the transient sim
  • model: model to pass into Simulation()
  • tspan: time span for transient simulation
  • tstops: places to force solver timesteps
  • solver: DE solver for transient simulation
  • dtmax: maximum timestep for DE solver
  • run_transient: whether or not to actually perform the transient simulation
  • log_path: path for simulation logs.

Returns:

  • (Simulation, SmallSignalOutput, UInt64, String): the Simulation object, the small signal analysis object, the time in nanoseconds the simulation took, and the error message. All but the time might be missing if things failed.
source
PowerSystemsExperiments.save_serde_dataMethod
save_serde_data(gss::GridSearchSys, path::String)

serializes the GridSearchSys and saves it to path to be read later using Serialization.deserialize (through load_serde_data)

deletes all .jls files and the .gss file in path if path is a directory containing those files.

source
PowerSystemsExperiments.set_chunksize!Method
set_chunksize!(gss::GridSearchSys, chunksize::Union{Int, Float64})

set the number of rows save in each file (and thus how many to hold in memory before saving to file).

Set to Inf to hold all rows in memory (useful for small datasets and to allow use of the dataframe immediately after running the sims)

source

Plotting

PowerSystemsExperiments.makeplotsMethod
makeplots(
    df::DataFrame;

    rows::Union{AbstractString, Symbol, Nothing}=nothing,
    cols::Union{AbstractString, Symbol, Nothing}=nothing,
    legendgroup::Union{AbstractString, Symbol, Nothing}=nothing,
    markershape::Union{AbstractString, Symbol, Nothing}=nothing,
    trace_names::Union{AbstractString, Symbol, Nothing}=nothing,
    color::Union{AbstractString, Symbol, Nothing}=nothing,
    opacity::Union{AbstractString, Symbol, Real}=1.0,
    markersize::Union{AbstractString, Symbol, Real}=8,
    hovertext::Union{AbstractString, Symbol, Nothing}=nothing,
    scattermode::Union{AbstractString, Symbol}="markers",
    scattertext::Union{AbstractString, Symbol, Nothing}=nothing,
    
    colorbar::Bool=false,
    map_to_colorbar::Bool=true,
    marker_line_width::Union{Real, Nothing} = nothing,

    slider_trace_id::Union{AbstractString, Symbol, Nothing}=nothing,

    row_sort_func::Function = identity,
    col_sort_func::Function = identity,
    color_sort_func::Function = identity,
    slider_sort_func::Function = identity,


    slider::Union{AbstractString, Symbol, Nothing}=nothing,
    slider_label_func::Function = x->round(x, sigdigits=2),
    slider_current_value_prefix::Union{AbstractString, Nothing} = nothing,
    x::Union{AbstractString, Symbol, NamedTuple{(:x0, :dx), <:Tuple{Real, Real}}, Vector{<:Real}}=nothing,
    y::Union{AbstractString, Symbol, NamedTuple{(:y0, :dy), <:Tuple{Real, Real}}, Vector{<:Real}}=nothing,
    x_title::Union{AbstractString, Nothing} = nothing,
    y_title::Union{AbstractString, Nothing} = nothing,

    col_title_func::Function=identity,
    row_title_func::Function=identity,
    legendgroup_title_func::Function=identity,
    supertitle::Union{AbstractString, Nothing} = nothing,

    yaxis_home_range::Union{NamedTuple{(:min, :max), <:Tuple{Real, Real}}, Nothing} = nothing,
    xaxis_home_range::Union{NamedTuple{(:min, :max), <:Tuple{Real, Real}}, Nothing} = nothing,

    image_export_filename::String = "saved_plot",
    image_export_size::@NamedTuple{height::Int64, width::Int64}=(height=1200, width=1200),
    colorlist::Union{Vector{String}, Vector{<:Colors.Colorant}} = Colors.distinguishable_colors(10),
    symbollist::Vector{<:AbstractString} = ["circle", "square", "diamond", "cross", "triangle-up", "star", "circle-cross", "y-up", "circle-open", "square-open", "diamond-open", "cross-open", "triangle-up-open", "star-open"],
    colorbar_args::Dict = Dict(attr(autocolorscale=true, colorbar=attr(outlinecolor=colorant"black", outlinewidth=1))),
    use_webgl::Bool = false,
    hide_legend_duplicates::Bool = true,
    legend_location::@NamedTuple{x::Float64, y::Float64} = (x=0.9, y=0.9),
    legend_visible::Bool=true,
    shared_xaxes::Union{String, Bool}="all",
    shared_yaxes::Union{String, Bool}="all",
    fontsize::Real=18,
    offsets::NamedTuple{(:xtitle, :ytitle, :coltitle, :rowtitle), <:Tuple{Real, Real, Real, Real}} = (xtitle = -0.03, ytitle=-0.05, coltitle=0.01, rowtitle=0.01),
    subplotgaps::NamedTuple{(:horizontal_spacing, :vertical_spacing), <:Tuple{Real, Real}}=(horizontal_spacing=0.1, vertical_spacing=0.06),
)

Plot data from a dataframe. allows high dimensional data through a grid of subplots, trace colors, and a slider.

  • 2 variables for (x, y) on plot
  • 2 variables for row and column in subplot grid
  • 1 variable for color
  • 1 variable for marker symbol
  • 1 variable on a slider
  • 1 variable on hovertext
  • 1 variable on marker text
  • 1 variable on opacity
  • 1 variable on trace name
  • 1 variable for trace or datapoint size/thickness
  • 0 variables for legend group (typically can't work independently; pair with color or marker symbol)

= 12 max variables

In reality, you should use less. For example, it's impossible to read the opacity correctly, so you should make hovertext and opacity the same. Also, if you don't need that many variables, you can just leave them out. If you don't specify what to separate along subplot rows and columns, makeplots will just give you a single subplot.

Notes

  • colorbar doesn't work very well with line plots. If you want to vary color by trace, that's ok, just set the colorlist to make the legend effectively a discrete colorbar.
  • for slider:
    • make sure that each slider value has the exact same number of traces
    • either provide the slider_trace_id or sort the dataframe such that subsetting to each slider value gives the remaining traces in the same order every time

Arguments

Mandatory Arguments

  • df: the DataFrame to get data from.

DATA TO PLOT

x and y can be:

  • AbstractString, Symbol: get from column of df
  • NamedTuple of (x0, dx) or (y0, dy): evenly spaced values with given parameters
  • Vector{<:Real}: this specific array every time

Use the data_sigdigits (default: 6) argument to specify how much to round this input data. This significantly helps reduce the size of the exported HTML files.

If you choose to get the series from the dataframe, each element of the column will be one series/trace, so it better be a vector! If you have scalar or categorical values, try the default PlotlyJS.plot(df, ...) - it's pretty good for that.

Specifying which columns to use for what

  • rows: the column of the dataframe specifying which row of subplots this data should be on
  • cols: the column of the dataframe specifying which column of subplots this data should be on
  • legendgroup: the column of the dataframe specifying which legendgroup this trace should be. Value of this column is the legendgroup name.
  • color: the column of the dataframe specifying the color of this trace (or the color of each datapoint in this trace). Mapped to colorlist after being sorted by color_sort_func if colorbar==false, otherwise used raw for colorbar.
  • opacity (default: 1.0): the opacity of all datapoints OR the column of the dataframe specifying the opacity of each datapoint
  • markershape: the column of the dataframe to map to marker shape. Uses shapes provided in symbollist.
  • markersize (default: 8): the size of all markers OR the column of the dataframe specifying the size of each trace or datapoint.
  • trace_names: the column of the dataframe specifying the text name of this trace. Visible in legend and on hover. If hide_legend_duplicates==true, duplicates of trace_names will not be seen in each legend group.
  • hovertext: the column of the dataframe specifying any additional text that appears for this trace on hover
  • slider: the column of the dataframe specifying which slider value this trace corresponds to. See Slider Config
  • scattertext: the column of the dataframe specifying the text to appear next to datapoints. Only has effect if scattermode contains the text flag.

Titles

  • x_title: x axis title for every plot.
  • y_title: y axis title for every plot.
  • col_title_func: function to get column title from value in column of df passed in cols. Default: identity
  • row_title_func: function to get row title from value in column of df passed in row. Default: identity
  • legendgroup_title_func: function to get the legendgroup title text from the legendgroup name. Default: identity
  • supertitle: Biggest title on the graph.

Legend & Axes

  • colorbar (default: false): whether to display the colorbar. Can be finnicky, only really works for non-line plots.
  • colorbar_args: Dictionary of parameters for the colorbar. See Julia PlotlyJS docs for Layout.coloraxis.
  • shared_xaxes, shared_yaxes: how to share axes. see Subplots.
  • hide_legend_duplicates (default: true): hide duplicate trace names for each legend group.
  • legend_visible (default: true): show the legend

Markers & Colors

  • scattermode (default: "markers"): mode for scatterplot. Some combination of "markers", "lines", and "text", joined with "+". ie, "markers+text".
  • colorlist (default: 10 ok colors): list of colors to use. If you have more than 10 different values you want represented by color, or you want a specific set of colors, set this array.
  • color_sort_func (default: identity): function by which to sort values of color before associating with colorlist (if no colorbar)
  • symbollist (default: 14 different symbols): list of symbols to use for the different values of markershape. If you have more than 14 different values, set this array.
  • use_webgl (default: true): Whether to use scattergl or plain scatter.
  • map_to_colorbar (default: true): whether to associate markers with colorbar and show the scale. Generally keep this set to true. You may have to set it to false when you have "lines" in your scattermode. The PlotlyJS documentation is not entirely clear on the effect of these options.

Sizes

  • fontsize (default 18): font size for most/main text
  • margin (default: 200): Int, just the margin size (px)
  • offsets (default: (xtitle = -0.03, ytitle=-0.05, coltitle=0.01, rowtitle=0.01)): where to put the titles, as a fraction of the plot
  • yaxis_home_range and xaxis_home_range can be NamedTuple{(:min, :max), <:Tuple{Real, Real}}
  • legend_location (default: (x=0.9, y=0.9)): NamedTuple with Float64 x and y defining the legend location relative to the paper.
  • image_export_size (default: (height=1200, width=1200)): NamedTuple with Integers height and width specifying the size in pixels of the SVG plots saved with the "download plot" button.
    • image_export_filename (default: "saved_plot"): default filename when image is exported using button on plot.

Slider Config

  • slider_sort_func (default: identity): a function to get keys for the sort of the slider labels. For example, (x -> -x) would reverse the slider.
  • slider_trace_id: the column in the dataframe corresponding to a trace identifier. There must be exactly one row for each unique combination of this column and the slider column. If not passed, will use the order of the dataframe.
  • slider_label_func (default: x->round(x, sigdigits=2)): gets slider tick label from value.
  • slider_current_value_prefix: prefix to put before the value on the slider's label. For example, "val: " would make the label "val: 0.5" or something
source
PowerSystemsExperiments.savehtmlplotFunction
savehtmlplot(plot, filename::String=nothing)

utility to save plot to filename.html. Default filename is the plot's image download filename, if it exists, or worst case just "plot.html"

source

Results Getters

These functions allow you to store results of your simulations. Feel free to write your own; these are just here to get you started.

They all return either a value or a vector of values. If they return a vector, this vector will still be entered into the DataFrame as ONE entry, unless you pass a vector of titles with the same length as the returned vector of values to add_result!. In that case, each entry in titles will be its own column.

All results getters have the following signature:

getter(
    gss::GridSearchSys, 
    sim::Union{Simulation, Missing}, 
    sm::Union{PSID.SmallSignalOutput, Missing}, 
    error::Union{String, Missing},
    dt::Real
)

These arguments are:

  • gss: the GridSearchSys object that this simulation was run from. This allows you to get things like the base system.
  • sim: the PSID.Simulation object that holds all the information about this sim.
  • sm: the PSID.SmallSignalOutput object returned by the small signal analysis
  • error: a string containing any errors thrown (does not include logs printed with @error) during small signal or transient simulations
  • dt: the amount of time this simulation took to run, in nanoseconds.
Future Changes Possible

These semantics are precisely the type of thing that should be encoded in a results type/struct. This would make getting results significantly more clean, abstracted, and flexible. As such, this interface may change in the future.

When you write your own getters, there are a few things to keep in mind:

  • Any of the arguments except gss and dt could be missing
  • sim.results could be nothing if the setup failed (if powerflow fails to solve, for example)
  • make sure your getter's signature matches the format, or it will throw type errors.
  • These getter functions should be configuration-agnostic. If you need to do something different based on the sweep parameters relevant to this run, you should do that via direct manipulation of the results dataframe.
PowerSystemsExperiments.get_bus_voltagesMethod

RETURNS A VECTOR! use a vector of column titles if you want each bus to be a separate column.

gets voltage time series at every bus in the system.

returns them in the same order as get_components(Generator, gss.base).

source
PowerSystemsExperiments.get_generator_speedsMethod

RETURNS A VECTOR! use a vector of column titles if you want each generator to be a separate column.

gets speed (in rad/s) of all generators in the system.

returns them in the same order as get_components(Generator, gss.base).

source
PowerSystemsExperiments.get_injector_currentsMethod

RETURNS A VECTOR! use a vector of column titles if you want each inverter to be a separate column.

Returns a vector with an entry for each Generator in the base system. Each entry is a time series of the current magnitude.

returns them in the same order as get_components(Generator, gss.base).

source
PowerSystemsExperiments.get_inverter_currentsMethod

RETURNS A VECTOR! use a vector of column titles if you want each inverter to be a separate column.

Returns a vector with an entry for each Generator in the base system. If there is an inverter there, the entry is a time series of the current magnitude. Otherwise, it's missing.

returns them in the same order as get_components(Generator, gss.base).

source
PowerSystemsExperiments.get_zipe_load_voltagesMethod

RETURNS A VECTOR! use a vector of column titles if you want each load to be a separate column.

Gets the voltage magnitude time series at all ZIPE loads.

returns them in the same order as get_components(Generator, gss.base).

source

Sample Models

device_models.jl just contains some sample generator and inverter components with sane parameter values to help you get started easily. Not written by me; not sure who wrote these or where the numbers come from. Will update if I find out.

PowerSystemsExperiments.AF_machineMethod
AF_machine() = AndersonFouadMachine(
    0.0041, # R::Float64
    1.25, # Xd::Float64
    1.22, # Xq::Float64
    0.232, # Xd_p::Float64
    0.715, # Xq_p::Float64
    0.12, # Xd_pp::Float64
    0.12, # Xq_pp::Float64
    4.75, # Td0_p::Float64
    1.5, # Tq0_p::Float64
    0.06, # Td0_pp::Float64
    0.21# Tq0_pp::Float64
)

Defines a sample AF machine for testing purposes.

source
PowerSystemsExperiments.GFL_inner_controlMethod
GFL_inner_control() = CurrentModeControl(
    kpc = 1.27,     #Current controller proportional gain
    kic = 14.3,     #Current controller integral gain
    kffv = 0.0,     #Binary variable enabling the current feed-forward in output of current controllers
)

Define an Inner Control as a Current Controler

source
PowerSystemsExperiments.GFL_outer_controlMethod
GFL_outer_control() = OuterControl(
    ActivePowerPI(Kp_p = 0.0059 , Ki_p = 7.36, ωz = 1000.0, P_ref = 0.5),
    ReactivePowerPI(Kp_q = 0.0059, Ki_q = 7.36, ωf = 1000.0, V_ref = 1.0, Q_ref = 0.1)
)

Define a GFL outer control as a composition of Active Power PI + Reactive Power PI

source
PowerSystemsExperiments.GFM_inner_controlMethod
GFM_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,      #Active damping gain
)

Define an Inner Control as a Voltage+Current Controler with Virtual Impedance

source
PowerSystemsExperiments.VSM_outer_controlMethod
VSM_outer_control() = OuterControl(
    VirtualInertia(Ta = 2.0, kd = 400.0, kω = 20.0),
    ReactivePowerDroop(kq = 0.2, ωf = 1000.0),
)

Define a GFM Outer Control as a composition of Virtual Inertia + Reactive Power Droop

source
PowerSystemsExperiments.avr_type1Method
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
)

Sample AVR (Type I); resembles a DC1 AVR

source
PowerSystemsExperiments.pllMethod
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 a Frequency Estimator as a PLL based on Vikram Kaura and Vladimir Blaskoc 1997 paper:

source