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" 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)

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.