Skip to content

Commit 8282e6a

Browse files
authored
Merge pull request #238 from JuliaDynamics/hw/df_extension
add dataframe extension
2 parents c7342b4 + 61df2d6 commit 8282e6a

19 files changed

+961
-112
lines changed

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "NetworkDynamics"
22
uuid = "22e9dc34-2a0d-11e9-0de0-8588d035468b"
33
authors = ["Frank Hellmann <[email protected]>, Michael Lindner <[email protected]>, Hans Würfel <[email protected]"]
4-
version = "0.9.16"
4+
version = "0.9.17"
55

66
[deps]
77
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"
@@ -36,13 +36,15 @@ TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
3636
[weakdeps]
3737
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
3838
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
39+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
3940
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
4041
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
4142
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
4243
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
4344

4445
[extensions]
4546
NetworkDynamicsCUDAExt = ["CUDA", "Adapt"]
47+
NetworkDynamicsDataFramesExt = ["DataFrames"]
4648
NetworkDynamicsMTKExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"]
4749
NetworkDynamicsSymbolicsExt = ["Symbolics", "MacroTools"]
4850

@@ -51,6 +53,7 @@ Adapt = "4.0.4"
5153
ArgCheck = "2.3.0"
5254
Atomix = "1"
5355
CUDA = "5.5.2"
56+
DataFrames = "1"
5457
DiffEqCallbacks = "4.2.2"
5558
DocStringExtensions = "0.9.3"
5659
FastClosures = "0.3.2"

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "ND.jl docs"
33
[deps]
44
Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
55
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
6+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
67
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
78
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
89
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
@@ -44,6 +45,7 @@ path = "../NetworkDynamicsInspector"
4445
[compat]
4546
Bonito = "≥0.0.1"
4647
CairoMakie = "0.13.1"
48+
DataFrames = "≥0.0.1"
4749
DataInterpolations = "7, 8"
4850
DiffEqCallbacks = "4.2.2"
4951
Distributions = "0.25.117"

docs/make.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using NetworkDynamicsInspector: NetworkDynamicsInspector as NDI
77
using SciMLBase
88
using Literate
99
using ModelingToolkit
10+
using DataFrames: DataFrames
1011
using DocumenterInterLinks
1112
using Electron
1213

@@ -34,10 +35,11 @@ for example in filter(contains(r".jl$"), readdir(example_dir, join=true))
3435
end
3536

