API Reference
GridSearchSys
PowerSystemsExperiments.GridSearchSys
— Typemutable 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 toresults_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.
PowerSystemsExperiments.GridSearchSys
— MethodGridSearchSys(
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 ofinjectors
(array ofDynamicInjection
): injectors to use. if one dimensional, all configurations of the given injectors will be returned. If 2d, each row ofinjectors
will represent output configuration.busgroups
(array of String or array ofVector{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 totrue
. whether to include thesim
,sm
,dt
, anderror
columns. Saves disk space to set tofalse
at the cost of not being able to useadd_result!
after running sims.
Returns:
GridSearchSys
: properly initialized GridSearchSys with all the right injectors and the default columnserror
,sim
,sm
, anddt
.
Base.length
— Methodlength(gss::GridSearchSys)
number of total systems in the gridsearch
Base.show
— MethodBase.show(io::IO, gss::GridSearchSys)
pretty printing for GridSearchSys
objects.
Base.size
— Methodsize(gss::GridSearchSys)
returns tuple of (number of systems in gridsearch, number of columns in header)
PowerSystemsExperiments.add_generic_sweep!
— Methodadd_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>
PowerSystemsExperiments.add_lines_sweep!
— Functionadd_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
.
PowerSystemsExperiments.add_result!
— Methodadd_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
.
PowerSystemsExperiments.add_result!
— Methodadd_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
.
PowerSystemsExperiments.add_zipe_sweep!
— Methodadd_zipe_sweep!(gss::GridSearchSys, standardLoadFunction::Union{Function, Missing}, zipe_params::Vector{LoadParams})
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.
PowerSystemsExperiments.execute_sims!
— Methodexecute_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 systemschange::Perturbation
: perturbation to apply to the systemtspan::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)
.
PowerSystemsExperiments.get_permutations
— Methodget_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 lengthk
of combinations of elements ofiterable
Examples
julia> collect(get_permutations([1, 2], 2))
4-element Vector{Vector{Int64}}:
[1, 1]
[1, 2]
[2, 1]
[2, 2]
PowerSystemsExperiments.load_serde_data
— Methodload_serde_data(path::String)
Loads serialized dataframe from file or folder.
If path
is a folder, looks for all non-hidden .jls files, reads them, and concatenates them.
PowerSystemsExperiments.makeSystems
— MethodmakeSystems(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.
This function is deprecated; just use GridSearchSys
, which wraps this.
Args:
sys::System
: the base system to work off ofinjectors
(array ofDynamicInjection
): injectors to use. if one dimensional, all configurations of the given injectors will be returned. If 2d, each row ofinjectors
will represent output configuration.busgroups
(array of String or array ofVector{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.
PowerSystemsExperiments.runSim
— FunctionrunSim(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 simulatechange
: perturbation to apply for the transient simmodel
: model to pass into Simulation()tspan
: time span for transient simulationtstops
: places to force solver timestepssolver
: DE solver for transient simulationdtmax
: maximum timestep for DE solverrun_transient
: whether or not to actually perform the transient simulationlog_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 bemissing
if things failed.
PowerSystemsExperiments.save_serde_data
— Methodsave_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.
PowerSystemsExperiments.set_chunksize!
— Methodset_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)
PowerSystemsExperiments.withName
— MethodwithName(injector::DynamicInjection, name::String)
creates a copy of injector
with the given name
Plotting
PowerSystemsExperiments.makeplots
— Methodmakeplots(
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 oncols
: the column of the dataframe specifying which column of subplots this data should be onlegendgroup
: 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 tocolorlist
after being sorted bycolor_sort_func
ifcolorbar==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 datapointmarkershape
: the column of the dataframe to map to marker shape. Uses shapes provided insymbollist
.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. Ifhide_legend_duplicates==true
, duplicates oftrace_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 hoverslider
: the column of the dataframe specifying which slider value this trace corresponds to. See Slider Configscattertext
: the column of the dataframe specifying the text to appear next to datapoints. Only has effect ifscattermode
contains thetext
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 incols
. Default: identityrow_title_func
: function to get row title from value in column of df passed inrow
. Default: identitylegendgroup_title_func
: function to get the legendgroup title text from the legendgroup name. Default: identitysupertitle
: 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. seeSubplots
.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 ofcolor
before associating withcolorlist
(if no colorbar)symbollist
(default: 14 different symbols): list of symbols to use for the different values ofmarkershape
. 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 totrue
. You may have to set it to false when you have"lines"
in yourscattermode
. The PlotlyJS documentation is not entirely clear on the effect of these options.
Sizes
fontsize
(default 18): font size for most/main textmargin
(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 plotyaxis_home_range
andxaxis_home_range
can beNamedTuple{(:min, :max), <:Tuple{Real, Real}}
legend_location
(default: (x=0.9, y=0.9)): NamedTuple with Float64x
andy
defining the legend location relative to the paper.image_export_size
(default: (height=1200, width=1200)): NamedTuple with Integersheight
andwidth
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
PowerSystemsExperiments.savehtmlplot
— Functionsavehtmlplot(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"
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
: theGridSearchSys
object that this simulation was run from. This allows you to get things like the base system.sim
: thePSID.Simulation
object that holds all the information about this sim.sm
: thePSID.SmallSignalOutput
object returned by the small signal analysiserror
: a string containing any errors thrown (does not include logs printed with@error
) during small signal or transient simulationsdt
: the amount of time this simulation took to run, in nanoseconds.
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
anddt
could bemissing
sim.results
could benothing
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_voltages
— MethodRETURNS 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)
.
PowerSystemsExperiments.get_dt
— Methodgets the time taken to run this simulation.
PowerSystemsExperiments.get_eigenvalues
— Methodgets system eigenvalues from small signal analysis.
PowerSystemsExperiments.get_eigenvectors
— Methodgets system eigenvectors from small signal analysis.
PowerSystemsExperiments.get_error
— Methodgets the string representation of the error raised during simulation (or missing)
PowerSystemsExperiments.get_generator_speeds
— MethodRETURNS 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)
.
PowerSystemsExperiments.get_injector_currents
— MethodRETURNS 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)
.
PowerSystemsExperiments.get_inverter_currents
— MethodRETURNS 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)
.
PowerSystemsExperiments.get_sim
— Methodgets the whole Simulation object.
PowerSystemsExperiments.get_sim_status
— Methodgets the value sim.status
from the Simulation object (or missing).
PowerSystemsExperiments.get_sm
— Methodgets the small signal analysis object sm
PowerSystemsExperiments.get_time
— MethodGets the timestamps for transient results.
PowerSystemsExperiments.get_zipe_load_current_magnitudes
— MethodRETURNS A VECTOR! use a vector of column titles if you want each inverter to be a separate column.
Gets the currrent magnitude time series at each ZIPE load.
PowerSystemsExperiments.get_zipe_load_voltages
— MethodRETURNS 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)
.
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_machine
— MethodAF_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.
PowerSystemsExperiments.GFL_inner_control
— MethodGFL_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
PowerSystemsExperiments.GFL_outer_control
— MethodGFL_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
PowerSystemsExperiments.GFM_inner_control
— MethodGFM_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
PowerSystemsExperiments.VSM_outer_control
— MethodVSM_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
PowerSystemsExperiments.avr_type1
— Methodavr_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
PowerSystemsExperiments.converter_high_power
— Methodconverter_high_power() = AverageConverter(rated_voltage = 138.0, rated_current = 100.0)
PowerSystemsExperiments.dc_source_lv
— Methoddc_source_lv() = FixedDCSource(voltage = 600.0)
Define DC Source as a FixedSource
PowerSystemsExperiments.lc_filt
— Methodlc_filt() = LCFilter(lf = 0.08, rf = 0.003, cf = 0.074)
PowerSystemsExperiments.lcl_filt
— Methodlcl_filt() = LCLFilter(lf = 0.08, rf = 0.003, cf = 0.074, lg = 0.2, rg = 0.01)
PowerSystemsExperiments.pll
— Methodpll() = 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:
PowerSystemsExperiments.pss_none
— Methodpss_none() = PSSFixed(0.0)
fixed PSS with V_s = 0
PowerSystemsExperiments.shaft_no_damping
— Methodshaft_no_damping() = SingleMass(
5.06, # H (M = 6.02 -> H = M/2)
2.0, #0 #D
)
PowerSystemsExperiments.tg_none
— Methodtg_none() = TGFixed(1.0)
turbine governer with perfect efficiency