|
2 | 2 |
|
3 | 3 | Interactive solution inspection tool based on [WGLMakie](https://makie.org/website/) and [Bonito](https://github.com/SimonDanisch/Bonito.jl) are provided through the helper package `NetworkDynamicsInspector`.
|
4 | 4 |
|
| 5 | +Firstoff, we need to define the system we want to interactively inspect. |
5 | 6 |
|
6 |
| -```@example ndi |
7 |
| -using NetworkDynamics |
8 |
| -using NetworkDynamicsInspector |
9 |
| -using NetworkDynamicsInspector: NetworkDynamicsInspector as NDI #hide |
10 |
| -using Electron # hide |
11 |
| -using OrdinaryDiffEqTsit5 |
12 |
| -using Graphs |
13 |
| -
|
14 |
| -include(joinpath(pkgdir(NetworkDynamics), "test", "ComponentLibrary.jl")) |
15 |
| -function get_sol(;limit=1.0) |
16 |
| - g = SimpleGraph([0 1 1 0 1; |
17 |
| - 1 0 1 1 0; |
18 |
| - 1 1 0 1 0; |
19 |
| - 0 1 1 0 1; |
20 |
| - 1 0 0 1 0]) |
21 |
| - vs = [Lib.swing_mtk() for _ in 1:5]; |
22 |
| - set_default!(vs[1], :Pmech, -1) |
23 |
| - set_default!(vs[2], :Pmech, 1.5) |
24 |
| - set_default!(vs[3], :Pmech, -1) |
25 |
| - set_default!(vs[4], :Pmech, -1) |
26 |
| - set_default!(vs[5], :Pmech, 1.5) |
27 |
| - ls = [Lib.line_mtk() for _ in 1:7]; |
28 |
| - nw = Network(g, vs, ls) |
29 |
| - sinit = NWState(nw) |
30 |
| - s0 = find_fixpoint(nw) |
31 |
| - set_defaults!(nw, s0) |
32 |
| -
|
33 |
| - # set_position!(vs[1], (0.0, 0.0)) |
34 |
| - set_marker!(vs[1], :dtriangle) |
35 |
| - set_marker!(vs[2], :utriangle) |
36 |
| - set_marker!(vs[3], :dtriangle) |
37 |
| - set_marker!(vs[4], :dtriangle) |
38 |
| - set_marker!(vs[5], :utriangle) |
39 |
| -
|
40 |
| - cond = ComponentCondition([:P, :₋P, :srcθ], [:limit, :K]) do u, p, t |
41 |
| - abs(u[:P]) - p[:limit] |
42 |
| - end |
43 |
| - affect = ComponentAffect([],[:active]) do u, p, ctx |
44 |
| - @info "Trip line $(ctx.eidx) between $(ctx.src) and $(ctx.dst) at t=$(ctx.t)" |
45 |
| - p[:active] = 0 |
46 |
| - end |
47 |
| - cb = ContinousComponentCallback(cond, affect) |
48 |
| - set_callback!.(ls, Ref(cb)) |
| 7 | +!!! details "Define some network, simulate it and get a solution object" |
| 8 | + ```@example ndi |
| 9 | + using NetworkDynamics |
| 10 | + using NetworkDynamicsInspector |
| 11 | + using NetworkDynamicsInspector: NetworkDynamicsInspector as NDI #hide |
| 12 | + using Electron # hide |
| 13 | + using OrdinaryDiffEqTsit5 |
| 14 | + using Graphs |
49 | 15 |
|
50 |
| - tripfirst = PresetTimeComponentCallback(1.0, affect) # reuse the same affect |
51 |
| - add_callback!(nw[EIndex(5)], tripfirst) |
| 16 | + include(joinpath(pkgdir(NetworkDynamics), "test", "ComponentLibrary.jl")) |
| 17 | + function get_sol(;limit=1.0) |
| 18 | + g = SimpleGraph([0 1 1 0 1; |
| 19 | + 1 0 1 1 0; |
| 20 | + 1 1 0 1 0; |
| 21 | + 0 1 1 0 1; |
| 22 | + 1 0 0 1 0]) |
| 23 | + vs = [Lib.swing_mtk() for _ in 1:5]; |
| 24 | + set_default!(vs[1], :Pmech, -1) |
| 25 | + set_default!(vs[2], :Pmech, 1.5) |
| 26 | + set_default!(vs[3], :Pmech, -1) |
| 27 | + set_default!(vs[4], :Pmech, -1) |
| 28 | + set_default!(vs[5], :Pmech, 1.5) |
| 29 | + ls = [Lib.line_mtk() for _ in 1:7]; |
| 30 | + nw = Network(g, vs, ls) |
| 31 | + sinit = NWState(nw) |
| 32 | + s0 = find_fixpoint(nw) |
| 33 | + set_defaults!(nw, s0) |
52 | 34 |
|
53 |
| - nwcb = NetworkDynamics.get_callbacks(nw); |
54 |
| - s0 = NWState(nw) |
55 |
| - s0.p.e[:, :limit] .= limit |
| 35 | + # set_position!(vs[1], (0.0, 0.0)) |
| 36 | + set_marker!(vs[1], :dtriangle) |
| 37 | + set_marker!(vs[2], :utriangle) |
| 38 | + set_marker!(vs[3], :dtriangle) |
| 39 | + set_marker!(vs[4], :dtriangle) |
| 40 | + set_marker!(vs[5], :utriangle) |
56 | 41 |
|
57 |
| - prob = ODEProblem(nw, uflat(s0), (0,6), copy(pflat(s0)), callback=nwcb) |
58 |
| - sol = solve(prob, Tsit5()) |
59 |
| -end |
| 42 | + cond = ComponentCondition([:P, :₋P, :srcθ], [:limit, :K]) do u, p, t |
| 43 | + abs(u[:P]) - p[:limit] |
| 44 | + end |
| 45 | + affect = ComponentAffect([],[:active]) do u, p, ctx |
| 46 | + @info "Trip line $(ctx.eidx) between $(ctx.src) and $(ctx.dst) at t=$(ctx.t)" |
| 47 | + p[:active] = 0 |
| 48 | + end |
| 49 | + cb = ContinousComponentCallback(cond, affect) |
| 50 | + set_callback!.(ls, Ref(cb)) |
60 | 51 |
|
61 |
| -sol = get_sol() |
62 |
| -sleep(1) # hide |
63 |
| -inspect(sol; restart=false, reset=true) |
| 52 | + tripfirst = PresetTimeComponentCallback(1.0, affect) # reuse the same affect |
| 53 | + add_callback!(nw[EIndex(5)], tripfirst) |
| 54 | + |
| 55 | + nwcb = NetworkDynamics.get_callbacks(nw); |
| 56 | + s0 = NWState(nw) |
| 57 | + s0.p.e[:, :limit] .= limit |
| 58 | + |
| 59 | + prob = ODEProblem(nw, uflat(s0), (0,6), copy(pflat(s0)), callback=nwcb) |
| 60 | + sol = solve(prob, Tsit5()) |
| 61 | + end |
| 62 | + |
| 63 | + sol = get_sol() |
| 64 | + ``` |
| 65 | + |
| 66 | +No that we have and `ODESolution` `sol`, we can call [`inspect`](@ref) to open the inspector gui. In the docstring you can find several options to chose how the app is displayed. |
| 67 | + |
| 68 | +```@example ndi |
| 69 | +inspect(sol; reset=true) |
64 | 70 | sleep(1) # hide
|
65 |
| -define_timeseries!([ |
66 |
| - (; selcomp=[EIndex(i) for i in 1:7], states=[:P]) |
67 |
| -]) |
| 71 | +define_timeseries!([ # hide |
| 72 | + (; selcomp=[EIndex(i) for i in 1:7], states=[:P]) # hide |
| 73 | +]) # hide |
| 74 | +sleep(3) # hide |
68 | 75 | NDI.save_electron_screenshot("screenshot.png") #hide
|
| 76 | +``` |
| 77 | + |
69 | 78 |
|
70 | 79 |
|
71 |
| -set_state!(; t=2.0) #hide |
72 |
| -sleep(1) # hide |
| 80 | +## Programmatric Acces and GUI State manipulation |
| 81 | +Internally, the `NetworkDynamicsInspector` holds a global reference to an object `AppState`. This AppState reflects the changes the user made to the GUI and can be also altered programmaticially. |
| 82 | + |
| 83 | +See [NetworkDynamicsInspector API](@ref) for a list of all available function. |
| 84 | +As a good starting point, there is a function [`dump_app_state`](@ref) |
| 85 | +which helps you to recreate the GUI state which was previously adjusted by hand. |
| 86 | + |
| 87 | +Lets say we've adjuste the appstate to include another timeseries plot for the node states. |
| 88 | + |
| 89 | +```@example ndi |
| 90 | +set_state!(; t=1.75) #hide |
73 | 91 | define_timeseries!([ #hide
|
74 | 92 | (; selcomp=[VIndex(i) for i in 1:5], states=[:θ, :ω]) #hide
|
75 | 93 | (; selcomp=[EIndex(i) for i in 1:7], states=[:P]) #hide
|
76 | 94 | ]) #hide
|
77 |
| -sleep(1) #hide |
78 |
| -
|
| 95 | +sleep(3) #hide |
79 | 96 | nothing #hide
|
80 | 97 | ```
|
81 |
| - |
82 | 98 |
|
83 |
| -## Programmatric Acces and GUI State manipulation |
| 99 | +We can dump the code which helps us to recreate the app state: |
84 | 100 | ```@example ndi
|
85 | 101 | dump_app_state()
|
86 | 102 | ```
|
87 | 103 |
|
| 104 | +Now we can use this code to recreate the app state even though we've reseted it. |
88 | 105 | ```@example ndi
|
89 | 106 | buf = IOBuffer() #hide
|
90 | 107 | dump_app_state(buf) #hide
|
91 | 108 | code = String(take!(buf)) #hide
|
92 |
| -sleep(1) #hide |
93 |
| -inspect(sol; reset=true) #hide |
| 109 | +inspect(sol; reset=true) |
94 | 110 | sleep(1) #hide
|
95 | 111 | eval(Meta.parse("begin;"*code*"end;")) #hide
|
96 |
| -sleep(1) #hide |
| 112 | +sleep(3) #hide |
97 | 113 | NDI.save_electron_screenshot("screenshot2.png") #hide
|
98 |
| -nothing #hide |
| 114 | +"copy-paste and execute code returned by `dump_app_state` here" |
99 | 115 | ```
|
100 | 116 | 
|
0 commit comments