Skip to content

Commit 0f9e944

Browse files
committed
start working on yaml import
1 parent 8ba6421 commit 0f9e944

File tree

4 files changed

+289
-0
lines changed

4 files changed

+289
-0
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
3232
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
3333
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
3434
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
35+
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
3536

3637
[weakdeps]
3738
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
@@ -81,4 +82,5 @@ SymbolicIndexingInterface = "0.3.27"
8182
SymbolicUtils = "3.24"
8283
Symbolics = "6.19.0"
8384
TimerOutputs = "0.5.23"
85+
YAML = "0.4.12"
8486
julia = "1.10"

src/NetworkDynamics.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ using InteractiveUtils: subtypes
3535

3636
import SymbolicIndexingInterface as SII
3737
using StaticArrays: StaticArrays, SVector
38+
using YAML: YAML
39+
using OrderedCollections: OrderedDict
3840

3941
include("utils.jl")
4042

@@ -96,6 +98,9 @@ const CHECK_COMPONENT = Ref(true)
9698
export chk_component
9799
include("doctor.jl")
98100

101+
export load_network
102+
include("importexport.jl")
103+
99104
#=
100105
using StyledStrings
101106
s1 = styled"{bright_red:brred} {bright_green:brgreen} {bright_yellow:bryellow} {bright_blue:brblue} {bright_magenta:brmagenta} {bright_cyan:brcyan} {bright_black:brblack} {bright_white:brwhite}";

