|
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