Skip to content

Commit 8d338ac

Browse files
committed
add set_interface_defaults! method
1 parent 44d6eda commit 8d338ac

File tree

4 files changed

+104
-23
lines changed

4 files changed

+104
-23
lines changed

docs/examples/init_tutorial.jl

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ nothing #hide
4444
#=
4545
Now we'll define three specific node types:
4646
47-
1. A constant pressure node that forces pressure to maintain a specific value
47+
**A) A constant pressure node that forces pressure to maintain a specific value**
4848
=#
4949
@mtkmodel ConstantPressureNode begin
5050
@extend GasNode()
@@ -58,7 +58,7 @@ end
5858
nothing #hide
5959

6060
#=
61-
2. A static prosumer node which forces a certain flow (pressure is fully implicit)
61+
**B) A static prosumer node which forces a certain flow (pressure is fully implicit)**
6262
=#
6363
@mtkmodel StaticProsumerNode begin
6464
@extend GasNode()
@@ -72,7 +72,7 @@ end
7272
nothing #hide
7373

7474
#=
75-
3. A dynamic prosumer node with compliance, which adds dynamics to the pressure state
75+
**C) A dynamic prosumer node with compliance, which adds dynamics to the pressure state**
7676
=#
7777
@mtkmodel DynamicProsumerNode begin
7878
@extend GasNode()
@@ -87,7 +87,7 @@ end
8787
nothing #hide
8888

8989
#=
90-
4. A pressure control node that tries to maintain a set pressure by adjusting its injection
90+
**D) A pressure control node that tries to maintain a set pressure by adjusting its injection**
9191
=#
9292
@mtkmodel PressureControlNode begin
9393
@extend GasNode()
@@ -206,7 +206,7 @@ u_static = find_fixpoint(nw_static, u_static_guess)
206206
207207
Now we'll define our dynamic model using more complex components:
208208
=#
209-
@named v1_mod_dyn = PressureControlNode(;p_set=1)
209+
@named v1_mod_dyn = PressureControlNode()
210210
v1_dyn = VertexModel(v1_mod_dyn, [:q̃_nw], [:p], vidx=1)
211211

212212
@named v2_mod_dyn = DynamicProsumerNode(q̃_prosumer=-0.6)
@@ -236,37 +236,62 @@ nw_dyn = Network([v1_dyn, v2_dyn, v3_dyn], [p12_dyn, p13_dyn, p23_dyn])
236236
Now comes the important part: we need to initialize the interface values (pressures and flows)
237237
of the dynamic model with the results from the static model.
238238
239-
First, let's handle node 1 (the pressure control node):
239+
We can do this manually:
240240
=#
241-
241+
## Vertex 1: output
242242
set_default!(nw_dyn[VIndex(1)], :p, u_static.v[1, :p])
243+
## Vertex 1: input
243244
set_default!(nw_dyn[VIndex(1)], :q̃_nw, u_static.v[1, :q̃_nw])
244-
v1_dyn # hide
245+
nothing #hide
246+
247+
#=
248+
But there is also a built in method [`set_interface_defaults!`](@ref) which we can use
249+
automaticially:
250+
=#
251+
set_interface_defaults!(nw_dyn, u_static; verbose=true)
252+
nothing #hide
253+
245254
#=
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:
255+
With the interfaces all set, we can "initialize" the internal states of the dynamic models.
256+
257+
For example, let's inspect the state of our first vertex:
258+
=#
259+
nw_dyn[VIndex(1)]
260+
261+
#=
262+
We observe, that both the initial state `ξ` as well as the pressure setpoint `p_set`
263+
is left "free". Using [`initialize_component!`](@ref), we can try to find values for the
264+
"free" states and parameters such that the interface constraints are fulfilled.
248265
=#
249-
initialize_component!(v1_dyn)
250-
v1_dyn #hide
266+
initialize_component!(nw_dyn[VIndex(1)])
267+
#=
268+
We may also use [`dump_initial_state`](@ref) to get a more detailed view of the state:
269+
=#
270+
dump_initial_state(nw_dyn[VIndex(1)])
271+
nothing #hide
251272

252273
#=
253-
For the other two vertices (which are simpler), we just need to set the default values:
274+
We can also initialize the other two vertices, however it is a bit useless
275+
since their state is allready completely determined by the fixed input/output:
254276
=#
255-
set_default!(nw_dyn[VIndex(2)], :p, u_static.v[2, :p])
256-
set_default!(nw_dyn[VIndex(3)], :p, u_static.v[3, :p])
277+
initialize_component!(nw_dyn[VIndex(2)])
278+
initialize_component!(nw_dyn[VIndex(3)])
257279
nothing #hide
258280

259281
#=
260-
For the pipe models, we manually initialize the flow state of the dynamic line model:
282+
Similarily, we can initialize the dynamic pipe models. However since their dynamic state
283+
equals the output, once again there is nothing to initialize.
261284
=#
262-
set_default!(nw_dyn[EIndex(1)], :q̃, u_static.e[1, :q̃])
263-
set_default!(nw_dyn[EIndex(2)], :q̃, u_static.e[2, :q̃])
264-
set_default!(nw_dyn[EIndex(3)], :q̃, u_static.e[3, :q̃])
285+
initialize_component!(nw_dyn[EIndex(1)])
286+
initialize_component!(nw_dyn[EIndex(2)])
287+
initialize_component!(nw_dyn[EIndex(3)])
265288
nothing #hide
266289

