Network Construction

Building a Network

The main type of NetworkDyanmics.jl is a Network. A network bundles various component models (edge and vertex models) together with a graph to form a callable object which represents the RHS of the overall dynamical system, see Mathematical Model.

A Network is build by passing a graph g, vertex models vertexm and edge models edgem.

nw = Network(g, vertexm, edgem; kwargs...)

Two important keywords for the Network constructor are:

  • execution: Defines the ExecutionStyle of the coreloop, e.g. SequentialExecution{true}(). A execution style is a special struct which tells the backend how to parallelize for example. A list of available executions styles can be found under Execution Types in the API.

  • aggregator: Tells the backend how to aggregate and which aggregation function to use. Aggregation is the process of creating a single vertex input by reducing over the outputs of adjecent edges of said vertex. The aggregator contains both the function and the algorith. E.g. SequentialAggregator(+) is a sequential aggregation by summation. A list of availabe Aggregators can be found under Aggregators in the API.

Building VertexModels

This chapter walks through the most important aspects when defining custom vertex model. For a list of all keyword arguments please check out the docstring of VertexModel. As an example, we'll construct an second order kuramoto model, because that's what we do.

function kuramoto_f!(dv, v, esum, p, t)
    M, P, D = p
    dv[1] = v[2]
    dv[2] = (P - D*v[2] + esum[1])/M
    nothing
end
function kuramoto_g!(y, v, esum, p, t)
    y[1] = v[1]
    nothing
end
VertexModel(; f=kuramoto_f!, g=kuramoto_g!, dim=2, pdim=3, outdim=1)
VertexModel :VertexM FeedForward()
 ├─ 2 states: [v₁, v₂]
 ├─ 1 output: [o]
 └─ 3 params: [p₁, p₂, p₃]

Those keywords are the minimum metadata we need to provide.

However there is a problem: the vertex is classified as a FeedForward vertex, which is unnecessary. We can improve the implementation of g according to the Feed Forward Behavior section.

function kuramoto_g_noff!(y, v, p, t)
    y[1] = v[1]
    nothing
end
VertexModel(; f=kuramoto_f!, g=kuramoto_g_noff!, dim=2, pdim=3, outdim=1)
VertexModel :VertexM NoFeedForward()
 ├─ 2 states: [v₁, v₂]
 ├─ 1 output: [o]
 └─ 3 params: [p₁, p₂, p₃]

It is still annoying to explicitly write this trivial output function. You can prevent this by using StateMask. By writing

VertexModel(; f=kuramoto_f!, g=StateMask(1:1), dim=2, pdim=3)
VertexModel :VertexM PureStateMap()
 ├─ 2 states: [v₁, v₂]
 ├─ 1 output: [v₁]
 └─ 3 params: [p₁, p₂, p₃]

we told the vertex model, that the output is part of the states x[1:1]. This enables a few things:

  • outdim is not needed anymore, can be inferred from StateMask
  • outsym is not a generic :o any more but inferred from the state symbols.

We can be even less verbose by writing g=1:1 or just g=1.

In a last we define better names for our states and parameters as well as assigning a position in the graph to enable the graphless network construction. Whenever you provide sym keyword the corresponding dim keyword is not neccessary anymore. We end up with a relatively short definition

VertexModel(; f=kuramoto_f!, g=1,
              sym=[:θ, :ω], psym=[:M=>1, :P=>0.1, :D=>0],
              insym=[:P_nw], name=:swing, vidx=1)
VertexModel :swing PureStateMap() @ Vertex 1
 ├─ 1 input:  [P_nw]
 ├─ 2 states: [θ, ω]
 ├─ 1 output: [θ]
 └─ 3 params: [M=1, P=0.1, D=0]

Building EdgeModels

This chapter walks through the most important aspects when defining custom edge models. For a list of all keyword arguments please check out the docstring of EdgeModel.

As an example edge model we want to define standard sinusoidal coupling between the vertices in our network. The full definition looks like this:

function edge_f!(de, e, vsrc, vdst, p, t)
    nothing
end
function edge_g!(ysrc, ydst, e, vsrc, vdst, p, t)
    ydst[1] = p[1] * sin(vsrc[1] - vdst[1])
    ysrc[1] = -ydst[1]
end
EdgeModel(; f=edge_f!, g=edge_g!, dim=0, pdim=1, outdim=1)
EdgeModel :StaticEdgeM FeedForward()
 ├─   0 states:  []  
 ├─ 1/1 outputs: src=[src₊o] dst=[dst₊o]
 └─   1 param:   [p] 

This is a purely "static" edge without internal states. This means we can omit f and dim entirely. Also, we can define a variant of g without the e input

function edge_g_ff!(ysrc, ydst, vsrc, vdst, p, t)
    ydst[1] = p[1] * sin(vsrc[1] - vdst[1])
    ysrc[1] = -ydst[1]
end
EdgeModel(;g=edge_g_ff!, pdim=1, outdim=1)
EdgeModel :StaticEdgeM PureFeedForward()
 ├─   0 states:  []  
 ├─ 1/1 outputs: src=[src₊o] dst=[dst₊o]
 └─   1 param:   [p] 

which no classifies as a PureFeedForward edge. In cases like this, where the edge is actually anti symmetric we can alternatively define a single sided output function and wrapping it in an AntiSymmetric object

function edge_g_s!(ydst, vsrc, vdst, p, t)
    ydst[1] = p[1] * sin(vsrc[1] - vdst[1])
end
EdgeModel(;g=AntiSymmetric(edge_g_ff!), pdim=1, outdim=1)
EdgeModel :StaticEdgeM FeedForward()
 ├─   0 states:  []  
 ├─ 1/1 outputs: src=[₋o] dst=[o]
 └─   1 param:   [p] 

which can also lead to briefer output naming. Available single sided wrappers are

Once again we can add additonal data like defining a src and dst index

function edge_g_s!(ydst, vsrc, vdst, p, t)
    ydst[1] = p[1] * sin(vsrc[1] - vdst[1])
end
EdgeModel(;g=AntiSymmetric(edge_g_ff!), psym=:K=>1, outsym=:P, insym=:θ, src=1, dst=4)
EdgeModel :StaticEdgeM FeedForward() @ Edge 1=>4
 ├─ 1/1 inputs:  src=[src₊θ] dst=[dst₊θ]
 ├─   0 states:  []  
 ├─ 1/1 outputs: src=[₋P] dst=[P]
 └─   1 param:   [K=1]