Modeling Concepts

Terminal

The Terminal─Connector is an important building block for every model. It represents a connection point with constant voltage in dq─cordinates u_r and u_i and enforces the kirchoff constraints sum(i_r)=0 and sum(i_i)=0.

Modeling of Buses

Model class Injector

An injector is a class of components with a single Terminal() (called :terminal). Examples for injectors might be Generators, Shunts, Loads.

      ┌───────────┐
(t)   │           │
 o←───┤  Injector │
      │           │
      └───────────┘

The current for injectors is always in injector convention, i.e. positive currents flow out of the injector towards the terminal.

Model classes

Model "classes" are nothing formalized. In this document, a model class is just a description for some ODESystem from ModelingToolkit.jl, which satisfies certain requirements. For example, any ODESystem is considered an "Injector" if it contains a connector Terminal() called :terminal.

Code example: definition of PQ load as injector
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
using ModelingToolkit: D_nounits as Dt, t_nounits as t
@mtkmodel MyPQLoad begin
    @components begin
        terminal = Terminal()
    end
    @parameters begin
        Pset, [description="Active Power demand"]
        Qset, [description="Reactive Power demand"]
    end
    @variables begin
        P(t), [description="Active Power"]
        Q(t), [description="Reactive Power"]
    end
    @equations begin
        P ~ terminal.u_r*terminal.i_r + terminal.u_i*terminal.i_i
        Q ~ terminal.u_i*terminal.i_r - terminal.u_r*terminal.i_i
        # if possible, its better for the solver to explicitly provide algebraic equations for the current
        terminal.i_r ~ (Pset*terminal.u_r + Qset*terminal.u_i)/(terminal.u_r^2 + terminal.u_i^2)
        terminal.i_i ~ (Pset*terminal.u_i - Qset*terminal.u_r)/(terminal.u_r^2 + terminal.u_i^2)
    end
end

Model class MTKBus

A MTKBus isa class of models, which are used to describe the dynamic behavior of a full bus in a power grid. Each MTKBus musst contain a predefined model of type BusBar() (named :busbar). This busbar represents the connection point to the grid. Optionally, it may contain various injectors.

 ┌───────────────────────────────────┐
 │ MTKBus             ┌───────────┐  │
 │  ┌──────────┐   ┌──┤ Generator │  │
 │  │          │   │  └───────────┘  │
 │  │  BusBar  ├───o                 │
 │  │          │   │  ┌───────────┐  │
 │  └──────────┘   └──┤ Load      │  │
 │                    └───────────┘  │
 └───────────────────────────────────┘

Sometimes it is not possible to connect all injectors directly but instead one needs or wants Branches between the busbar and injector terminal. As long as the :busbar is present at the toplevel, there are few limitations on the overall model complexity.

For simple models (direct connections of a few injectors) it is possible to use the convenience method MTKBus(injectors...) to create the composite model based on provide injector models.

Code example: definition of a Bus containing a swing equation and a load
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
@mtkmodel MyMTKBus begin
    @components begin
        busbar = BusBar()
        swing = Swing()
        load = PQLoad()
    end
    @equations begin
        connect(busbar.terminal, swing.terminal)
        connect(busbar.terminal, load.terminal)
    end
end

Alternativly, for that system you could have just called

mybus = MTKBus(Swing(;name=:swing), PQLoad(;name=:load))

to get an instance of a model which is structually aquivalent to MyMTKBus.

Line Modeling

Model class Branch

A branch is the two-port equivalent to an injector. I needs to have two Terminal()s, one is called :src, the other :dst.

Examples for branches are: PI─Model branches, dynamic RL branches or transformers.

      ┌───────────┐
(src) │           │ (dst)
  o←──┤  Branch   ├──→o
      │           │
      └───────────┘

Both ends follow the injector interface, i.e. current leaving the device towards the terminals is always positive.

Code example: algebraic R-line
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
@mtkmodel MyRLine begin
    @components begin
        src = Terminal()
        dst = Terminal()
    end
    @parameters begin
        R=0, [description="Resistance"]
    end
    @equations begin
        dst.i_r ~ (dst.u_r - src.u_r)/R
        dst.i_i ~ (dst.u_i - src.u_i)/R
        src.i_r ~ -dst.i_r
        src.i_i ~ -dst.i_i
    end
end

Model class: MTKLine

Similar to the MTKBus, a MTKLine is a model class which represents a transmission line in the network.

It musst contain two LineEnd() instances, one called :src, one called :dst.

 ┌────────────────────────────────────────────────┐
 │ MTKLine          ┌──────────┐                  │
 │  ┌─────────┐  ┌──┤ Branch A │──┐  ┌─────────┐  │
 │  │ LineEnd │  │  └──────────┘  │  │ LineEnd │  │
 │  │  :src   ├──o                o──┤  :dst   │  │
 │  │         │  │  ┌──────────┐  │  │         │  │
 │  └─────────┘  └──┤ Branch B │──┘  └─────────┘  │
 │                  └──────────┘                  │
 └────────────────────────────────────────────────┘

Simple line models, which consist only of valid Branch models can be instantiated using the MTKLine(branches...) constructor.

More complex models can be created manually. For example if you want to chain multiple branches between the LineEnds, for example something like

LineEnd(:src) ──o── Transformer ──o── Pi─Line ──o── LineEnd(:dst)
Code example: Transmission line with two pi-branches
using OpPoDyn, OpPoDyn.Library, ModelingToolkit
@mtkmodel MyMTKLine begin
    @components begin
        src = LineEnd()
        dst = LineEnd()
        branch1 = DynawoPiLine()
        branch2 = DynawoPiLine()
    end
    @equations begin
        connect(src.terminal, branch1.src)
        connect(src.terminal, branch2.src)
        connect(dst.terminal, branch1.dst)
        connect(dst.terminal, branch2.dst)
    end
