Skip to content

Commit 15a05f7

Browse files
authored
Merge pull request #228 from JuliaDynamics/hw/init_tutorial
improve docs
2 parents 054adc3 + 7d81ce3 commit 15a05f7

13 files changed

+128
-94
lines changed

docs/examples/gas_network.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#=
2-
# Dynamic Flow in Simple Gas Network
2+
# [Dynamic Flow in Simple Gas Network](@id gas-example)
33
44
This example is based on the paper
55

docs/examples/init_tutorial.jl

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# [Tutorial on Stepwise Initialization of a Complex Model](@id init-tutorial)
33
44
This example demonstrates how to initialize a complex network model with both static
5-
and dynamic components. We'll create a simple gas network model with three nodes
5+
and dynamic components.
6+
The models are closely related to the ones used in the [gas network example](@ref gas-example),
7+
but greatly simplified for the sake of this tutorial.
8+
We'll create a gas network model with three nodes
69
and pipes connecting them, and show how to:
710
811
1. Create static models for initialization
@@ -361,14 +364,3 @@ let
361364

362365
fig
363366
end
364-
365-
#=
366-
## Interactive Visualization
367-
368-
You can also visualize the results interactively using NetworkDynamicsInspector:
369-
370-
```julia
371-
using NetworkDynamicsInspector
372-
inspect(sol)
373-
```
374-
=#