3637
mtkext = Base.get_extension(NetworkDynamics, :NetworkDynamicsMTKExt)
38+
dfext = Base.get_extension(NetworkDynamics, :NetworkDynamicsDataFramesExt)
3739
kwargs = (;
3840
root=joinpath(pkgdir(NetworkDynamics), "docs"),
3941
sitename="NetworkDynamics",
40-
modules=[NetworkDynamics, mtkext, NetworkDynamicsInspector],
42+
modules=[NetworkDynamics, mtkext, dfext, NetworkDynamicsInspector],
4143
linkcheck=true, # checks if external links resolve
4244
pagesonly=true,
4345
plugins=[links],

docs/src/API.md

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ NWParameter
6363
NWParameter(::Any)
6464
NWParameter(::NWParameter)
6565
NWParameter(::SciMLBase.DEIntegrator)
66+
pflat
67+
parameter_symbols
6668
```
6769

6870
### Network State Object
@@ -73,7 +75,7 @@ NWState(::NWState)
7375
NWState(::NWParameter)
7476
NWState(::SciMLBase.DEIntegrator)
7577
uflat
76-
pflat
78+
variable_symbols
7779
```
7880

7981
### Symbolic Indices
@@ -93,18 +95,14 @@ vpidxs
9395
epidxs
9496
```
9597

96-
### Solution Inspection
97-
```@docs
98-
dump_state
99-
```
100-
10198
## Metadata API
10299
### Component Metadata API
103100
```@docs
104101
metadata
105102
has_metadata(::NetworkDynamics.ComponentModel, ::Symbol)
106103
get_metadata(::NetworkDynamics.ComponentModel, ::Symbol)
107104
set_metadata!(::NetworkDynamics.ComponentModel, ::Symbol, ::Any)
105+
delete_metadata!(::NetworkDynamics.ComponentModel, ::Symbol)
108106
has_graphelement
109107
get_graphelement
110108
set_graphelement!
@@ -121,18 +119,41 @@ symmetadata
121119
get_metadata(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
122120
has_metadata(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
123121
set_metadata!(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol, ::Any)
122+
delete_metadata!(::NetworkDynamics.ComponentModel, ::Symbol, ::Symbol)
124123
has_default
125124
get_default
126125
set_default!
126+
delete_default!
127127
has_guess
128128
get_guess
129129
set_guess!
130+
delete_guess!
130131
has_init
131132
get_init
132133
set_init!
134+
delete_init!
133135
has_bounds
134136
get_bounds
135137
set_bounds!
138+
delete_bounds!
139+
set_defaults!
140+
set_interface_defaults!
141+
```
142+
143+
### Metadata and Inspection Utils
144+
```@docs
145+
dump_state
146+
dump_initial_state
147+
get_initial_state
148+
describe_vertices
149+
describe_edges
150+
```
151+
152+
## Initialization
153+
```@docs
154+
find_fixpoint
155+
initialize_component!
156+
init_residual
136157
```
137158

138159
## Callbacks API
@@ -156,17 +177,6 @@ set_callback!
156177
add_callback!
157178
```
158179

159-
## Initialization
160-
```@docs
161-
find_fixpoint
162-
initialize_component!
163-
init_residual
164-
get_initial_state
165-
dump_initial_state
166-
set_defaults!
167-
set_interface_defaults!
168-
```
169-
170180
## Execution Types
171181
```@docs
172182
ExecutionStyle

docs/src/data_structure.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ A typical workflow is:
2020

2121
1. Set default values in the models using the metadata (see [Metadata](@ref)).
2222
2. Create a network (see [Network Construction](@ref)).
23-
3. Generate a state `s = NWState(nw)` which will be prefilled with the default values from the component metadata (see [SymbolicIndexing](@ref)).
23+
3. Generate a state `s = NWState(nw)` which will be prefilled with the default values from the component metadata (see [Symbolic Indexing](@ref)).
2424
4. Change the values of `s`, i.e., `s.v[1,:x] = 1.0`: This changes the **underlying flat array** but not the metadata of the models.
2525
5. Build a problem with the updated flat arrays using `uflat(s)` and `pflat(s)`.
2626

docs/src/metadata.md

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# Metadata
2-
3-
Component model such as [`VertexModel`](@ref) and [`EdgeModel`](@ref) can store metadata. We distinguish between two kinds of metadata: component metadata and symbol metadata.
2+
Component models such as [`VertexModel`](@ref) and [`EdgeModel`](@ref) can store metadata. We distinguish between two kinds of metadata: component metadata and symbol metadata.
43

54
## Component Metadata
65
Component metadata is a `Dict{Symbol,Any}` attached to each component to store various information. Use [`metadata`](@ref) to retrieve the full dict.
76

8-
To access the data, you can use the methods `has_metadata`, `get_metadata` and `set_metadata!` (see [Component Metadata API](@ref)).
7+
To access the data, you can use the methods `has_metadata`, `get_metadata`, `set_metadata!` and `delete_metadata!` (see [Component Metadata API](@ref)).
98

109
Special metadata:
1110

@@ -19,18 +18,28 @@ Special metadata:
1918

2019

2120
## Symbol Metadata
22-
Each component stores symbol metadata. The symbol metadata is a `Dict{Symbol, Dict{Symbol, Any}}` which stores a metadate dict per symbol. Symbols are everything that appears in [`sym`](@ref), [`psym`](@ref), [`obssym`](@ref) and [`insym`](@ref).
21+
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).
2322

24-
To access the data, you can use the methods `has_metadata`, `get_metadata` and `set_metadata!` (see [Per Symbol Metadata API](@ref)).
23+
To access the data, you can use the methods `has_metadata`, `get_metadata`, `set_metadata!` and `delete_metadata!` (see [Per Symbol Metadata API](@ref)).
2524

2625
Special cases for symbol metadata are:
2726

2827
- `default`: Stores default values for states/parameters. In initialization, those are considered fixed.
29-
- `guess`: Stores a guess for a state/parameter which needs to solved during initialization ("free" variables).
30-
- `bounds`: Store bounds for variables/parameters
31-
- `init`: Stores the solution of the "free" variables, this is rarely set manual but instead when calling [`initialize_component!`](@ref).
28+
- `guess`: Stores a guess for a state/parameter which needs to be solved during initialization ("free" variables).
29+
- `bounds`: Stores bounds for variables/parameters
30+
- `init`: Stores the solution of the "free" variables, this is rarely set manually but instead when calling [`initialize_component!`](@ref).
31+
32+
For those, there are special functions `has_*`, `get_*`, `set_*!` and `delete_*!`. See [Per Symbol Metadata API](@ref).
33+
3234

33-
Fore those, there are special functions `has_*`, `get_*` and `set_*!`. See [Per Symbol Metadata API](@ref).
35+
These are closely aligned with the [metadata use in ModelingToolkit](https://docs.sciml.ai/ModelingToolkit/stable/basics/Variable_metadata/). They are automatically copied from the `ODESystem` if you use MTK models to create NetworkDynamics models.
3436

37+
## Metadata Utils
38+
Accessing metadata (especially defaults) of states and parameters is a very
39+
common task. We provide several helper methods to do so. Please check out their docstrings for further explanation:
3540

36-
Those are closely aligned to the [metadata use in ModelingToolkit](https://docs.sciml.ai/ModelingToolkit/stable/basics/Variable_metadata/). They are automatically copied from the `ODESystem` if you use MTK models to create NetworkDynamics models.
41+
- [`dump_state`](@ref)
42+
- [`dump_initial_state`](@ref)
43+
- [`get_initial_state`](@ref)
44+
- [`describe_vertices`](@ref) (needs `DataFrames.jl` loaded)
45+
- [`describe_edges`](@ref) (needs `DataFrames.jl` loaded)

docs/src/symbolic_indexing.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ For both `NWState` and `NWParameter` objects, there is a more convenient way to
9292
```
9393

9494
The `NWState` and `NWParameter` objects are mutable, thus changing them will also change the underlying wrapped flat arrays.
95-
You can always access the flat representations by calling [`uflat`](@ref) and [`pflat`](@ref).
95+
You can always access the flat representations by calling [`uflat`](@ref) and [`pflat`](@ref). The ordering of elements
96+
in these flat arrays corresponds exactly to the order returned by [`variable_symbols`](@ref) and
97+
[`parameter_symbols`](@ref) respectively.
9698

9799
!!! note
98100
The `NWState` and `NWParameter` wrappers can be constructed from various objects.
@@ -156,4 +158,8 @@ and
156158
```@example si
157159
SII.parameter_symbols(nw)
158160
```
161+
These functions return the symbolic indices in the exact order they appear in the flat arrays
162+
returned by [`uflat`](@ref) and [`pflat`](@ref), making them essential when you need to map
163+
between flat array indices and symbolic representations.
164+
159165
All above examples also work on other "symbolic containers", e.g. `SII.variable_symbols(::NWState)`.

ext/NetworkDynamicsDataFramesExt.jl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
module NetworkDynamicsDataFramesExt
2+
3+
using NetworkDynamics: NetworkDynamics, Network
4+
using DataFrames: DataFrames, DataFrame
5+
using OrderedCollections: OrderedDict
6+
7+
function _df_from_list(list, f)
8+
df = DataFrame()
9+
for (i, v) in list
10+
row = OrderedDict{Symbol,Any}()
11+
row[:idx] = i
12+
for sym in f(v)
13+
if NetworkDynamics.has_default_or_init(v, sym)
14+
row[sym] = NetworkDynamics.get_default_or_init(v, sym)
15+
else
16+
row[sym] = missing
17+
end
18+
end
19+
push!(df, NamedTuple(row); cols=:union)
20+
end
21+
df
22+
end
23+
function _df_from_pair(list, pair)
24+
key, f = pair
25+
df = DataFrame()
26+
for (i, v) in list
27+
row = OrderedDict{Symbol,Any}(:idx => i, key => f(v))
28+
push!(df, NamedTuple(row); cols=:union)
29+
end
30+
df
31+
end
32+
33+
"""
34+
describe_vertices(nw::Network, extras...; parameters=true, states=true, batch=nothing)
35+
36+
Creates a DataFrame containing information about the vertices in a Network.
37+
38+
# Arguments
39+
- `nw::Network`: The network to describe
40+
- `extras...`: Additional pairs of (key, function) to include as columns,
41+
where the function gets the [`VertexModel`](@ref NetworkDynamics.VertexModel) as its only parameter
42+
to extract a custom metadata field for example..
43+
- `parameters=true`: Whether to include parameter values
44+
- `states=true`: Whether to include state values
45+
- `batch=nothing`: Optionally filter by specific batches
46+
47+
# Returns
48+
A DataFrame with columns for vertex indices, names, batch numbers, and any parameter/state values.
49+
"""
50+
function NetworkDynamics.describe_vertices(nw::Network, extras...; parameters=true, states=true, batch=nothing)
51+
pairs = enumerate(nw.im.vertexm);
52+
batches = map(idx -> findfirst(batch -> idx batch.indices, nw.vertexbatches), first.(pairs))
53+
54+
if !isnothing(batch)
55+
idxs = findall(b -> b batch, batches)
56+
pairs = collect(pairs)[idxs]
57+
batch = collect(batches)[idxs]
58+
end
59+
isempty(pairs) && return DataFrame()
60+
61+
basedf = DataFrame(
62+
idx = first.(pairs),
63+
name = map(v->last(v).name, pairs),
64+
batch = map(idx -> findfirst(batch -> idx batch.indices, nw.vertexbatches), first.(pairs)),
65+
)
66+
67+
dfs = [basedf,]
68+
if parameters
69+
push!(dfs, _df_from_list(pairs, NetworkDynamics.psym))
70+
end
71+
if states
72+
push!(dfs, _df_from_list(pairs, NetworkDynamics.sym))
73+
end
74+
for p in extras
75+
push!(dfs, _df_from_pair(pairs, p))
76+
end
77+
78+
foldl((a,b) -> DataFrames.leftjoin(a,b; on=:idx), dfs)
79+
end
80+
81+
"""
82+
describe_edges(nw::Network, extras...; parameters=true, states=true, batch=nothing)
83+
84+
Creates a DataFrame containing information about the edges in a Network.
85+
86+
# Arguments
87+
- `nw::Network`: The network to describe
88+
- `extras...`: Additional pairs of (key, function) to include as columns,
89+
where the function gets the [`EdgeModel`](@ref NetworkDynamics.EdgeModel) as its only parameter
90+
to extract a custom metadata field for example..
91+
- `parameters=true`: Whether to include parameter values
92+
- `states=true`: Whether to include state values
93+
- `batch=nothing`: Optionally filter by specific batches
94+
95+
# Returns
96+
A DataFrame with columns for edge indices, source-destination pairs, names, batch numbers, and any parameter/state values.
97+
"""
98+
function NetworkDynamics.describe_edges(nw::Network, extras...; parameters=true, states=true, batch=nothing)
99+
pairs = enumerate(nw.im.edgem);
100+
batches = map(idx -> findfirst(batch -> idx batch.indices, nw.layer.edgebatches), first.(pairs))
101+
102+
if !isnothing(batch)
103+
idxs = findall(b -> b batch, batches)
104+
pairs = collect(pairs)[idxs]
105+
batch = collect(batches)[idxs]
106+
end
107+
isempty(pairs) && return DataFrame()
108+
109+
basedf = DataFrame(
110+
idx = first.(pairs),
111+
srcdst = map(idx -> nw.im.edgevec[idx].src => nw.im.edgevec[idx].dst, first.(pairs)),
112+
name = map(v->last(v).name, pairs),
113+
batch = map(idx -> findfirst(batch -> idx batch.indices, nw.layer.edgebatches), first.(pairs)),
114+
)
115+
116+
dfs = [basedf,]
117+
if parameters
118+
push!(dfs, _df_from_list(pairs, NetworkDynamics.psym))
119+
end
120+
if states
121+
push!(dfs, _df_from_list(pairs, NetworkDynamics.sym))
122+
end
123+
for p in extras
124+
push!(dfs, _df_from_pair(pairs, p))
125+
end
126+
127+
foldl((a,b) -> DataFrames.leftjoin(a,b; on=:idx), dfs)
128+
end
129+
130+
end

ext/NetworkDynamicsMTKExt.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,15 @@ function _get_metadata(sys, name)
220220
if def isa Symbolic
221221
def = fixpoint_sub(def, alldefaults)
222222
end
223-
def isa Symbolic && error("Could not resolve default $(ModelingToolkit.getdefault(sym)) for $name")
224-
nt = (; nt..., default=def)
223+
224+
if def isa Symbolic
225+
# do nothing, as the warning can get annoying
226+
# @warn "Could not resolve rhs for default term $name = $(ModelingToolkit.getdefault(sym)). Some rhs symbols might not have default values. Leave free."
227+
elseif def == ModelingToolkit.NoValue || def isa ModelingToolkit.NoValue
228+
# skip NoValue thing
229+
else
230+
nt = (; nt..., default=def)
231+
end
225232
end
226233

227234
# check for guess both in symbol metadata and in guesses of system

0 commit comments

Comments
 (0)