-
Notifications
You must be signed in to change notification settings - Fork 14
Edit tutorial contents #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f9d5262
3f47a4f
ad9bd53
0aaf778
ffff152
12a4821
9f92084
02f0868
31f7f96
27075a5
4810a1e
334d867
49f8074
76cf0b4
8a34eaf
65dee53
728f0b6
f5ba8de
c780541
5955040
c08ceae
1f04988
ecfac0c
2e555c6
bb3993f
f9e33db
c7749de
47dfd31
84f1f2b
45b002e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
% \documentclass[tikz,convert={outfile=\jobname.svg}]{standalone} | ||
\documentclass[tikz,convert=pdf2svg]{standalone} | ||
%\usetikzlibrary{...}% tikz package already loaded by 'tikz' option | ||
\usetikzlibrary{arrows, arrows.meta} | ||
% \tikzset{ | ||
% >=stealth', | ||
% } | ||
\usepackage{amsmath,amssymb} | ||
\usepackage{tikz} | ||
\usepackage{xcolor} | ||
|
||
\begin{document} | ||
\begin{tikzpicture} | ||
\node[draw, minimum height=1.5cm](n){ | ||
Edge Model | ||
}; | ||
\draw[->](n.east)++(0,0.4)--++(1.0,0) node[right]{$y^{\mathrm e}_{\mathrm{src}}$}; \draw[->](n.east)++(0,-0.4)--++(1.0,0) node[right]{$y^{\mathrm e}_{\mathrm{dst}}$}; | ||
|
||
\draw[<-](n.west)++(0,0.4)--++(-1.0,0) node[left]{$y^{\mathrm v}_{i}=i^{\mathrm e}_{\mathrm{src}}$}; | ||
\draw[<-](n.west)++(0,-0.4)--++(-1.0,0) node[left]{$y^{\mathrm v}_{j}=i^{\mathrm e}_{\mathrm{dst}}$}; | ||
|
||
\node[text width=3.5cm, align=center, font=\scriptsize] at ([xshift=-2.7cm, yshift=-1.2cm]n) {mapping of adjecent node outputs as edge inputs}; | ||
\end{tikzpicture} | ||
\end{document} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
% \documentclass[tikz,convert={outfile=\jobname.svg}]{standalone} | ||
\documentclass[tikz,convert=pdf2svg]{standalone} | ||
%\usetikzlibrary{...}% tikz package already loaded by 'tikz' option | ||
\usetikzlibrary{arrows, arrows.meta} | ||
% \tikzset{ | ||
% >=stealth', | ||
% } | ||
\usepackage{amsmath,amssymb} | ||
\usepackage{tikz} | ||
\usepackage{xcolor} | ||
|
||
\begin{document} | ||
\begin{tikzpicture} | ||
\node[draw](n){ | ||
Node Model | ||
}; | ||
\draw[->](n.east)--++(1.0,0) node[above, pos=.5]{$y^{\mathrm v}$}; | ||
|
||
% Draw the accumulator circle | ||
\node[draw, circle] (acc) at ([xshift=-1.5cm]n.west) {$\mathrm{acc}$}; | ||
|
||
% Connect acc to the node | ||
\draw[->] (acc) -- (n.west) node[pos=.5, above]{$i^{v}$}; | ||
|
||
% Add three arrows to acc with rectangular corners | ||
\draw[<-] (acc) |- ++(-1.0,0.7) node[left](in){$y^e_i$}; | ||
\draw[<-] (acc) -- ++(-1.0,0) node[left]{$y^e_j$}; | ||
\draw[<-] (acc) |- ++(-1.0,-0.7) node[left]{$\ldots$}; | ||
|
||
% Add a label for the accumulation function | ||
\node[text width=3cm, align=center, font=\scriptsize] at ([xshift=-.5cm, yshift=-1.2cm]acc) {accumulation of adjacent edge outputs}; | ||
|
||
\end{tikzpicture} | ||
\end{document} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,25 @@ | ||
# Mathematical Model | ||
The basic mathematical model of `NetworkDynamics.jl` splits up the system into two parts: vertex and edge components. | ||
The basic mathematical model of `NetworkDynamics.jl` splits the system in two parts: the vertex and | ||
the edge components. | ||
|
||
The main goal of `NetworkDynamics.jl` is to express the overall network dynamics as a | ||
[Differential-Algebraic-Equation (DAE)](https://mathworld.wolfram.com/Differential-AlgebraicEquation.html) | ||
|
||
The main goal of `NetworkDynamics.jl` is, to express the overall network dynamics as a Differential-Algebraic-Equation (DAE) | ||
```math | ||
M\,\frac{\mathrm{d}}{\mathrm{d}t}u = f^{\mathrm{nw}}(u, p, t) | ||
``` | ||
where M is a (possibly singular) mass matrix, $u$ is the internal state vector of the system, $p$ are the parameters and $t$ is the time. | ||
To make this compatible with the solvers for `OrdinaryDiffEq.jl`, the created [`Network`](@ref) object is a callable object | ||
where $M$ is a (possibly singular) mass matrix, $u$ is the internal state vector of the system, $p$ are the parameters | ||
and $t$ is the time. | ||
To make this compatible with the solvers used in `OrdinaryDiffEq.jl`, the generated | ||
[`Network`](@ref) object is a callable object | ||
``` | ||
nw(du, u, p, t) # mutates du | ||
nw(du, u, p, t) # mutates du as an "output" | ||
``` | ||
with stored mass matrix information to build an `ODEProblem` based on the `Network`. | ||
which represents the right-hand-side (RHS) of the equation above. The mass-matrix $m$ is stored in the `Network` object as well. | ||
|
||
Instead of defining $f^{\mathrm{nw}}$ by hand, `ND.jl` helps you to build it automatically based on a list of decentralized nodal and edge dynamics, so-called `VertexModel` and `EdgeModel` objects. | ||
Each component model $\mathrm c$ is modeled as general input-output-system | ||
Instead of defining $f^{\mathrm{nw}}$ by hand, `ND.jl` helps you to build it automatically based on a list of | ||
decentralized nodal and edge dynamics, the so-called `VertexModel` and `EdgeModel` objects. Each component model | ||
$\mathrm c$ is modeled as a general input-output-system: | ||
|
||
```math | ||
\begin{aligned} | ||
|
@@ -22,25 +28,39 @@ y^{\mathrm c} &= g^{\mathrm c}(x^\mathrm{c}, i_{\mathrm c}, p_{\mathrm c}, t) | |
\end{aligned} | ||
``` | ||
|
||
where $M_{\mathrm{c}}$ is the component mass matrix, $x^{\mathrm c}$ are the component states, $i^{\mathrm c}$ are the *inputs* of the component and $y^{\mathrm c}$ is the *output* of the component. It is possible to have $\mathrm{dim}(x^{\mathrm{c}}) = 0$ and thus no internal states. | ||
where $M_{\mathrm{c}}$ is the component mass matrix, $x^{\mathrm c}$ are the component states, $i^{\mathrm c}$ are the | ||
***inputs*** of the component and $y^{\mathrm c}$ is the ***output*** of the component. | ||
If $\mathrm{dim}(x^{\mathrm{c}}) = 0$, the number of internal states is 0. | ||
|
||
In the context of the network, the **output of the edges are flow variables** and the **outputs of vertices are | ||
potential variables**. | ||
When the node and edge models are placed on a graph, the inputs and outputs ware connected: the nodes receive the output of the | ||
adjacent edges as inputs and the edges receive the output of the adjecent nodes as inputs. | ||
|
||
Thus, the *flow* on the edges depends on the *potentials* at both ends as inputs. The *potentials* of the nodes depend on the | ||
incoming *flows* from all connected edges as an input. (Here, flow and potentials are meant in a conceptional and not | ||
necessarily physical way.) | ||
|
||
In the network context, the **output of the edges are flow variables**. The **outputs of vertices are potential variables**. In interconnection, the *flow* on the edges depends on the *potentials* at both ends as inputs. The *potentials* of the nodes depend on the incoming *flows* from all connected edges as an input. (Here, flow and potentials are meant in a conceptional and not necessarily physical way.) | ||
```@raw html | ||
<img src="../assets/mathmodel.svg" width="100%"/> | ||
``` | ||
@Hans: the image seems to be missing here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is not visible in the markdown preview in github, because relative links work different there. But it does show up for me in the rendered documentation. |
||
|
||
## Vertex Models | ||
Specifically, a (single-layer) vertex model has one input, and one output. | ||
The input is an aggregation/reduction over all *incident edge outputs*, | ||
```@raw html | ||
<img src="../assets/nodemodel.svg" width="100%"/> | ||
``` | ||
A (single-layer) vertex model has one input, and one output. | ||
The input is an aggregation/reduction over all of the *incident edge outputs*, | ||
```math | ||
i^{\mathrm v} = \mathop{\mathrm{agg}}\limits_k^{\text{incident}} y^{\mathrm e}_k \qquad\text{often}\qquad | ||
i^{\mathrm v} = \sum_k^{\text{incident}} y^{\mathrm e}_k | ||
``` | ||
The full vertex model | ||
```math | ||
\begin{aligned} | ||
M^{\mathrm v}\,\frac{\mathrm{d}}{\mathrm{d}t}x^{\mathrm v} &= f^{\mathrm v}(u^{\mathrm v}, i^{\mathrm v}, p^{\mathrm v}, t)\\ | ||
y^{\mathrm v} &= g^{\mathrm v}(u^{\mathrm v}, i^{\mathrm v}, p^{\mathrm v}, t) | ||
M^{\mathrm v}\,\frac{\mathrm{d}}{\mathrm{d}t}x^{\mathrm v} &= f^{\mathrm v}(x^{\mathrm v}, i^{\mathrm v}, p^{\mathrm v}, t)\\ | ||
y^{\mathrm v} &= g^{\mathrm v}(x^{\mathrm v}, i^{\mathrm v}, p^{\mathrm v}, t) | ||
\end{aligned} | ||
``` | ||
corresponds to the Julia functions | ||
|
@@ -57,13 +77,23 @@ vertf = VertexModel(; f=fᵥ, g=gᵥ, mass_matrix=Mᵥ, ...) | |
``` | ||
|
||
## Edge Models | ||
In contrast to vertex models, edge models in general have *two* inputs and *two* outputs, both for source and destination end of the edge. | ||
We commonly use `src` and `dst` to describe the source and destination end of an edge respectively. | ||
```@raw html | ||
<img src="../assets/edgemodel.svg" width="100%"/> | ||
``` | ||
In contrast to vertex models, edge models in general have *two* inputs and *two* outputs, for both the source and the | ||
destination end of the edge. We commonly use `src` and `dst` to describe the source and destination end of an edge | ||
respectively. | ||
|
||
!!! note "On the directionality of edges" | ||
Mathematically, in a system defined on an undirected graph there is no difference between the edge $(1,2)$ and $(2,1)$, the edge has no direction. However, from an implementation point of view we always need to have some kind of ordering for function arguments, state order and so on. For undirected graphs, `Graphs.jl` chooses the direction of an edge `v1->v2` such that `v1 < v2`. | ||
Mathematically, in a system defined on an undirected graph there is no difference between edge $(1,2)$ and | ||
edge $(2,1)$, because the edge has no direction. However, from an implementation point of view we always need to have | ||
some kind of ordering, which is why we introduce the source and destination terminology. | ||
|
||
For undirected graphs, `Graphs.jl` chooses the direction of an edge `v1->v2` such that `v1 < v2`, i.e. the edge between vertices 16 and 12 will be always and edge with `src=12` and `dst=16`. | ||
|
||
The *inputs* of the edge are just the outputs of the two nodes at both ends. The output is split into two: the `dst` output goes to the input of the vertex at the destination end, the `src` output goes to the input of the vertex at the `src` end. | ||
The *inputs* of the edge are the outputs of the two nodes at both their ends. The output is split into two parts: | ||
the `dst` output goes to the input of the vertex at the destination end, the `src` output goes to the input of the | ||
vertex at the `src` end. | ||
|
||
The full model of an edge | ||
```math | ||
|
@@ -73,7 +103,7 @@ y^{\mathrm e}_{\mathrm{dst}} &= g_\mathrm{dst}^{\mathrm e}(u^{\mathrm e}, y^{\ma | |
y^{\mathrm e}_{\mathrm{src}} &= g_\mathrm{src}^{\mathrm e}(u^{\mathrm e}, y^{\mathrm v}_{\mathrm{src}}, y^{\mathrm v}_{\mathrm{dst}}, p^{\mathrm e}, t) | ||
\end{aligned} | ||
``` | ||
corresponds to the Julia functions | ||
which corresponds to the Julia functions: | ||
```julia | ||
function fₑ(dxₑ, xₑ, v_src, v_dst, pₑ, t) | ||
# mutate dxᵥ | ||
|
@@ -86,9 +116,9 @@ end | |
vertf = EdgeModel(; f=fₑ, g=gₑ, mass_matrix=Mₑ, ...) | ||
``` | ||
|
||
The sign convention for both outputs of an edge must be identical, | ||
typically, a positive flow represents a flow *into* the connected vertex. | ||
This is important, because the vertex only receives the flows, it does not know whether the flow was produce by the source or destination end of an edge. | ||
The sign convention for both outputs of an edge must be identical, so typically, a positive flow represents a flow | ||
*into* the connected vertex. This is important, because the vertex only receives the flows, it does not know whether | ||
the flow was produce by the source or the destination end of an edge. | ||
``` | ||
y_src y_dst | ||
V_src o───←─────────→───o V_dst | ||
|
@@ -97,23 +127,27 @@ This is important, because the vertex only receives the flows, it does not know | |
|
||
|
||
### Single Sided Edge Outputs | ||
Often, edge outputs will possess some symmetry which makes it more convenient to define "single sided" edge output functions | ||
Often, edge outputs will possess some symmetry. This makes it more convenient to define | ||
"single sided" edge output functions: | ||
```julia | ||
function g_single(y, xᵥ, v_src, v_dst, pₑ, t) | ||
# mutate y | ||
nothing | ||
end | ||
``` | ||
There are multiple wrappers available to automaticially convert them into double-sided edge output functions: | ||
There are multiple wrappers available to automatically convert them into double-sided edge | ||
output functions: | ||
|
||
- `Directed(g_single)` builds a double-sided function *which only couples* to the destination side. | ||
- `Symmetric(g_single)` builds a double-sided function in which both ends receive `y`. | ||
- `AntiSymmetric(g_single)` builds a double-sided function where the destination receives `y` and the source receives `-y`. | ||
- `Fiducial(g_single_src, g_singl_dst)` builds a double-sided edge output function based on two single sided functions. | ||
|
||
|
||
## Feed Forward Behavior | ||
The most general version of the component models can contain direct feed forwards from the input, i.e. the edge output might depend directly on the connected vertices or the vertex output might depend directly on the aggregated edge input. | ||
Component models can show have so-called feed forward behavior. Feed forward means, that there is a direct link from input to output. | ||
|
||
The most generic version of the component models can contain direct feed forwards from the input to the output. | ||
This means, the output function depends $g$ directly depends on the component inputs $i$ and not only on the component state $x$. | ||
|
||
Whenever possible, you should define output functions without feed forwards, i.e. | ||
```julia | ||
|
@@ -127,16 +161,19 @@ gₑ([y_src], y_dst, xᵥ, v_src, v_dst, pₑ, t) | |
``` | ||
|
||
NetworkDynamics cannot couple two components with feed forward to each other. | ||
It is always possible to transform feed forward behavior to an internal state `x` with mass matrix entry zero to circumvent this problem. This transformation can be performed automatically by using [`ff_to_constraint`](@ref). | ||
But, it is always possible to transform feed forward behavior to an internal state `x` with mass matrix entry zero to | ||
circumvent this problem. This transformation can be performed automatically by using [`ff_to_constraint`](@ref). | ||
|
||
|
||
!!! warning "Feed Forward Vertices" | ||
As of 11/2024, vertices with feed forward are not supported at all. Use [`ff_to_constraint`](@ref) to transform them into vertex model without FF. | ||
As of 11/2024, vertices with feed forward are not supported at all. Use [`ff_to_constraint`](@ref) to transform them | ||
into vertex model without FF. | ||
|
||
Concretely, NetworkDynamics distinguishes between 4 types of feed forward behaviors of `g` functions based on the [`FeedForwardType`](@ref) trait. | ||
The different types the signature of provided function `g`. | ||
Based on the signatures avaialable, ND.jl will try to find the correct type automaticially. Using the `ff` | ||
keyword in the constructors, the user can enforce a specific type. | ||
Concretely, NetworkDynamics distinguishes between 4 types of feed forward behaviors of `g` functions based on the | ||
[`FeedForwardType`](@ref) trait. | ||
The feed forward type is inferred automatically based on the provided function `g`, more concretely it is determined by the signature of that method / the number of arguments it takes. | ||
|
||
The code blow shows the different `g` signatures for the different feed forward types | ||
**[`PureFeedForward()`](@ref)** | ||
```julia | ||
g!(outs..., ins..., p, t) # abstractly | ||
|
@@ -166,3 +203,4 @@ g!(out_src, out_dst, x) # double-sided edge | |
g!(v_out, x) # single layer vertex | ||
``` | ||
|
||
If the automatic inference of feed forward type fails, the user may specify it explicitly using the `ff` keyword argument of the Edge/VertexModel constructor. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,8 +31,11 @@ vertexf = VertexModel(f=_vertexf!, g=1, sym=[:storage]) | |
## Fundamental Symbolic Indices | ||
The default types for this access are the types [`VIndex`](@ref), [`EIndex`](@ref), [`VPIndex`](@ref) and [`EPIndex`](@ref). | ||
Each of those symbolic indices consists of 2 elements: a reference to the network component and a reference to the symbol within that component. | ||
As such, `VIndex(2, :x)` refers to variable with symbolic name `:x` in vertex number 2. | ||
`EPIndex(4, 2)` would refer to the *second* parameter of the edge component for the 4th edge. | ||
As such: | ||
- `VIndex(2, :x)` refers to variable with symbolic name `:x` in vertex number 2. | ||
- `EPIndex(4, 2)` refers to the *second* parameter of the edge component for the 4th edge. | ||
- `VPIndex`() refers to (@Hans please fill in this with an example) | ||
- `EPIndex`() refers to (@Hans please fill in this with an example) | ||
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You added those examples, neither |
||
|
||
!!! details "Setup code to make following examples work" | ||
```@example si | ||
|
@@ -47,13 +50,12 @@ As such, `VIndex(2, :x)` refers to variable with symbolic name `:x` in vertex nu | |
|
||
Those fundamental indices can be used in a lot of scenarios. Most importantly you can use them to | ||
```@example si | ||
sol(sol.t; idxs=VIndex(1, :storage)) # extract timeseries out ouf solution object | ||
sol(sol.t; idxs=VIndex(1, :storage)) # extract timeseries out of a solution object | ||
plot(sol; idxs=[VIndex(1, :storage), VIndex(5,:storage)]) # plot storage of two nodes | ||
``` | ||
|
||
## Generate Symbolic Indices | ||
Often, you need many individual symbolic indices. For that there are the helper methods [`vidxs`](@ref), [`eidxs`](@ref), [`vpidxs`](@ref) and [`epidxs`](@ref). | ||
With the help of those methods you can generate arrays of symbolic indices: | ||
Often, you need many individual symbolic indices. To achieve this you can use the helper methods [`vidxs`](@ref), [`eidxs`](@ref), [`vpidxs`](@ref) and [`epidxs`](@ref). With their help you can generate arrays of symbolic indices: | ||
|
||
```@example si | ||
vidxs(nw, :, :storage) # get variable "storage" for all vertices | ||
|
@@ -98,7 +100,7 @@ in these flat arrays corresponds exactly to the order returned by [`variable_sym | |
|
||
!!! note | ||
The `NWState` and `NWParameter` wrappers can be constructed from various objects. | ||
Fore example, within a callback you might construct `p = NWParameter(integrator)` to then change the parameters of the network within the callback. | ||
For example, within a callback you might construct `p = NWParameter(integrator)` to then change the parameters of the network within the callback. | ||
|
||
|
||
## Observables | ||
|
@@ -109,10 +111,10 @@ the `SciML` ecosystem, these values are called Observables. | |
A prime example of Observables are edge/vertex-outputs, such as the `flow` in the edge model defined above. | ||
It is also possible to define additional Observables manually by using the `obssym` and `obsf` keyword | ||
on the `EdgeModel`/`VertexModel` constructors. | ||
When building models using ModelingToolkit, the reduced algebraic states will be preserved as observables automatically. | ||
|
||
Observables can be accessed like any other state, for example, the flows in the network don't show up in the state array but can be accessed in all the ways discussed above, for example | ||
When building models using ModelingToolkit, the reduced algebraic states will be preserved automatically as observables. | ||
|
||
Observables can be accessed like any other state. For example, the flows in the network don't show up in the state array but can be accessed in all the ways discussed above. | ||
For example: | ||
```@example si | ||
plot(sol; idxs=eidxs(nw, :, :flow)) | ||
``` | ||
|
@@ -150,7 +152,7 @@ idxs = SII.parameter_index(nw, eidxs(1:2, :K)) | |
pflat(s)[idxs] == s.p.e[1:2, :K] | ||
``` | ||
|
||
If you need the symbols of all the states/parameters in order, you can use | ||
If you need the symbols of all the states/parameters in order, you can use: | ||
```@example si | ||
SII.variable_symbols(nw) | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revise
is only an optional development dependency,LiveServer
is only necessary if you want to observe the docs locally (which most people will never do). Therefore, I'd cut this part.If you think it would be good to point people to general usage help of Julia (so they learn about Revise, Project Environments and so on) I'd instead suggest to link out to something like https://modernjuliaworkflows.org/