src/importexport.jl

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""
2+
For a given model, collect all referenced models
3+
"""
4+
collect_model_dependencies(model) = unique!(_collect_model_dependencies(String[], model))
5+
_collect_model_dependencies(deps, other) = deps
6+
function _collect_model_dependencies(deps, model::Union{OrderedDict,Vector})
7+
for val in values(model)
8+
if val isa String
9+
m = match(r"^Models.(.*)$", val)
10+
if !isnothing(m)
11+
push!(deps, m[1])
12+
end
13+
else
14+
_collect_model_dependencies(deps, val)
15+
end
16+
end
17+
deps
18+
end
19+
20+
"""
21+
recursiv_resolve!(models, container)
22+
23+
Go through container, and replace every occurence of "Models.<modelname>" with
24+
deepcopy of actual model dict.
25+
"""
26+
recursive_resolve!(models, _) = nothing
27+
function recursive_resolve!(models, container::Union{OrderedDict,Vector})
28+
for (key, value) in pairs(container)
29+
if value isa String
30+
m = match(r"^Models.(.*)$", value)
31+
if !isnothing(m)
32+
container[key] = deepcopy(models[m[1]])
33+
end
34+
else
35+
recursive_resolve!(models, value)
36+
end
37+
end
38+
nothing
39+
end
40+
41+
"""
42+
update_parent_property!(parent, key, value)
43+
44+
Update the property in the parent structure.
45+
- if key is nested (i.e. "a.b.c"), it will update parent["a"]["b"]["c"]
46+
- if key is indexed (i.e. "a[1]"), it will update parent["a"][1]
47+
"""
48+
function update_parent_property!(parent, key, value)
49+
@info "Update $key -> $value"
50+
if contains(key, ".")
51+
key_head, key_tail = split(key, ".", limit=2)
52+
@info "Found nested key $key_head -> $key_tail"
53+
m = match(r"^(.*)\[(.*)\]$", key_head)
54+
_parent = if !isnothing(m)
55+
parent[m[1]][parse(Int, m[2])]
56+
else
57+
parent[key]
58+
end
59+
@info m
60+
update_parent_property!(_parent, key_tail, value)
61+
else
62+
m = match(r"^(.*)\[(.*)\]$", key)
63+
if !isnothing(m)
64+
if !(value isa eltype(parent[m[1]]))
65+
# need to widen type
66+
parent[m[1]] = collect(Any, parent[m[1]])
67+
end
68+
parent[m[1]][parse(Int, m[2])] = value
69+
else
70+
parent[key] = value
71+
end
72+
end
73+
end
74+
"""
75+
merge_properties!(parent, child)
76+
77+
Takes a child dict, and applies every key-value pair to the parent dict.
78+
keys will be resolved using update_parent_property!
79+
"""
80+
function merge_properties!(parent, child)
81+
for (key, value) in pairs(child)
82+
update_parent_property!(parent, key, value)
83+
end
84+
parent
85+
end
86+
87+
"""
88+
depth_first_flatten!(container)
89+
90+
This goes through the container, and if it finds a dict with key "MODEL", it will
91+
merge the properties of the referenced parent model with the current dict.
92+
"""
93+
depth_first_flatten!(_) = nothing
94+
function depth_first_flatten!(container::Union{OrderedDict,Vector})
95+
for (key, sub) in pairs(container)
96+
depth_first_flatten!(sub)
97+
if sub isa Union{OrderedDict} && haskey(sub, "MODEL")
98+
parent_model = sub["MODEL"]
99+
delete!(sub, "MODEL")
100+
container[key] = merge_properties!(parent_model, sub)
101+
end
102+
end
103+
nothing
104+
end
105+
106+
function parse_network(file)
107+
data = YAML.load_file(file, dicttype=OrderedDict{Any,Any})
108+
dependencies = OrderedDict(keys(data["Models"]) .=> map(collect_model_dependencies, values(data["Models"])))
109+
110+
# dependencies muss by subset of models
111+
@assert reduce(vcat, values(dependencies)) keys(data["Models"])
112+
resolved_models = findall(deps -> isempty(deps), dependencies)
113+
delete!.(Ref(dependencies), resolved_models) # delete resolve dependnecies
114+
115+
# resolve all models, i.e. replace parent/template models with full dict
116+
while true
117+
isempty(dependencies) && break
118+
resolved_something = false
119+
for (mod, deps) in dependencies
120+
if deps resolved_models
121+
@info "Resolving $mod -> $deps"
122+
recursive_resolve!(data["Models"], data["Models"][mod])
123+
delete!(dependencies, mod)
124+
push!(resolved_models, mod)
125+
resolved_something = true
126+
else
127+
@info "Skip $mod -> $deps"
128+
end
129+
end
130+
resolved_something && continue
131+
error("Could not resolve all dependencies")
132+
end
133+
134+
# with all deps resolve, we can replace the models edge and vertex list
135+
recursive_resolve!(data["Models"], data["Vertices"])
136+
recursive_resolve!(data["Models"], data["Edges"])
137+
138+
# traverses the tree and flattens the model properties, i.e. merges
139+
# the local given properties with the parent/template model properties
140+
depth_first_flatten!(data)
141+
return data
142+
end
143+
144+
recursive_construct(constructors, data) = _depth_first_construct!(constructors, deepcopy(data))
145+
_depth_first_construct!(_, data) = data
146+
_depth_first_construct!(_, s::String) = startswith(s, ":") ? Symbol(s[2:end]) : s
147+
function _depth_first_construct!(constructors, data::Vector)
148+
if data isa Vector && eltype(data) !== Any
149+
data = convert(Vector{Any}, data)
150+
end
151+
for (key, value) in pairs(data)
152+
data[key] = _depth_first_construct!(constructors, value)
153+
end
154+
data
155+
end
156+
function _depth_first_construct!(constructors, data::OrderedDict)
157+
if data isa OrderedDict && eltype(values(data)) !== Any
158+
data = convert(OrderedDict{eltype(keys(data)), Any}, data)
159+
end
160+
161+
for (key, value) in pairs(data)
162+
data[key] = _depth_first_construct!(constructors, value)
163+
end
164+
if haskey(data, "CONSTRUCTOR")
165+
if !haskey(constructors, data["CONSTRUCTOR"])
166+
error("No constructor found for $(data["CONSTRUCTOR"])")
167+
end
168+
args = get(data, "ARGS", [])
169+
kwargs = Dict{Symbol, Any}()
170+
for (key, value) in data
171+
key ("CONSTRUCTOR", "ARGS") && continue
172+
kwargs[Symbol(key)] = value
173+
end
174+
return constructors[data["CONSTRUCTOR"]](args...; kwargs...)
175+
end
176+
data
177+
end
178+
179+
function build_network(data, constructors)
180+
vertexm = VertexModel[]
181+
for (k, v) in data["Vertices"]
182+
vm = recursive_construct(constructors, v)
183+
set_graphelement!(vm, k)
184+
push!(vertexm, vm)
185+
end
186+
edgem = EdgeModel[]
187+
for (k, v) in data["Vertices"]
188+
vm = recursive_construct(constructors, v)
189+
190+
m = match(r"^(.*)=>(.*)$", k)
191+
isnothing(m) && error("Edge key must be of form 'src=>dst', got $k")
192+
src = tryparse(Int, m[1])
193+
dst = tryparse(Int, m[2])
194+
isnothing(src) && src = m[1]
195+
isnothing(dst) && src = m[1]
196+
set_graphelement!(vm, src, dst)
197+
push!(vertexm, vm)
198+
end
199+
end

test/importexport_test.jl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using NetworkDynamics
2+
using ModelingToolkit
3+
using ModelingToolkit: t_nounits as t, D_nounits as Dt
4+
5+
@mtkmodel Swing begin
6+
@variables begin
7+
θ(t) = 0.0, [description = "voltage angle", output=true]
8+
ω(t) = 0.0, [description = "Rotor frequency"]
9+
Pel(t), [description = "Electical power flowing from network into node", input=true]
10+
Pdamping(t), [description = "Damping Power (observed)"]
11+
end
12+
@parameters begin
13+
M = 1, [description = "Inertia"]
14+
D = 0.1, [description = "Damping"]
15+
Pmech=1.0, [description = "Mechanical Power"]
16+
end
17+
@equations begin
18+
Dt(θ) ~ ω
19+
Pdamping ~ - D * ω
20+
Dt(ω) ~ 1/M * (Pmech + Pdamping + Pel)
21+
end
22+
end
23+
24+
@mtkmodel Load begin
25+
@parameters begin
26+
Pd = -1.0, [description = "Power Load"]
27+
end
28+
@variables begin
29+
θ(t) = 0.0, [description = "voltage angle", output=true]
30+
Pel(t), [description = "Electrical power flowing from network into node", input=true]
31+
end
32+
@equations begin
33+
0 ~ Pel + Pd
34+
end
35+
end
36+
37+
@mtkmodel StaticPowerLine begin
38+
@structural_parameters begin
39+
K = 1.63 # structural parameter to keep them out of nw parameters
40+
end
41+
@variables begin
42+
srcθ(t), [description = "voltage angle at src end", input=true]
43+
dstθ(t), [description = "voltage angle at dst end", input=true]
44+
P(t), [description = "flow towards node at dst end", output=true]
45+
Δθ(t), [description = "Voltage angle difference"]
46+
end
47+
@equations begin
48+
Δθ ~ srcθ - dstθ
49+
P ~ K*sin(Δθ)
50+
end
51+
end
52+
53+
function CombinedModel(subsystems...; kwargs...)
54+
@variables begin
55+
θ(t) = 0.0, [description = "voltage angle", output=true]
56+
Pel(t), [description = "Electical power flowing from network into node", input=true]
57+
end
58+
θ_eqs =~ sub.θ for sub in subsystes]
59+
Pel_eqs = [Pel ~ sub.Pel for sub in subsystems]
60+
ODESystem(; kwargs...)
61+
end
62+
63+
mtk_models = [
64+
Swing(Pmech=1, M=1, D=1.0; name=:G1),
65+
Load(Pload; name=:L2),
66+
Swing(Pmech=1, M=1, D=1.0; name=:G2),
67+
Load(Pload; name=:L3),
68+
Swing(Pmech=1, M=1, D=1.0; name=:G3),
69+
Load(Pload; name=:L4),
70+
];
71+
72+
vertexmodels = map(mtk_models) do mtk_model
73+
VertexModel(mtk_model, [:Pel], [])
74+
end;
75+
vertexmodels2 = map(mtk_models2) do mtk_model
76+
VertexModel(mtk_model, [:Pel], [])
77+
end;
78+
79+
80+
data = NetworkDynamics.parse_network("testgrid.yaml")
81+
82+
constructors = Dict("Swing"=>Swing, "VertexModel"=>VertexModel, "Load"=>Load)
83+
NetworkDynamics.build_network(data, constructors)

0 commit comments

Comments
 (0)