end

Alternatively, an equivalent model with multiple valid branch models in parallel could be created and instantiated with the convenience constructor

line = MTKLine(DynawoPiLine(;name=:branch1), DynawoPiLine(;name=:branch2))

From MTK Models to NetworkDynamics

Valid MTKLine and MTKBus can be transformed into so called Line and Bus objects.

Line and Bus structs are no MTK models anymore, but rather containers. Currently, they mainly contain a NetworkDynamics component function (VertexModel, EdgeModel).

Eventually, those models will contain more metadata. For example

  • static representation for powerflow,
  • possibly local information about PU system (for transforming parameters between SI/PU),
  • meta information for initialization, for example initialization model or the information which parameters are considered "tunable" in order to initialize the dynamical model

The exact structure here is not clear yet!

The result would look something like that:

using OpPoDyn, OpPoDyn.Library, ModelingToolkit
using Graphs, NetworkDynamics
using OrdinaryDiffEqRosenbrock, OrdinaryDiffEqNonlinearSolve
using CairoMakie

Define a swing bus with load

# define injectors
@named swing = Swing(; Pm=1, V=1, D=0.1)
@named load = PQLoad(; Pset=-.5, Qset=0)
bus1mtk = MTKBus(swing, load; name=:swingbus)
vertex1f = Bus(bus1mtk) # extract component function
VertexModel :swingbus NoFeedForward()
 ├─ 2 inputs:  [busbar₊i_r, busbar₊i_i]
 ├─ 2 states:  [swing₊θ≈0, swing₊ω≈0]
 ├─ 2 outputs: [busbar₊u_r=1, busbar₊u_i=0]
 └─ 7 params:  [load₊Qset=0, swing₊D=0.1, swing₊M=0.005, swing₊ω_ref=1, swing₊Pm=1, swing₊V=1, load₊Pset=-0.5]

Define a second bus as a slack

bus2mtk = SlackDifferential(; name=:slackbus)
vertex2f = Bus(bus2mtk) # extract component function
VertexModel :slackbus PureStateMap()
 ├─ 2 inputs:  [busbar₊i_r, busbar₊i_i]
 ├─ 2 states:  [busbar₊u_i=0, busbar₊u_r=1]
 ├─ 2 outputs: [busbar₊u_r=1, busbar₊u_i=0]
 └─ 2 params:  [u_init_i=0, u_init_r=1]

Define the powerline connecting both nodes

@named branch1 = DynawoPiLine()
@named branch2 = DynawoPiLine()
linemtk = MTKLine(branch1, branch2; name=:powerline)
edgef = Line(linemtk) # extract component function
EdgeModel :powerline PureFeedForward()
 ├─ 2/2 inputs:  src=[src₊u_r, src₊u_i] dst=[dst₊u_r, dst₊u_i]
 ├─   0 states:  []  
 ├─ 2/2 outputs: src=[src₊i_r, src₊i_i] dst=[dst₊i_r, dst₊i_i]
 └─   8 params:  [branch1₊GPu=0, branch2₊BPu=0, branch2₊GPu=0, branch1₊RPu=0, branch2₊XPu=0.022522, branch1₊XPu=0.022522, branch2₊RPu=0, branch1₊BPu=0]

Define the graph, the network and extract initial conditions

g = complete_graph(2)
nw = Network(g, [vertex1f, vertex2f], edgef)
u0 = NWState(nw) # extract parameters and state from models
u0.v[1, :swing₊θ] = 0 # set missing initial conditions
u0.v[1, :swing₊ω] = 1
1

Then we can solve the problem

prob = ODEProblem(nw, uflat(u0), (0,1), pflat(u0))
sol = solve(prob, Rodas5P())

And finally we can plot the solution:

fig = Figure();
ax = Axis(fig[1,1])
lines!(ax, sol; idxs=VIndex(1,:busbar₊P), label="Power injection Bus", color=Cycled(1))
lines!(ax, sol; idxs=VIndex(1,:swing₊Pel), label="Power injection Swing", color=Cycled(2))
lines!(ax, sol; idxs=VIndex(1,:load₊P), label="Power injection load", color=Cycled(3))
axislegend(ax)

ax = Axis(fig[2,1])
lines!(ax, sol; idxs=VIndex(1,:busbar₊u_arg), label="swing bus voltage angle", color=Cycled(1))
lines!(ax, sol; idxs=VIndex(2,:busbar₊u_arg), label="slack bus voltage angle", color=Cycled(2))
axislegend(ax)
Example block output

Internals

Internally, we use different input/output conventions for bus and line models. The predefined models BusBar() and LineEnd() are defined in the following way:

Model: BusBar()

A busbar is a concrete model used in bus modeling. It represents the physical connection within a bus, the thing where all injectors and lines attach.

           ┌──────────┐
i_lines ──→│          │  (t)
           │  Busbar  ├───o
  u_bus ←──│          │
           └──────────┘

It receives the sum of all line currents as an input and equals that to the currents flowing into the terminal. As an output, it gives forwards the terminal voltage to the backend.

Model: LineEnd()

A LineEnd model is very similar to the BusBar model. It represents one end of a transmission line.

          ┌───────────┐
 u_bus ──→│           │  (t)
          │  LineEnd  ├───o
i_line ←──│           │
          └───────────┘

It has special input/output connectors which handle the network interconnection. The main difference beeing the different input/output conventions for the network interface.