267290
#=
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:
291+
Now, everything is initialized, which means every input, output, state and parameter
292+
either has a `default` metadata or a `init` metadate. When constructing the `NWState`
293+
for this network, it will be filled with all those values which should now correspond
294+
to a steady state of the system:
270295
=#
271296
u0_dyn = NWState(nw_dyn)
272297

docs/src/API.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ init_residual
164164
get_initial_state
165165
dump_initial_state
166166
set_defaults!
167+
set_interface_defaults!
167168
```
168169

169170
## Execution Types

src/NetworkDynamics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ include("callbacks.jl")
8787
using NonlinearSolve: AbstractNonlinearSolveAlgorithm, NonlinearFunction
8888
using NonlinearSolve: NonlinearLeastSquaresProblem, NonlinearProblem
8989
using SteadyStateDiffEq: SteadyStateProblem, SteadyStateDiffEqAlgorithm, SSRootfind
90-
export find_fixpoint, initialize_component!, init_residual
90+
export find_fixpoint, initialize_component!, init_residual, set_interface_defaults!
9191
include("initialization.jl")
9292

9393
include("show.jl")

src/initialization.jl

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ function initialization_problem(cf::T; t=NaN, apply_bound_transformation, verbos
6666

6767
# count free variables and equations
6868
Nfree = mapreduce(sum, +, outfree_ms) + sum(ufree_m) + mapreduce(sum, +, infree_ms) + sum(pfree_m)
69+
70+
# return "fake NonlinearProblem" if no free variables
71+
iszero(Nfree) && return ((; u0=[]), identity)
72+
6973
Neqs = dim(cf) + mapreduce(length, +, outfree_ms)
7074

7175

@@ -269,7 +273,11 @@ function initialize_component!(cf; verbose=true, apply_bound_transformation=true
269273
resid = sol.resid
270274
else
271275
resid = init_residual(cf; recalc=true)
276+
if resid < 1e-10
272277
verbose && @info "No free variables! Residual $(LinearAlgebra.norm(resid))"
278+
else
279+
@warn "No free variables! However model does not appear to be initialized in steady state. Residual $(LinearAlgebra.norm(resid))"
280+
end
273281
end
274282

275283
set_metadata!(cf, :init_residual, resid)
@@ -321,7 +329,7 @@ function init_residual(cf::T; t=NaN, recalc=false) where {T<:ComponentModel}
321329
end
322330

323331
function broken_bounds(cf)
324-
allsyms = vcat(sym(cf), psym(cf), insym_all(cf), outsym_flat(cf), obssym(cf))
332+
allsyms = vcat(sym(cf), psym(cf), insym_flat(cf), outsym_flat(cf), obssym(cf))
325333
boundsyms = filter(s -> has_bounds(cf, s), allsyms)
326334
bounds = get_bounds.(Ref(cf), boundsyms)
327335
vals = get_initial_state(cf, boundsyms)
@@ -349,3 +357,50 @@ function set_defaults!(nw::Network, s::NWState)
349357
end
350358
nw
351359
end
360+
361+
"""
362+
set_interface_defaults!(nw::Network, s::NWState; verbose=false)
363+
364+
Sets the **interface** (i.e. node and edge inputs/outputs) defaults of a given
365+
network to the ones defined by the given state. Notably, while the graph
366+
topology and interface dimensions of the target network `nw` and the source
367+
network of `s` musst be identicaly, the systems may differ in the dynamical
368+
components.
369+
370+
This is mainly inteded for initialization purposes: solve the interface values
371+
with a simpler -- possible static -- network and "transfer" the steady state
372+
interface values to the full network.
373+
"""
374+
function set_interface_defaults!(nw::Network, s::NWState; verbose=false)
375+
@argcheck s.nw.im.g == nw.im.g "Graphs musst have the same structure!"
376+
verbose && println("Setting the interface defaults:")
377+
values = Dict{SymbolicIndex, Float64}()
378+
for (idxs, _Index, comp) in ((1:nv(nw), VIndex, "Vertex"), (1:ne(nw), EIndex, "Edge"))
379+
for i in idxs
380+
target = nw[_Index(i)]
381+
source = s.nw[_Index(i)]
382+
383+
tsyms = _Index(i, outsym_flat(target)) |> collect
384+
svals = s[_Index(i, outsym_flat(source))]
385+
@assert length(tsyms) == length(svals) "$comp $i: Source network has $(length(svals)) outputs, but target network has $(length(tsyms)) outputs!"
386+
for (tsym, sval) in zip(tsyms, svals)
387+
values[tsym] = sval
388+
verbose && println(" $comp $(lpad(i, length(repr(idxs[end])))) \
389+
output: $(tsym.subidx) => $sval")
390+
end
391+
392+
tsyms = _Index(i, insym_flat(target)) |> collect
393+
svals = s[_Index(i, insym_flat(source))]
394+
@assert length(tsyms) == length(svals) "$comp $i: Source network has $(length(svals)) inputs, but target network has $(length(tsyms)) inputs!"
395+
for (tsym, sval) in zip(tsyms, svals)
396+
values[tsym] = sval
397+
verbose && println(" $comp $(lpad(i, length(repr(idxs[end])))) \
398+
input: $(tsym.subidx) => $sval")
399+
end
400+
end
401+
end
402+
for (sym, val) in values
403+
set_default!(nw, sym, val)
404+
end
405+
nw
406+
end

0 commit comments

Comments
 (0)