docs/make.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ kwargs = (;
4545
"General" => "index.md",
4646
"mathematical_model.md",
4747
"network_construction.md",
48+
"data_structure.md",
4849
"Features" => [
4950
"symbolic_indexing.md",
5051
"metadata.md",
@@ -57,14 +58,12 @@ kwargs = (;
5758
"API.md",
5859
"Tutorials" => [
5960
"Getting Started" => "generated/getting_started_with_network_dynamics.md",
60-
"Directed and Weighted Graphs" => "generated/directed_and_weighted_graphs.md",
6161
"Heterogeneous Systems" => "generated/heterogeneous_system.md",
62-
# "Stochastic differential equations" => "generated/StochasticSystem.md",
63-
# "Delay differential equations" => "generated/kuramoto_delay.md",
62+
"Initialization" => "generated/init_tutorial.md",
6463
"Cascading Failure" => "generated/cascading_failure.md",
65-
"Stress on Truss" => "generated/stress_on_truss.md",
6664
"Gas Network" => "generated/gas_network.md",
67-
"Initialization" => "generated/init_tutorial.md",
65+
"Stress on Truss" => "generated/stress_on_truss.md",
66+
"Directed and Weighted Graphs" => "generated/directed_and_weighted_graphs.md",
6867
]
6968
],
7069
draft=false,

docs/src/API.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ KAAggregator
191191
save_parameters!
192192
ff_to_constraint
193193
Base.copy(::NetworkDynamics.ComponentModel)
194+
extract_nw
194195
```
195196

196197
## NetworkDynamicsInspector API

docs/src/Multithreading.md

Lines changed: 0 additions & 21 deletions
This file was deleted.

docs/src/data_structure.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Data Structure
2+
3+
A [`Network`](@ref) contains a list of vertex and edge models along with a graph.
4+
However, in tight numerical loops, it will never access these lists of models directly.
5+
Instead, the network maintains an internal representation that tracks all symbolic indices, defining the precise ordering of states and parameters in a flat array representation. To optimize performance, especially for heterogeneous networks, the network employs specialized data structures that batch identical models together.
6+
7+
This disconnect between the explicit lists and the internal data structures
8+
can be confusing.
9+
10+
## Flat Parameter and State Arrays
11+
12+
The vertex and edge models may contain metadata, such as initial values for states and parameters.
13+
Crucially, this metadata is **only** for building and initialization of the
14+
simulation.
15+
During actual simulation, the state and parameters are handled as **flat arrays**, i.e., plain `Vector{Float64}` objects.
16+
17+
[`NWState`](@ref) and [`NWParameter`](@ref) serve as wrappers around flat arrays and the [`Network`](@ref) objects, allowing you to inspect and modify those flat arrays by addressing vertices and edges directly.
18+
19+
A typical workflow is:
20+
21+
1. Set default values in the models using the metadata (see [Metadata](@ref)).
22+
2. Create a network (see [Network Construction](@ref)).
23+
3. Generate a state `s = NWState(nw)` which will be prefilled with the default values from the component metadata (see [SymbolicIndexing](@ref)).
24+
4. Change the values of `s`, i.e., `s.v[1,:x] = 1.0`: This changes the **underlying flat array** but not the metadata of the models.
25+
5. Build a problem with the updated flat arrays using `uflat(s)` and `pflat(s)`.
26+
27+
## Accessing Components
28+
Per default, the models are not copied on Network construction:
29+
30+
```@example data_structure
31+
using NetworkDynamics # hide
32+
using Graphs #hide
33+
include(joinpath(pkgdir(NetworkDynamics), "test", "ComponentLibrary.jl")) # hide
34+
kuramoto_first = Lib.kuramoto_vertex! # hide
35+
kuramoto_secnd = Lib.kuramoto_inertia! # hide
36+
kuramoto_edge = Lib.kuramoto_edge! # hide
37+
38+
v1 = VertexModel(f=kuramoto_first, sym=[:θ], psym=[:ω], g=1)
39+
v2 = VertexModel(f=kuramoto_secnd, sym=[:δ, :ω], psym=[:M, :D, :Pm], g=1)
40+
e = EdgeModel(;g=AntiSymmetric(kuramoto_edge), outsym=[:P], psym=[:K])
41+
nw = Network(complete_graph(2), [v1, v2], e)
42+
```
43+
You can access the models using `getindex`/`[]` with `VIndex` or `EIndex`:
44+
```@example data_structure
45+
v1 === nw[VIndex(1)]
46+
```
47+
This can be important when changing metadata of components. i.e., both lines below are equivalent:
48+
```@example data_structure
49+
set_position!(v1, (1,0))
50+
set_position!(nw[VIndex(1)], (1,0))
51+
nothing #hide
52+
```
53+
54+
!!! note "Aliasing of component models"
55+
Since components are not copied, multiple entries in vertex and edge lists might point to the same instance of a model.
56+
```@example data_structure
57+
nw = Network(complete_graph(3), [v1,v2,v1], e)
58+
v1 === nw[VIndex(1)] === nw[VIndex(3)]
59+
```
60+
Consequently, metadata set for one model might affect another model.
61+
This behavior can be beneficial for performance reasons.
62+
To force copying of components, use the `dealias` keyword:
63+
```@example data_structure
64+
nw = Network(complete_graph(3), [v1,v2,v1], e; dealias=true)
65+
nw[VIndex(1)] === nw[VIndex(3)] # neither of them === v1
66+
```
67+
68+
## Extracting `Network`-object from Containers
69+
`NetworkDynamics.jl` provides a [`extract_nw`](@ref) function, to get a reference to the wrapped `Network` object from different containers, such as solution objects or integrator objects.

docs/src/metadata.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Special cases for symbol metadata are:
2828
- `default`: Stores default values for states/parameters. In initialization, those are considered fixed.
2929
- `guess`: Stores a guess for a state/parameter which needs to solved during initialization ("free" variables).
3030
- `bounds`: Store bounds for variables/parameters
31-
- `init`: Stores the solution of the "free" variables during initialization.
31+
- `init`: Stores the solution of the "free" variables, this is rarely set manual but instead when calling [`initialize_component!`](@ref).
3232

3333
Fore those, there are special functions `has_*`, `get_*` and `set_*!`. See [Per Symbol Metadata API](@ref).
3434

docs/src/mtk_integration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ as tearing of thousands of equations.
3636

3737
## RC-Circuit Example
3838
In good [MTK tradition](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/acausal_components/), this feature will be explained along a simple RC circuit example.
39-
The [Dynamic Flow in Simple Gas Network](@ref) example is another showcase of the MTK constructors.
39+
The [Gas Network Example](@ref gas-example) or [Initialization Tutorial](@ref init-tutorial) also showcase the MTK constructors.
4040

4141
The system to model is 2 node, 1 edge network. The node output states are the voltage (to ground), the edge output sates are the currents at both ends.
4242
```

docs/src/parameters.md

Lines changed: 0 additions & 34 deletions
This file was deleted.

docs/src/symbolic_indexing.md

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Symbolic Indexing
22

3-
Using SciML's [`SymblicIndexingInterface.jl`](https://github.com/SciML/SymbolicIndexingInterface.jl), `ND.jl` provides lots of methods to access and change variables and Parameters.
3+
Using SciML's [`SymbolicIndexingInterface.jl`](https://github.com/SciML/SymbolicIndexingInterface.jl), `ND.jl` provides numerous methods to access and change variables and parameters.
44

55
!!! details "Setup code to make following examples work"
66
```@example si
@@ -11,7 +11,7 @@ Using SciML's [`SymblicIndexingInterface.jl`](https://github.com/SciML/SymbolicI
1111
```
1212

1313
## Provide Symbol Names
14-
When construction component models, you can pass symbolic names using the `sym` and `psym` keywords.
14+
When constructing component models, you can pass symbolic names using the `sym` and `psym` keywords.
1515
```@example si
1616
function _edgef!(e, v_s, v_d, (K,), t)
1717
e .= K * (v_s[1] .- v_d[1])
@@ -28,9 +28,9 @@ vertexf = VertexModel(f=_vertexf!, g=1, sym=[:storage])
2828
```
2929

3030

31-
## Fundamental Symblic Indices
31+
## Fundamental Symbolic Indices
3232
The default types for this access are the types [`VIndex`](@ref), [`EIndex`](@ref), [`VPIndex`](@ref) and [`EPIndex`](@ref).
33-
Each of those symbolic indices consists of 2 elements: a reference to the network componen and a reference to the symbol within that component.
33+
Each of those symbolic indices consists of 2 elements: a reference to the network component and a reference to the symbol within that component.
3434
As such, `VIndex(2, :x)` refers to variable with symbolic name `:x` in vertex number 2.
3535
`EPIndex(4, 2)` would refer to the *second* parameter of the edge component for the 4th edge.
3636

@@ -56,7 +56,7 @@ Often, you need many individual symbolic indices. For that there are the helper
5656
With the help of those methods you can generate arrays of symbolic indices:
5757

5858
```@example si
59-
vidxs(nw, :, :storage) # get variable "storage" for all nodes
59+
vidxs(nw, :, :storage) # get variable "storage" for all vertices
6060
```
6161
```@example si
6262
plot(sol; idxs=vidxs(nw, :, :storage))
@@ -70,7 +70,7 @@ p = NWParameter(nw)
7070
```
7171
creates a `NWParameter` object for the network `nw`.
7272
It essentially creates a new flat parameter array and fills it with the default parameter values define in the component.
73-
The parameters in the `NWParameter` object can be accessed using the symbolic indices.
73+
The parameters in the `NWParameter` object can be accessed using symbolic indices.
7474
```@example si
7575
p[EPIndex(5, :K)] = 2.0 # change the parameter K of the 5th edge
7676
nothing #hide
@@ -79,20 +79,20 @@ Similarly, you can create a `NWState` object for the network `nw` using
7979
```@example si
8080
s = NWState(nw)
8181
```
82-
No default values were provided in the network components, so the state array is filled with `NaN`s.
82+
No default values were provided in the network components, so the state array is filled with `NaN` values.
8383
```@example si
84-
s[VIndex(:, :storage)] .= randn(5) # set the (initial) storage for alle nodes
84+
s[VIndex(:, :storage)] .= randn(5) # set the (initial) storage for all vertices
8585
s #hide
8686
```
87-
For both `NWState` and `NWParameter` objects, the there is a more convenient way to access the variables and parameters.
87+
For both `NWState` and `NWParameter` objects, there is a more convenient way to access the variables and parameters.
8888
```@example si
8989
@assert s.v[1, :storage] == s[VIndex(1, :storage)] # s.v -> access vertex states
9090
@assert s.e[1, :flow] == s[EIndex(1, :flow)] # s.e -> access edge states
9191
@assert s.p.e[1,:K] == p[EPIndex(1, :K)] # s.p -> access parameters
9292
```
9393

9494
The `NWState` and `NWParameter` objects are mutable, thus changing them will also change the underlying wrapped flat arrays.
95-
You can allways access the flat representations by calling [`uflat`](@ref) and [`pflat`](@ref).
95+
You can always access the flat representations by calling [`uflat`](@ref) and [`pflat`](@ref).
9696

9797
!!! note
9898
The `NWState` and `NWParameter` wrappers can be constructed from various objects.
@@ -101,8 +101,8 @@ You can allways access the flat representations by calling [`uflat`](@ref) and [
101101

102102
## Observables
103103
Sometimes, the "states" you're interested in aren't really states in the DAE sense but rather
104-
algebraic derivations from DAE states, parameters and time -- in accordance with the naming in
105-
the `SciML`-ecosystem those states are called Observables.
104+
algebraic derivations from DAE states, parameters, and time -- in accordance with the naming in
105+
the `SciML` ecosystem, these values are called Observables.
106106

107107
A prime example of Observables are edge/vertex-outputs, such as the `flow` in the edge model defined above.
108108
It is also possible to define additional Observables manually by using the `obssym` and `obsf` keyword
@@ -117,17 +117,43 @@ plot(sol; idxs=eidxs(nw, :, :flow))
117117

118118
## Derived `ObservableExpressions` using `@obsex`
119119

120-
Sometimes it is usefull to plot or observe some simple derived quantity.For that,
121-
one can used the [`@obsex`](@ref) macro, to define simple derived quantities.
120+
Sometimes it is useful to plot or observe simple derived quantities. For that,
121+
one can use the [`@obsex`](@ref) macro to define simple derived quantities.
122122

123123
For example, we can directly plot the storage difference with respect to storage of node 1.
124124

125125
```@example si
126126
plot(sol; idxs=@obsex(vidxs(nw,:,:storage) .- VIndex(1,:storage)))
127127
```
128128

129-
Other examples are the calculation of magnitude and argument of complex values which are modeld in real and imaginary part.
129+
Other examples include calculating the magnitude and argument of complex values that are modeled using real and imaginary parts.
130130
```
131131
@obsex mag = sqrt(VIndex(1, :u_r)^2 + VIndex(2, :u_i)^2)
132132
```
133133

134+
## Low-level accessors for flat array indices
135+
Sometimes, you want to know the indices of your states in the flat arrays.
136+
For that, you can use the low-level methods defined in `SymbolicIndexingInterface.jl`:
137+
138+
```@example si
139+
using NetworkDynamics: SII # SII = SymbolicIndexingInterface
140+
idxs = SII.variable_index(nw, vidxs(1:2, :storage))
141+
```
142+
```@example si
143+
uflat(s)[idxs] == s.v[1:2, :storage]
144+
```
145+
Analogous with parmeters:
146+
```@example si
147+
idxs = SII.parameter_index(nw, eidxs(1:2, :K))
148+
pflat(s)[idxs] == s.p.e[1:2, :K]
149+
```
150+
151+
If you need the symbols of all the states/parameters in order, you can use
152+
```@example si
153+
SII.variable_symbols(nw)
154+
```
155+
and
156+
```@example si
157+
SII.parameter_symbols(nw)
158+
```
159+
All above examples also work on other "symbolic containers", e.g. `SII.variable_symbols(::NWState)`.

0 commit comments

Comments
 (0)