Custom Node Types

There are two ways to define custom node types:

Custom Nodes using BlockSystems.jl

PowerDynmaics.jl provides a node constructor to build nodes from BlockSystem.jl objects based on voltage setting blocks:

PowerDynamics.IONodeType
IONode{T,M} <: AbstractNode
IONode(bp::BlockPara)
IONode(blk::IOBlock, parameters::Dict)

Create an IONode based on an IOBlock and a parameter dict.

The block needs to fulfil the folowing interface:

  • inputs: i_r, i_i
  • outputs: u_r, u_i

The block gets the flow sum of the connected lines as an input. It should calculate the resulting node voltage as an output (either per DGL or constraint).

The parameters should be provided as a Dict{Symbol,Float} such as

p = Dict(:a => 1.0,
         :b => -π)
source

Check out the examples/BlockSystems folder for more examples.

There is another constructor to combine several current injecting blocks to a single node:

PowerDynamics.BusNodeFunction
BusNode(inj::BlockPara...; name=gensym(:Bus), verbose=false)

Create an IONode based on several current injectors/draws. Each current injector has to be provided as a BlockPara object, a composite of an IOBlock and a parameter dict. Each IOBlock has to fulfil the interface u_r, u_i ↦ i_r, i_i.

source
PowerDynamics.BlockParaType
BlockPara(block, para; strict=true)

Composite type holdes an IOBlock and a parameter Dict. Parameter Dict may be provided with Symbols (:a) or Symbolic types (blk.a). The latter will be transformed to Symbols.

If strict=true the construtor asserts that the given parameters match the internal parameters of the IOBlock.

source

Component library

PowerDynamics has a submodule IOComponents which contains several predefined building blocks which may be used to build complex nodes.

PowerDynamics.IOComponents.ConstantsMethod
Constants(constants...)

Returns in IOBlock with outputs which are directly mapped to values.

julia> blk = PowerDynamics.IOComponents.Constants(:a=>42, :b=>3.14; name=:const)
IOBlock :const with 2 eqs
  ├ inputs:  (empty)
  ├ outputs: a(t), b(t)
  ├ istates: (empty)
  └ iparams: (empty)

julia> equations(blk)
2-element Vector{Equation}:
 a(t) ~ 42
 b(t) ~ 3.14
source
PowerDynamics.IOComponents.DroopControlMethod
DroopControl(;name, renamings...)

Returns a DroopControl. The name of the system and the names of the vars can be changed with keyword arguments name=:myname, K=:myK, ….

u = - K*(x - x_ref) + u_ref

       +-----------------+
x(t) --| K, x_ref, u_ref |-- u(t)
       +-----------------+

IOBlock :##droop# with 1 eqs
├ inputs:  x(t)
├ outputs: u(t)
├ istates: (empty)
└ iparams: K, x_ref, u_ref
source
PowerDynamics.IOComponents.ImpedanceConstraintMethod
ImpedanceConstraint(;name, renamings...)

Returns a Block that calculates complex current for fixed impedance: i = u/Z

i_r = (R u_r + X u_i)/(R² + X²)
i_i = (R u_i - X u_r)/(R² + X²)

         +-----+
u_r(t) --|  R  |-- i_r(t)
u_i(t) --|  X  |-- i_i(t)
         +-----+
source
PowerDynamics.IOComponents.InversePowerConstraintMethod
InversePowerConstraint(;name, renamings...)

Returns a Block that calculates complex current for fixed complex power: i = conj(S/u)

i_r = (P u_r + Q u_i)/(u_r² + u_i²)
i_i = (P u_i - Q u_r)/(u_r² + u_i²)

         +-----+
u_r(t) --|  P  |-- i_r(t)
u_i(t) --|  Q  |-- i_i(t)
         +-----+
source
PowerDynamics.IOComponents.LowPassFilterMethod
LowPassFilter(;name, renamings...)

Returns a low pass filter. The name of the system and the names of the vars can be changed with keyword arguments name=:myname, τ=:mytau, ….

out'(t) = 1/τ (in(t) - out(t))

           +-----+
input(t) --|  τ  |-- output(t)
           +-----+

