1
- # Tutorial on stepwise initialization of a complex model
1
+ #=
2
+ # Tutorial on Stepwise Initialization of a Complex Model
3
+
4
+ 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
6
+ and pipes connecting them, and show how to:
7
+
8
+ 1. Create static models for initialization
9
+ 2. Find a steady-state solution
10
+ 3. Create corresponding dynamic models
11
+ 4. Initialize the dynamic models with the steady-state solution
12
+ 5. Simulate the system with dynamic behavior
13
+
14
+ This script can be downloaded as a normal Julia script [here](@__NAME__.jl). #md
15
+
16
+ First, let's import the necessary packages:
17
+ =#
2
18
3
19
using NetworkDynamics
4
20
using ModelingToolkit
5
21
using ModelingToolkit: t_nounits as t, D_nounits as D
6
22
using OrdinaryDiffEqTsit5
7
23
using CairoMakie
24
+ nothing # hide
8
25
26
+ #=
9
27
## Node Models
10
- # template for commont states and equations in all gas nodes
28
+
29
+ We'll start by defining our node models using ModelingToolkit.
30
+ First, let's create a template for common states and equations in all gas nodes:
31
+ =#
11
32
@mtkmodel GasNode begin
12
33
@variables begin
13
34
p (t), [description= " Pressure" ] # node output
@@ -20,7 +41,11 @@ using CairoMakie
20
41
end
21
42
nothing # hide
22
43
23
- # node that forces pressure to be constant
44
+ #=
45
+ Now we'll define three specific node types:
46
+
47
+ 1. A constant pressure node that forces pressure to maintain a specific value
48
+ =#
24
49
@mtkmodel ConstantPressureNode begin
25
50
@extend GasNode ()
26
51
@parameters begin
@@ -32,7 +57,9 @@ nothing #hide
32
57
end
33
58
nothing # hide
34
59
35
- # node which forces a certain flow (pressure fully implicit)
60
+ #=
61
+ 2. A static prosumer node which forces a certain flow (pressure is fully implicit)
62
+ =#
36
63
@mtkmodel StaticProsumerNode begin
37
64
@extend GasNode ()
38
65
@parameters begin
@@ -44,7 +71,9 @@ nothing #hide
44
71
end
45
72
nothing # hide
46
73
47
- # dynamic prosumer node with compliance
74
+ #=
75
+ 3. A dynamic prosumer node with compliance, which adds dynamics to the pressure state
76
+ =#
48
77
@mtkmodel DynamicProsumerNode begin
49
78
@extend GasNode ()
50
79
@parameters begin
@@ -57,7 +86,9 @@ nothing #hide
57
86
end
58
87
nothing # hide
59
88
60
- # node with a pressure controller
89
+ #=
90
+ 4. A pressure control node that tries to maintain a set pressure by adjusting its injection
91
+ =#
61
92
@mtkmodel PressureControlNode begin
62
93
@extend GasNode ()
63
94
@parameters begin
@@ -80,8 +111,11 @@ nothing #hide
80
111
end
81
112
nothing # hide
82
113
83
- # ## Edge Models
84
- # template for pipe
114
+ #=
115
+ ## Edge Models
116
+
117
+ Now we'll define our edge models, starting with a template for the pipe:
118
+ =#
85
119
@mtkmodel GasPipe begin
86
120
@variables begin
87
121
q̃ (t), [description= " flow through pipe" ] # output
@@ -91,7 +125,9 @@ nothing #hide
91
125
end
92
126
nothing # hide
93
127
94
- # pipe with a simple delayed model
128
+ #=
129
+ Next, we define a dynamic pipe with inertia (a simple delayed model):
130
+ =#
95
131
@mtkmodel DynamicPipe begin
96
132
@extend GasPipe ()
97
133
@parameters begin
@@ -104,7 +140,10 @@ nothing #hide
104
140
end
105
141
nothing # hide
106
142
107
- # quasistatic pipe model for initialization (equals delayed model in steady state!)
143
+ #=
144
+ And finally a quasistatic pipe model for initialization purposes. This equals the
145
+ dynamic model in steady state, making it ideal for finding initial conditions:
146
+ =#
108
147
@mtkmodel QuasistaticPipe begin
109
148
@extend GasPipe ()
110
149
@parameters begin
@@ -117,36 +156,56 @@ end
117
156
nothing # hide
118
157
119
158
#=
120
- ## Define a static model
121
- =#
159
+ ## Defining a Static Model for Initialization
122
160
123
- # step 1: definition of a static model
124
- # node 1 is our producer which will later be a controlled producer. For initialization we use a static model
161
+ Our first step is to define a static model that we'll use to find the steady-state solution.
162
+ This is a crucial step for initializing complex dynamic models.
163
+
164
+ Step 1: Define all the components of our static model
165
+ First, node 1 is our producer which will later be a controlled producer. For initialization, we use a static model:
166
+ =#
125
167
@named v1_mod_static = ConstantPressureNode (p_set= 1 )
126
168
v1_static = VertexModel (v1_mod_static, [:q̃_nw ], [:p ], vidx= 1 )
127
169
128
- # node 2 and 3 are consumers, there is no difference between static and dynamic model for them
129
- @named v2_mod_static = StaticProsumerNode (q̃_prosumer= - 0.6 ) # consumer, initialize pressure
130
- v2_static = VertexModel (v2_mod , [:q̃_nw ], [:p ], vidx= 2 )
170
+ # # Nodes 2 and 3 are consumers. For them, we'll use static prosumer models:
171
+ @named v2_mod_static = StaticProsumerNode (q̃_prosumer= - 0.6 ) # consumer
172
+ v2_static = VertexModel (v2_mod_static , [:q̃_nw ], [:p ], vidx= 2 )
131
173
132
174
@named v3_mod_static = StaticProsumerNode (q̃_prosumer= - 0.4 ) # consumer
133
- v3_static = VertexModel (v3_mod, [:q̃_nw ], [:p ], vidx= 3 )
175
+ v3_static = VertexModel (v3_mod_static, [:q̃_nw ], [:p ], vidx= 3 )
176
+ nothing # hide
134
177
135
- # static pipes
178
+ #=
179
+ Now we define the static pipe models connecting our nodes:
180
+ =#
136
181
@named p_mod_static = QuasistaticPipe ()
137
182
p12_static = EdgeModel (p_mod_static, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 1 , dst= 2 )
138
183
p13_static = EdgeModel (p_mod_static, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 1 , dst= 3 )
139
184
p23_static = EdgeModel (p_mod_static, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 2 , dst= 3 )
140
-
185
+ nothing # hide
186
+ #=
187
+ Assemble all components into a static network:
188
+ =#
141
189
nw_static = Network ([v1_static, v2_static, v3_static], [p12_static, p13_static, p23_static])
142
190
191
+ #=
192
+ Create an initial guess for the steady state and modify it with reasonable values:
193
+ =#
143
194
u_static_guess = NWState (nw_static)
144
195
u_static_guess. v[2 , :p ] = 1.0
145
196
u_static_guess. v[3 , :p ] = 1.0
197
+ nothing # hide
146
198
199
+ #=
200
+ Find the steady-state solution using our initial guess:
201
+ =#
147
202
u_static = find_fixpoint (nw_static, u_static_guess)
148
203
149
- # ## Define a dynamic model
204
+ #=
205
+ ## Defining a Dynamic Model
206
+
207
+ Now we'll define our dynamic model using more complex components:
208
+ =#
150
209
@named v1_mod_dyn = PressureControlNode (;p_set= 1 )
151
210
v1_dyn = VertexModel (v1_mod_dyn, [:q̃_nw ], [:p ], vidx= 1 )
152
211
@@ -155,53 +214,104 @@ v2_dyn = VertexModel(v2_mod_dyn, [:q̃_nw], [:p], vidx=2)
155
214
156
215
@named v3_mod_dyn = DynamicProsumerNode (q̃_prosumer= - 0.4 )
157
216
v3_dyn = VertexModel (v3_mod_dyn, [:q̃_nw ], [:p ], vidx= 3 )
217
+ nothing # hide
158
218
219
+ #=
220
+ Create dynamic pipe models with inertia:
221
+ =#
159
222
@named p_mod_dyn = DynamicPipe ()
160
223
p12_dyn = EdgeModel (p_mod_dyn, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 1 , dst= 2 )
161
224
p13_dyn = EdgeModel (p_mod_dyn, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 1 , dst= 3 )
162
225
p23_dyn = EdgeModel (p_mod_dyn, [:p_src ], [:p_dst ], AntiSymmetric ([:q̃ ]), src= 2 , dst= 3 )
226
+ nothing # hide
163
227
228
+ #=
229
+ Assemble the dynamic network:
230
+ =#
164
231
nw_dyn = Network ([v1_dyn, v2_dyn, v3_dyn], [p12_dyn, p13_dyn, p23_dyn])
165
232
166
- # no we need to do some magic. we want to initialize the interface values (pressures and flow)
167
- # of the dynamic model with the results of the static model
233
+ #=
234
+ ## Initializing the Dynamic Model with the Static Solution
235
+
236
+ Now comes the important part: we need to initialize the interface values (pressures and flows)
237
+ of the dynamic model with the results from the static model.
238
+
239
+ First, let's handle node 1 (the pressure control node):
240
+ =#
168
241
169
242
set_default! (nw_dyn[VIndex (1 )], :p , u_static. v[1 , :p ])
170
243
set_default! (nw_dyn[VIndex (1 )], :q̃_nw , u_static. v[1 , :q̃_nw ])
171
- v1_dyn
172
- # in the output you can see now that state ξ is "approx" 1 (guess value) while the rest is fixed
173
- # so we can initialize the ydnamic model
244
+ v1_dyn # hide
245
+ #=
246
+ In the output, you can see that state ξ is "approx" 1 (guess value) while the rest is fixed.
247
+ Now we can initialize the dynamic model's internal states:
248
+ =#
174
249
initialize_component! (v1_dyn)
175
- v1_dyn
250
+ v1_dyn # hide
176
251
177
- # the ther two vertices are simplere, the we need t oset the default values
252
+ #=
253
+ For the other two vertices (which are simpler), we just need to set the default values:
254
+ =#
178
255
set_default! (nw_dyn[VIndex (2 )], :p , u_static. v[2 , :p ])
179
256
set_default! (nw_dyn[VIndex (3 )], :p , u_static. v[3 , :p ])
257
+ nothing # hide
180
258
181
- # we can manually "initialize" the only open state of the dynamic line model
259
+ #=
260
+ For the pipe models, we manually initialize the flow state of the dynamic line model:
261
+ =#
182
262
set_default! (nw_dyn[EIndex (1 )], :q̃ , u_static. e[1 , :q̃ ])
183
263
set_default! (nw_dyn[EIndex (2 )], :q̃ , u_static. e[2 , :q̃ ])
184
264
set_default! (nw_dyn[EIndex (3 )], :q̃ , u_static. e[3 , :q̃ ])
265
+ nothing # hide
185
266
186
- # we have set all the "default" values for all teh states now. so we call `NWState` on the
187
- # network we should get a fully initialized state
267
+ #=
268
+ Now that we've set all the "default" values for all the states, we can call `NWState` on the
269
+ network to get a fully initialized state vector:
270
+ =#
188
271
u0_dyn = NWState (nw_dyn)
189
272
273
+ #=
274
+ Let's verify that our initialization is correct by checking that the derivatives are close to zero:
275
+ =#
190
276
du = ones (dim (nw_dyn))
191
277
nw_dyn (du, uflat (u0_dyn), pflat (u0_dyn), 0.0 )
192
- extrema (du .- zeros (dim (nw_dyn))) # very close to zero, is steady state!
278
+ extrema (du .- zeros (dim (nw_dyn))) # very close to zero, confirming we have a steady state!
193
279
194
- # now we can solve the dynamic model
280
+ #=
281
+ ## Simulating the Dynamic Model
282
+
283
+ Now we can solve the dynamic model and add a disturbance to see how the system responds:
284
+ =#
195
285
affect = ComponentAffect ([], [:q̃_prosumer ]) do u, p, ctx
196
- @info " Increas consumer at t=$(ctx. t) "
286
+ @info " Increase consumer demand at t=$(ctx. t) "
197
287
p[:q̃_prosumer ] -= 0.1
198
288
end
199
289
cb = PresetTimeComponentCallback ([1.0 ], affect)
200
- set_callback! (nw_dyn[VIndex (2 )], cb) # attach disturabnce to second node
290
+ set_callback! (nw_dyn[VIndex (2 )], cb) # attach disturbance to second node
291
+ nothing # hide
201
292
293
+ #=
294
+ Create and solve the ODE problem with the callback:
295
+ =#
202
296
prob = ODEProblem (nw_dyn, copy (uflat (u0_dyn)), (0 , 7 ), copy (pflat (u0_dyn));
203
297
callback= get_callbacks (nw_dyn))
204
298
sol = solve (prob, Tsit5 ())
299
+ nothing # hide
300
+
301
+ #=
302
+ ## Visualizing the Results
303
+
304
+ Finally, let's visualize the results of our simulation.
305
+ The plots show how our gas network responds to the increased consumer demand at t=1:
306
+
307
+ 1. **Pressure at nodes**: We see a pressure drop at all nodes after the disturbance before the pressure is stabilized by the controller.
308
+
309
+ 2. **Injection by producer**: Node 1 increases its injection to compensate for the higher demand.
310
+
311
+ 3. **Draw by consumers**: The solid lines show the actual flows at nodes 2 and 3, while the dashed lines show the set consumer demands. At t=1, we see the step change in consumer demand at node 2.
312
+
313
+ 4. **Flows through pipes**: Shows how the flows in all pipes adjust to the new demand pattern.
314
+ =#
205
315
206
316
let
207
317
fig = Figure (size= (1000 ,1000 ))
227
337
fig
228
338
end
229
339
340
+ #=
341
+ ## Interactive Visualization
230
342
231
- # btw have you tried the GUi yet? :)
232
- # using NetworkDynamicsInspector
233
- # inspect(sol)
343
+ You can also visualize the results interactively using NetworkDynamicsInspector:
344
+
345
+ ```julia
346
+ using NetworkDynamicsInspector
347
+ inspect(sol)
348
+ ```
349
+ =#
0 commit comments