Skip to content

Commit d42c29a

Browse files
authored
Merge pull request #299 from JuliaDynamics/hw/metadata
add metadata helper functions
2 parents e938b66 + 387e0bc commit d42c29a

File tree

7 files changed

+239
-10
lines changed

7 files changed

+239
-10
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# NetworkDynamics Release Notes
22

3+
## v0.10.2 Changelog
4+
- [#299](https://github.com/JuliaDynamics/NetworkDynamics.jl/pull/299) enhance metadata system with pattern matching and utility functions:
5+
- Add String/Regex pattern matching for all metadata functions (`has_metadata`, `get_metadata`, `set_metadata!`, etc.)
6+
- Add `strip_*!` functions to remove all metadata of a specific type from components
7+
- Add `free_u()` and `free_p()` functions to identify variables/parameters without default values
8+
- Support removing metadata by passing `nothing` or `missing` to `set_*!` functions
9+
310
## v0.10.1 Changelog
411
- [#294](https://github.com/JuliaDynamics/NetworkDynamics.jl/pull/294) add linear stability analysis functions: `isfixpoint`, `jacobian_eigenvals`, and `is_linear_stable` with support for both ODE and DAE systems
512
- [#283](https://github.com/JuliaDynamics/NetworkDynamics.jl/pull/283) add automatic sparsity detection using `get_jac_prototype` and `set_jac_prototype!`

docs/src/API.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,28 +121,35 @@ get_metadata(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
121121
has_metadata(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
122122
set_metadata!(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol, ::Any)
123123
delete_metadata!(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
124+
strip_metadata!
124125
has_default
125126
get_default
126127
set_default!
127128
delete_default!
129+
strip_defaults!
128130
has_guess
129131
get_guess
130132
set_guess!
131133
delete_guess!
134+
strip_guesses!
132135
has_init
133136
get_init
134137
set_init!
135138
delete_init!
139+
strip_inits!
136140
has_bounds
137141
get_bounds
138142
set_bounds!
139143
delete_bounds!
144+
strip_bounds!
140145
set_defaults!
141146
set_interface_defaults!
142147
get_defaults_dict
143148
get_guesses_dict
144149
get_bounds_dict
145150
get_inits_dict
151+
free_u
152+
free_p
146153
```
147154

148155
### Metadata and Inspection Utils

docs/src/metadata.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ details.
2222
## Symbol Metadata
2323
Each component stores symbol metadata. The symbol metadata is a `Dict{Symbol, Dict{Symbol, Any}}` which stores a metadata dict per symbol. Symbols are everything that appears in [`sym`](@ref), [`psym`](@ref), [`obssym`](@ref) and [`insym`](@ref).
2424

25-
To access the data, you can use the methods `has_metadata`, `get_metadata`, `set_metadata!` and `delete_metadata!` (see [Per Symbol Metadata API](@ref)).
25+
To access the data, you can use the methods `has_metadata`, `get_metadata`, `set_metadata!` and `delete_metadata!` (see [Per Symbol Metadata API](@ref)). These functions also support pattern matching using String or Regex patterns to match symbol names, making it easier to work with symbols containing special characters or when you only know part of the symbol name.
2626

2727
Special cases for symbol metadata are:
2828

@@ -31,7 +31,7 @@ Special cases for symbol metadata are:
3131
- `bounds`: Stores bounds for variables/parameters
3232
- `init`: Stores the solution of the "free" variables, this is rarely set manually but instead when calling [`initialize_component!`](@ref).
3333

34-
For those, there are special functions `has_*`, `get_*`, `set_*!` and `delete_*!`. See [Per Symbol Metadata API](@ref).
34+
For those, there are special functions `has_*`, `get_*`, `set_*!`, `delete_*!` and `strip_*!`. The `strip_*!` functions remove all metadata of a specific type from all symbols in a component. See [Per Symbol Metadata API](@ref).
3535

3636
These are closely aligned with the [metadata use in ModelingToolkit](@extref ModelingToolkit symbolic_metadata). They are automatically copied from the `ODESystem` if you use MTK models to create NetworkDynamics models.
3737

@@ -42,5 +42,7 @@ common task. We provide several helper methods to do so. Please check out their
4242
- [`dump_state`](@ref)
4343
- [`dump_initial_state`](@ref)
4444
- [`get_initial_state`](@ref)
45+
- [`free_u`](@ref) - find variables without default values
46+
- [`free_p`](@ref) - find parameters without default values
4547
- [`describe_vertices`](@ref) (needs `DataFrames.jl` loaded)
4648
- [`describe_edges`](@ref) (needs `DataFrames.jl` loaded)

src/NetworkDynamics.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@ export initialize_component, initialize_component!, init_residual
8888
export initialize_componentwise, initialize_componentwise!, interface_values
8989
include("initialization.jl")
9090

91-
export has_metadata, get_metadata, set_metadata!, delete_metadata!
92-
export has_default, get_default, set_default!, delete_default!, set_defaults!
93-
export has_guess, get_guess, set_guess!, delete_guess!
94-
export has_init, get_init, set_init!, delete_init!
95-
export has_bounds, get_bounds, set_bounds!, delete_bounds!
91+
export has_metadata, get_metadata, set_metadata!, delete_metadata!, strip_metadata!
92+
export has_default, get_default, set_default!, delete_default!, set_defaults!, strip_defaults!
93+
export has_guess, get_guess, set_guess!, delete_guess!, strip_guesses!
94+
export has_init, get_init, set_init!, delete_init!, strip_inits!
95+
export has_bounds, get_bounds, set_bounds!, delete_bounds!, strip_bounds!
9696
export has_graphelement, get_graphelement, set_graphelement!
9797
export get_initial_state, dump_initial_state, dump_state
9898
export has_callback, get_callbacks, set_callback!, add_callback!
@@ -101,6 +101,7 @@ export has_initformula, get_initformulas, set_initformula!, add_initformula!, de
101101
export has_position, get_position, set_position!
102102
export has_marker, get_marker, set_marker!
103103
export get_defaults_dict, get_guesses_dict, get_bounds_dict, get_inits_dict
104+
export free_p, free_u
104105
include("metadata.jl")
105106

106107
export isfixpoint, is_linear_stable, jacobian_eigenvals

src/metadata.jl

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,44 @@ function _assert_symbol_exists(c::ComponentModel, s::Symbol)
1818
return nothing
1919
end
2020

21+
function _match_symbol_name(c::ComponentModel, pattern)
22+
allsym = Symbol[]
23+
append!(allsym, sym(c))
24+
append!(allsym, psym(c))
25+
hasinsym(c) && append!(allsym, insym_all(c))
26+
append!(allsym, outsym_flat(c))
27+
append!(allsym, obssym(c))
28+
allssym = unique!(allsym)
29+
30+
fidx = findall(sym -> contains(string(sym), pattern), allsym)
31+
if isempty(fidx)
32+
throw(ArgumentError("No symbol matching pattern $pattern found in component model."))
33+
elseif length(fidx) > 1
34+
throw(ArgumentError("Multiple symbols matching pattern $pattern found in component model: $(allsym[fidx]). Please use a more specific pattern."))
35+
end
36+
return Symbol(allsym[only(fidx)])
37+
end
38+
2139
"""
2240
has_metadata(c::ComponentModel, sym::Symbol, key::Symbol)
2341
has_metadata(nw::Network, sni::SymbolicIndex, key::Symbol)
2442
2543
Checks if symbol metadata `key` is present for symbol `sym` in a component model,
2644
or for a symbol referenced by `sni` in a network.
45+
46+
`sym` can also be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
47+
2748
Throws an error if the symbol does not exist in the component model.
2849
"""
2950
function has_metadata(c::ComponentModel, sym::Symbol, key::Symbol)
3051
_assert_symbol_exists(c, sym)
3152
md = symmetadata(c)
3253
haskey(md, sym) && haskey(md[sym], key)
3354
end
55+
function has_metadata(c::ComponentModel, pattern::Union{String,Regex}, key::Symbol)
56+
sym = _match_symbol_name(c, pattern)
57+
has_metadata(c, sym, key)
58+
end
3459
function has_metadata(nw::Network, sym::SymbolicIndex, key::Symbol)
3560
has_metadata(getcomp(nw, sym), sym.subidx, key)
3661
end
@@ -41,12 +66,19 @@ end
4166
4267
Retrieves the metadata `key` for symbol `sym` in a component model,
4368
or for a symbol referenced by `sni` in a network.
69+
70+
`sym` can also be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
71+
4472
Throws an error if the symbol does not exist in the component model.
4573
"""
4674
function get_metadata(c::ComponentModel, sym::Symbol, key::Symbol)
4775
_assert_symbol_exists(c, sym)
4876
symmetadata(c)[sym][key]
4977
end
78+
function get_metadata(c::ComponentModel, pattern::Union{String,Regex}, key::Symbol)
79+
sym = _match_symbol_name(c, pattern)
80+
get_metadata(c, sym, key)
81+
end
5082
function get_metadata(nw::Network, sym::SymbolicIndex, key::Symbol)
5183
get_metadata(getcomp(nw, sym), sym.subidx, key)
5284
end
@@ -59,19 +91,29 @@ end
5991
6092
Sets the metadata `key` for symbol `sym` to `value` in a component model,
6193
or for a symbol referenced by `sni` in a network.
94+
95+
For component models, you can also use a `String` or `Regex` pattern to match symbol names:
96+
- String patterns use substring matching (e.g., `"δ"` matches `machine₊δ`)
97+
- Regex patterns use full regex matching (e.g., `r"P\$"` matches symbols ending with "P")
98+
This will error if there are none or multiple matches.
99+
100+
If the pattern matches multiple symbols, an error is thrown. Use a more specific pattern.
62101
Throws an error if the symbol does not exist in the component model.
63102
"""
64103
function set_metadata!(c::ComponentModel, sym::Symbol, key::Symbol, value)
65104
_assert_symbol_exists(c, sym)
66105
d = get!(symmetadata(c), sym, Dict{Symbol,Any}())
67106
d[key] = value
68107
end
108+
function set_metadata!(c::ComponentModel, pattern::Union{String,Regex}, key::Symbol, value)
109+
sym = _match_symbol_name(c, pattern)
110+
set_metadata!(c, sym, key, value)
111+
end
69112
function set_metadata!(nw::Network, sym::SymbolicIndex, key::Symbol, value)
70113
set_metadata!(getcomp(nw, sym), sym.subidx, key, value)
71114
end
72115

73116
function set_metadata!(c::ComponentModel, sym::Symbol, pair::Pair)
74-
_assert_symbol_exists(c, sym)
75117
set_metadata!(c, sym, pair.first, pair.second)
76118
end
77119
function set_metadata!(nw::Network, sym::SymbolicIndex, pair::Pair)
@@ -84,6 +126,9 @@ end
84126
85127
Removes the metadata `key` for symbol `sym` in a component model,
86128
or for a symbol referenced by `sni` in a network.
129+
130+
`sym` can also be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
131+
87132
Returns `true` if the metadata existed and was removed, `false` otherwise.
88133
Throws an error if the symbol does not exist in the component model.
89134
"""
@@ -99,14 +144,39 @@ function delete_metadata!(c::ComponentModel, sym::Symbol, key::Symbol)
99144
end
100145
return false
101146
end
147+
function delete_metadata!(c::ComponentModel, pattern::Union{String,Regex}, key::Symbol)
148+
sym = _match_symbol_name(c, pattern)
149+
delete_metadata!(c, sym, key)
150+
end
102151
delete_metadata!(nw::Network, sym::SymbolicIndex, key::Symbol) = delete_metadata!(getcomp(nw, sym), sym.subidx, key)
103152

153+
"""
154+
strip_metadata!(c::ComponentModel, key::Symbol)
155+
156+
Remove all metadata of type `key` from the component.
157+
"""
158+
function strip_metadata!(c::ComponentModel, key::Symbol)
159+
md_dict = symmetadata(c)
160+
for (sym, sym_md) in md_dict
161+
delete!(sym_md, key)
162+
end
163+
return c
164+
end
165+
strip_metadata!(nw::Network, sym::SymbolicIndex, key::Symbol) = strip_metadata!(getcomp(nw, sym), key)
166+
104167
# generate default methods for some per-symbol metadata fields
105168
for md in [:default, :guess, :init, :bounds]
106169
fname_has = Symbol(:has_, md)
107170
fname_get = Symbol(:get_, md)
108171
fname_set = Symbol(:set_, md, :!)
109172
fname_del = Symbol(:delete_, md, :!)
173+
fname_strip = if md == :guess
174+
:strip_guesses!
175+
elseif md == :bounds
176+
:strip_bounds!
177+
else
178+
Symbol(:strip_, string(md)*"s", :!)
179+
end
110180
@eval begin
111181
"""
112182
has_$($(QuoteNode(md)))(c::ComponentModel, sym::Symbol)
@@ -115,6 +185,8 @@ for md in [:default, :guess, :init, :bounds]
115185
Checks if a `$($(QuoteNode(md)))` value is present for symbol `sym` in a component model,
116186
or for a symbol referenced by `sni` in a network.
117187
188+
`sym` can be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
189+
118190
See also [`get_$($(QuoteNode(md)))`](@ref), [`set_$($(QuoteNode(md)))!`](@ref).
119191
"""
120192
$fname_has(c::Comp_or_NW, sym) = has_metadata(c, sym, $(QuoteNode(md)))
@@ -126,6 +198,8 @@ for md in [:default, :guess, :init, :bounds]
126198
Returns the `$($(QuoteNode(md)))` value for symbol `sym` in a component model,
127199
or for a symbol referenced by `sni` in a network.
128200
201+
`sym` can be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
202+
129203
See also [`has_$($(QuoteNode(md)))`](@ref), [`set_$($(QuoteNode(md)))!`](@ref).
130204
"""
131205
$fname_get(c::Comp_or_NW, sym) = get_metadata(c, sym, $(QuoteNode(md)))
@@ -137,9 +211,19 @@ for md in [:default, :guess, :init, :bounds]
137211
Sets the `$($(QuoteNode(md)))` value for symbol `sym` to `value` in a component model,
138212
or for a symbol referenced by `sni` in a network.
139213
214+
If `value` is `nothing` or `missing`, the metadata is removed.
215+
216+
`sym` can be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
217+
140218
See also [`has_$($(QuoteNode(md)))`](@ref), [`get_$($(QuoteNode(md)))`](@ref).
141219
"""
142-
$fname_set(c::Comp_or_NW, sym, val) = set_metadata!(c, sym, $(QuoteNode(md)), val)
220+
function $fname_set(c::Comp_or_NW, sym, val)
221+
if !isnothing(val) && !ismissing(val)
222+
set_metadata!(c, sym, $(QuoteNode(md)), val)
223+
else
224+
delete_metadata!(c, sym, $(QuoteNode(md)))
225+
end
226+
end
143227

144228
"""
145229
delete_$($(QuoteNode(md)))!(c::ComponentModel, sym::Symbol)
@@ -148,9 +232,22 @@ for md in [:default, :guess, :init, :bounds]
148232
Removes the `$($(QuoteNode(md)))` value for symbol `sym` in a component model,
149233
or for a symbol referenced by `sni` in a network.
150234
235+
`sym` can be a String or Regex, to address the only symbol containing the pattern, see [`set_metadata!`](@ref) for details.
236+
151237
See also [`has_$($(QuoteNode(md)))`](@ref), [`set_$($(QuoteNode(md)))!`](@ref).
152238
"""
153239
$fname_del(c::Comp_or_NW, sym) = delete_metadata!(c, sym, $(QuoteNode(md)))
240+
241+
"""
242+
strip_$($(QuoteNode(md)))!(c::ComponentModel)
243+
strip_$($(QuoteNode(md)))!(nw::Network, idx::Union{VIndex,EIndex})
244+
245+
Removes all `$($(QuoteNode(md)))` values from a component model,
246+
or from a component referenced by `idx` in a network.
247+
248+
See also [`delete_$($(QuoteNode(md)))!`](@ref), [`set_$($(QuoteNode(md)))!`](@ref).
249+
"""
250+
$fname_strip(c::Comp_or_NW) = strip_metadata!(c, $(QuoteNode(md)))
154251
end
155252
end
156253

@@ -1042,3 +1139,39 @@ end
10421139

10431140
function describe_vertices end
10441141
function describe_edges end
1142+
1143+
"""
1144+
free_p(cf::ComponentModel)
1145+
free_p(nw::Network)
1146+
1147+
Returns the "free" parameters (parameters without default values) for the given system.
1148+
1149+
# Returns
1150+
- Vector of parameter symbols that do not have default values set
1151+
1152+
See also: [`free_u`](@ref), [`has_default`](@ref), [`set_default!`](@ref)
1153+
"""
1154+
function free_p(cf::NetworkDynamics.ComponentModel)
1155+
return filter(p -> !NetworkDynamics.has_default(cf, p), NetworkDynamics.psym(cf))
1156+
end
1157+
function free_p(nw::Network)
1158+
setdiff(NetworkDynamics.SII.parameter_symbols(nw), keys(NetworkDynamics.SII.default_values(nw)))
1159+
end
1160+
1161+
"""
1162+
free_u(nw::Network)
1163+
free_u(cf::ComponentModel)
1164+
1165+
Returns the "free" variables/states (variables without default values) for the given system.
1166+
1167+
# Returns
1168+
- Vector of variable/state symbols that do not have default values set
1169+
1170+
See also: [`free_p`](@ref), [`has_default`](@ref), [`set_default!`](@ref)
1171+
"""
1172+
function free_u(nw::Network)
1173+
setdiff(NetworkDynamics.SII.variable_symbols(nw), keys(NetworkDynamics.SII.default_values(nw)))
1174+
end
1175+
function free_u(cf::NetworkDynamics.ComponentModel)
1176+
return filter(u -> !NetworkDynamics.has_default(cf, u), NetworkDynamics.sym(cf))
1177+
end

src/show.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Base.show(io::IO, s::KAAggregator) = print(io, "KAAggregator($(repr(s.f)))")
4848
Base.show(io::IO, s::SequentialAggregator) = print(io, "SequentialAggregator($(repr(s.f)))")
4949
Base.show(io::IO, s::PolyesterAggregator) = print(io, "PolyesterAggregator($(repr(s.f)))")
5050

51-
function Base.show(io::IO, ::MIME"text/plain", c::ComponentModel)
51+
function Base.show(io::IO, mime::MIME"text/plain", c::ComponentModel)
5252
type = string(typeof(c))
5353
print(io, type, styled" {NetworkDynamics_name::$(c.name)}")
5454
print(io, styled" {NetworkDynamics_fftype:$(fftype(c))}")
@@ -64,6 +64,14 @@ function Base.show(io::IO, ::MIME"text/plain", c::ComponentModel)
6464
styling = Dict{Int,Symbol}()
6565

6666
print_states_params(io, c, styling)
67+
68+
# special known metadata of PowerDynamics
69+
if has_metadata(c, :pfmodel)
70+
pfm = get_metadata(c, :pfmodel)
71+
printstyled(io, "\nPowerflow model ", color=:blue, bold=true)
72+
print(io, ":", pfm.name)
73+
pdim(pfm) > 0 && print(io, styled" with $(stylesymbolarray(pfm.psym, _pdef(pfm), _pguess(pfm)))")
74+
end
6775
end
6876

6977
function print_states_params(io, @nospecialize(c::ComponentModel), styling)

0 commit comments

Comments
 (0)