IOBlock :##LPF# with 1 eqs
├ inputs:  input(t)
├ outputs: output(t)
├ istates: (empty)
└ iparams: τ
source
PowerDynamics.IOComponents.PowerMethod
Power(;name, renamings...)

Returns a Block which calculates the active and reactive power for a given complex input.

P = uᵣ iᵣ + uᵢ iᵢ
Q = uᵢ iᵣ - uᵣ iᵢ

         +-----+
u_r(t) --|     |-- P(t)
u_i(t) --|     |
i_r(t) --|     |
i_i(t) --|     |-- Q(t)
         +-----+

IOBlock :##power# with 2 eqs
├ inputs:  u_i(t), u_r(t), i_i(t), i_r(t)
├ outputs: P(t), Q(t)
├ istates: (empty)
└ iparams: (empty)
source
PowerDynamics.IOComponents.PowerConstraintMethod
PowerConstraint(;name, renamings...)

Returns a Block that calculates complex voltage for fixed complex power: u = S/conj(i)

u_r = (P i_r - Q i_i)/(i_r² + i_i²)
u_i = (P i_i + Q i_r)/(i_r² + i_i²)

         +-----+
i_r(t) --|  P  |-- u_r(t)
i_i(t) --|  Q  |-- u_i(t)
         +-----+
source
PowerDynamics.IOComponents.VoltageSourceMethod
VoltageSource(;name, renamings...)

Returns a VoltageSource Block. Models the complex voltage dynamic as a low pass inspired by Schiffer et. al. for a reference frequency ω and voltage magnitude V.

   A = 1/τ ⋅ (V/√(uᵢ² + uᵣ²) - 1)
u_r' = -ω uᵢ + A uᵣ
u_i' =  ω uᵣ + A uᵢ

       +-----+
ω(t) --|  τ  |-- u_r(t)
V(t) --|     |-- u_i(t)
       +-----+

IOBlock :##vsource# with 3 eqs
├ inputs:  ω(t), V(t)
├ outputs: u_i(t), u_r(t)
├ istates: A(t)
└ iparams: τ
source

Custom Nodes using the Node Macro

To define your own Node Types, use the PowerDynamics.@DynamicNode macro. The new node type will be a subtype of PowerDynamics.AbstractNode.

PowerDynamics.@DynamicNodeMacro

Macro for creating a new type of dynamic nodes.

Syntax Description:

@DynamicNode MyNewNodeName(Par1, Par2, ...) begin
    [MassMatrix definition]
end begin
    [all prepratory things that need to be run just once]
end [[x1, dx1], [x2, dx2]] begin
    [the actual dynamics equation]
    [important to set the output variables]
end

where MyNewNodeName is the name of the new type of dynamic node, Par1, Par2, ... are the names of the parameters, x1, x2, ... the internal variables of the node and dx1, dx2, ... the corresponding differentials.

In the first block a MassMatrix may be specified. Using the MassMatrix helper function here is recommended. The whole block can be omitted and the identity matrix I is then used as default.

In the second block, the preparation code that needs to be run only once is inserted. Finally, the third block contains the dynamics description, where it's important that the output variables need to be set. These are du and the differentials of the internal variables (here dx1, dx2).

Below are two examples:

@DynamicNode SwingEqParameters(H, P, D, Ω) begin
    @assert D > 0 "damping (D) should be >0"
    @assert H > 0 "inertia (H) should be >0"
    Ω_H = Ω * 2pi / H
end [[ω, dω]] begin
    p = real(u * conj(i_c))
    dϕ = ω # dϕ is only a temp variable that Julia should optimize out
    du = u * im * dϕ
    dω = (P - D*ω - p)*Ω_H
end
@DynamicNode SlackAlgebraicParameters(U) begin
    MassMatrix() # no masses
end begin
    # empty prep block
end [] begin
        du = u - U
end
source
PowerDynamics.MassMatrixFunction
MassMatrix(;m_u::Bool = false, m_int = no_internal_masses)

Creates a massmatrix. Calling:

MassMatrix()

creates a mass matrix with all masses turned off.

Keyword Arguments

  • m_u::Bool=false: Mass matrix value for the complex voltage u.
  • m_int::Vector{Bool}=Vector{Bool}(): A vector representing the diagonal of the mass matrix. Specifies the masses for the remaining/internal variables.
source