Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heterogeneous lags for networks with delay #104

Merged
merged 32 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
691e542
refactor submodules
lindnemi Oct 18, 2021
9d85d93
remove unused code
lindnemi Oct 18, 2021
b8a0b02
set up a simple test
lindnemi Oct 18, 2021
c3c82dd
remove internal exports
lindnemi Oct 18, 2021
115f163
first attempts
lindnemi Oct 21, 2021
7b95aa2
adding type information everywhere
lindnemi Oct 21, 2021
06236f4
minor
lindnemi Oct 25, 2021
872cf3d
Static Edge promotion is not used anymore
lindnemi Oct 25, 2021
c0ac04c
remove history from NetworkDE
lindnemi Oct 25, 2021
a8abe62
tuple syntax not necessary anymore
lindnemi Oct 25, 2021
f03df36
fixing the tests
lindnemi Oct 25, 2021
0bdb6d4
simplify tests (minor)
lindnemi Oct 25, 2021
2ef5325
new example with delay
lindnemi Nov 18, 2021
33e60b9
wrap `h`, change function signature
lindnemi Nov 22, 2021
57eadcf
Merge branch 'main' into hetero_delay
lindnemi Nov 22, 2021
7889fe5
Merge branch 'main' into hetero_delay
lindnemi Jun 21, 2022
a2cb157
adjustments to new conventions
lindnemi Jun 21, 2022
25effce
bump version
lindnemi Jun 21, 2022
ad40cd4
test on 1.8
lindnemi Jun 21, 2022
f9681b1
update docs with DDE
lindnemi Jun 22, 2022
e53caa4
update dde test
lindnemi Jun 22, 2022
9f1caf8
Literate compatible example
lindnemi Jun 22, 2022
5c5a582
rename
lindnemi Jun 22, 2022
aff297e
remove compat
lindnemi Jun 22, 2022
eeadf9b
fix arguments
lindnemi Jun 22, 2022
1f80006
Update tests.yml
lindnemi Jun 22, 2022
c5993ef
Update src/NetworkDiffEq.jl
lindnemi Jun 22, 2022
ba0ff11
Update NetworkDiffEq.jl
lindnemi Jun 22, 2022
18efdc4
Improve docstrings
lindnemi Jun 22, 2022
f77f25e
fix test
lindnemi Jun 22, 2022
8691c06
Apply suggestions from code review
lindnemi Jun 22, 2022
066d8fa
hopefully lower docs build time
lindnemi Jun 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6'
- '1.8'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- '1.8'
- '1.6'
- '1.8'

we shouldn't skip testing LTS?

- '1'
os:
- ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "NetworkDynamics"
uuid = "22e9dc34-2a0d-11e9-0de0-8588d035468b"
authors = ["Frank Hellmann <[email protected]>, Michael Lindner <[email protected]>"]
version = "0.7.1"
version = "0.8"

[deps]
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
Expand Down
3 changes: 2 additions & 1 deletion examples/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = ["Frank Hellmann <[email protected]>", "Michael Lindner <mlindne
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
DelayDiffEq = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb"
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand All @@ -18,4 +19,4 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
NetworkDynamics = "0.5"
NetworkDynamics = "0.5"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the examples really compatible with all breaking versions since 0.5 and also in the future? Maybe we should remove the compat all together in this file as this might be misleading...

94 changes: 94 additions & 0 deletions examples/kura_delay.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using DelayDiffEq
using Random
using LightGraphs
using NetworkDynamics
using Distributions
using BenchmarkTools

N = 6
g = SimpleDiGraph(complete_graph(N))


function kuramoto!(dθ, θ, edges, p, t)
ω = p
dθ[1] = ω
for e in edges
dθ[1] += e[1]
end
return nothing
end

function delay_coupling(e, θ_s, θ_d, uh, θ_s_idxs, θ_d_idxs, p, global_p, t)
hist1 = uh(t - p[1]; idxs=1)
e[1] = p[2] * sin(hist1 - θ_d[1])
return nothing
end

vertex = ODEVertex(f! = kuramoto!, dim = 1)
edge = StaticDelayEdge(f! = delay_coupling, dim=1, coupling=:directed)
nd = network_dynamics(vertex,edge,g)

Random.seed!(1)

ω = rand(nv(g))
τ = rand(ne(g))
minimum(τ)
κ = rand(ne(g))
p = (ω, [τ'; κ'])

θ₀ = rand(Uniform(0, 2π), N)

const past = rand(Uniform(0, 2π), N)
h(p, t; idxs=nothing) = past[idxs]

x0 = ones(N)
dx0 = similar(x0)
@allocated nd.f(dx0, x0,h,p,0) # Works as expected
@allocated nd.f(dx0, x0,h,p,0) # Works as expected
@btime $nd.f($dx0, $x0,$h,$p,0) # Works as expected

prob = DDEProblem(nd, θ₀, h, (0.0, 1.0), p, constant_lags=τ)

@btime solve(prob, MethodOfSteps(BS3()), saveat=0.01)

#### Other system

using DelayDiffEq
using Distributions
using Random

function f!(dθ, θ, h::H, p, t) where H
ω, A = p
n = length(θ)
lags = reshape(lag, n,n)
@inbounds for j in 1:n
coupling = 0.0
@inbounds for i in 1:n
coupling += foo(dθ, θ, h::H, p, t, i, j)
end
dθ[j] = ω[j] + coupling
end
nothing
end

function foo(dθ, θ, h::H, p, t, i, j) where H
ω, A = p
n = length(θ)
lags = reshape(lag, n,n)
hist = h(p, t-lags[i,j]; idxs=i)
return A[i,j]*sin(hist - θ[j])
end

n = 10
Random.seed!(1)
ω = rand(n)
A = rand(n,n)
const lag = rand(n*n)
θ₀ = rand(Uniform(0, 2π), n)
p = (ω, A)
const past = rand(Uniform(0, 2π), n)
h(p, t; idxs=nothing) = typeof(idxs) <: Number ? past[idxs] : past

prob = DDEProblem(f!, θ₀, h, (0.0, 1.0), p, constant_lags=lag)

@btime solve(prob, MethodOfSteps(BS3()), saveat=0.01, reltol=0, abstol=1e-5)
50 changes: 12 additions & 38 deletions src/ComponentFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ the source and destination of the described edge.

- `dim` is the number of independent variables in the edge equations and
- `sym` is an array of symbols for these variables.
- `coupling` is a Symbol describing if the EdgeFunction is intended for a directed graph (`:directed`) or for an undirected graph (`{:undirected, :symmetric, :antisymmetric, :fiducial}`). `:directed` is intended for directed graphs. `:undirected` is the default option and is only compatible with SimpleGraph. in this case f! should specify the coupling from a source vertex to a destination vertex. `:symmetric` and `:antisymmetric` trigger performance optimizations, if `f!` has that symmetry property. `:fiducial` lets the user specify both the coupling from src to dst, as well as the coupling from dst to src and is intended for advanced users.
- `coupling` is a Symbol describing if the EdgeFunction is intended for a directed graph (`:directed`) or for an undirected graph (`{:undirected, :symmetric, :antisymmetric, :fiducial}`). `:directed` is intended for directed graphs. `:undirected` is the default option and is only compatible with SimpleGraph. in this case f should specify the coupling from a source vertex to a destination vertex. `:symmetric` and `:antisymmetric` trigger performance optimizations, if `f` has that symmetry property. `:fiducial` lets the user specify both the coupling from src to dst, as well as the coupling from dst to src and is intended for advanced users.

For more details see the documentation.
"""
Expand Down Expand Up @@ -285,7 +285,7 @@ Here `dv`, `v`, `p` and `t` are the usual ODE arguments, while

- `dim` is the number of independent variables in the edge equations and
- `sym` is an array of symbols for these variables.
- `coupling` is a Symbol describing if the EdgeFunction is intended for a directed graph (`:directed`) or for an undirected graph (`{:undirected, :fiducial}`). `:directed` is intended for directed graphs. `:undirected` is the default option and is only compatible with SimpleGraph. in this case f! should specify the coupling from a source vertex to a destination vertex. `:fiducial` lets the user specify both the coupling from src to dst, as well as the coupling from dst to src and is intended for advanced users.
- `coupling` is a Symbol describing if the EdgeFunction is intended for a directed graph (`:directed`) or for an undirected graph (`{:undirected, :fiducial}`). `:directed` is intended for directed graphs. `:undirected` is the default option and is only compatible with SimpleGraph. in this case f should specify the coupling from a source vertex to a destination vertex. `:fiducial` lets the user specify both the coupling from src to dst, as well as the coupling from dst to src and is intended for advanced users.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only tangentially related to this PR, but this docstring should really mention how fiducial work. I.e. you specify the dimensionality N, you get passed a 2N state vector where the first N elements will be presented to the dst and the second N elements will be presented to src

- `mass_matrix` is an optional argument that defaults to the identity
matrix `I`. If a mass matrix M is given the system `M * de = f` will be
solved.
Expand Down Expand Up @@ -329,52 +329,26 @@ Base.@kwdef struct StaticDelayEdge{T} <: EdgeFunction
return new{T}(user_f, dim, coupling, sym)

elseif coupling == :fiducial
dim % 2 != 0 && error("Fiducial edges are required to have even dim. ",
"The first dim args are used for src -> dst ",
"the second for dst -> src coupling.")
dim % 2 == 0 ? nothing : error("Fiducial edges are required to have even dim.
The first dim args are used for src -> dst,
the second for dst -> src coupling.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old version with && is more idomatic than using the ternary operator i'd say. Also, multi line strings are possible in Julia, but only from 1.7 onwards if I remeber correctly. Therefore, the " and , solution is better because it does not include all of the whitespace in the literal string

return new{T}(user_f, dim, coupling, sym)

elseif coupling ∈ (:antisymmetric, :symmetric)
error("$coupling coupling not implemented for edges with delay. If you need it please open an issue on GitHub.")
elseif coupling == :undirected
# This might cause unexpected behaviour if source and destination vertex don't
# have the same internal arguments.
# Make sure to explicitly define the edge is :fiducial in that case.
# This might cause unexpected behaviour if source and destination vertex don't
# have the same internal arguments.
# Make sure to explicitly define the edge is :fiducial in that case.
f = @inline (e, v_s, v_d, h_v_s, h_v_d, p, t) -> begin
@inbounds user_f(view(e, 1:dim), v_s, v_d, h_v_s, h_v_d, p, t)
@inbounds user_f(view(e, dim+1:2dim), v_d, v_s, h_v_d, h_v_s, p, t)
nothing
end
elseif coupling == :antisymmetric
f = @inline (e, v_s, v_d, h_v_s, h_v_d, p, t) -> begin
@inbounds user_f(view(e, 1:dim), v_s, v_d, h_v_s, h_v_d, p, t)
@inbounds for i in 1:dim
e[dim+i] = -1.0 * e[i]
end
nothing
end
elseif coupling == :symmetric
f = @inline (e, v_s, v_d, h_v_s, h_v_d, p, t) -> begin
@inbounds user_f(view(e, 1:dim), v_s, v_d, h_v_s, h_v_d, p, t)
@inbounds for i in 1:dim
e[dim+i] = e[i]
end
@inbounds user_f(view(e,1:dim), v_s, v_d, h_v_s, h_v_d, p, t)
@inbounds user_f(view(e,dim+1:2dim), v_d, v_s, h_v_d, h_v_s, p, t)
nothing
end
end
return new{typeof(f)}(f, 2dim, coupling, repeat(sym, 2))
end
end

function StaticDelayEdge(se::StaticEdge)
let _f = se.f, dim = se.dim, coupling = se.coupling, sym = se.sym
f = @inline (e, v_s, v_d, h_v_s, h_v_d, p, t) -> _f(e, v_s, v_d, p, t)
if coupling ∈ (:undefined, :fiducial, :directed)
return StaticDelayEdge(f, dim, coupling, sym)
else
return StaticDelayEdge(f, dim, :fiducial, sym)
end
end
end

# Promotion rules


Expand Down
75 changes: 45 additions & 30 deletions src/NetworkDiffEq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ end


@inline function component_loop!(unique_components, unique_c_indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h::H, parallel) where H
for j in 1:length(unique_components)
# Function barrier
_inner_loop!(unique_components[j], unique_c_indices[j],
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h::H, parallel)
lindnemi marked this conversation as resolved.
Show resolved Hide resolved
end
return nothing
end
Expand All @@ -43,7 +43,7 @@ end
# inner loops for ODE + Static case

function _inner_loop!(component::ODEVertex, indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h, parallel)
@nd_threads parallel for i in indices
component.f(view(dx, gs.v_idx[i]),
get_vertex(gd, i),
Expand All @@ -55,7 +55,7 @@ function _inner_loop!(component::ODEVertex, indices,
end

function _inner_loop!(component::StaticEdge, indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h, parallel)
@nd_threads parallel for i in indices
component.f(get_edge(gd, i),
get_src_vertex(gd, i),
Expand All @@ -69,34 +69,43 @@ end
# inner loops for DDE + Static Delay

function _inner_loop!(component::DDEVertex, indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h::H, parallel) where H

@nd_threads parallel for i in indices
# Wrappers for the history function correct for global p and global idx
h_v = @inline((t; idxs) -> h(p,t;idxs=gs.v_idx[i][idxs]))

component.f(view(dx, gs.v_idx[i]),
get_vertex(gd, i),
get_dst_edges(gd, i),
view(history, gs.v_idx[i]),
p_v_idx(p, i),
t)
get_vertex(gd, i),
get_dst_edges(gd, i),
h::H,
gs.v_idx[i],
p_v_idx(p, i),
t)
end
return nothing
end

function _inner_loop!(component::StaticDelayEdge, indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h::H, parallel) where H
@nd_threads parallel for i in indices
# Wrappers for the history function correct for global p and global idx
h_v_s = @inline((t; idxs) -> h(p,t;idxs=gs.s_e_idx[i][idxs]))
h_v_d = @inline((t; idxs) -> h(p,t;idxs=gs.d_e_idx[i][idxs]))

component.f(get_edge(gd, i),
get_src_vertex(gd, i),
get_dst_vertex(gd, i),
view(history, gs.s_e_idx[i]),
view(history, gs.d_e_idx[i]),
p_e_idx(p, i),
t)
get_src_vertex(gd, i),
get_dst_vertex(gd, i),
h_v_s,
h_v_d,
p_e_idx(p, i),
t)
end
return nothing
end

function _inner_loop!(component::ODEEdge, indices,
dx, p, t, gd, gs, history, parallel)
dx, p, t, gd, gs, h, parallel)
@nd_threads parallel for i in indices
component.f(view(dx, gs.e_idx[i] .+ gs.dim_v),
get_edge(gd, i),
Expand All @@ -111,15 +120,14 @@ end
# struct for both cases

#@Base.kwdef struct NetworkDE{G, GDB, elV, elE, TUV, TUE, Th<:AbstractArray{elV}}
Base.@kwdef struct NetworkDE{G,GDB,elV,elE,TUV,TUE,Th<:Union{AbstractArray,Nothing}}
@Base.kwdef struct NetworkDE{G, GDB, elV, elE, TUV, TUE}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most styleguides ommit those spaces afaik

unique_vertices!::TUV
unique_v_indices::Vector{Vector{Int}}
unique_edges!::TUE
unique_e_indices::Vector{Vector{Int}}
graph::G #redundant?
graph_structure::GraphStruct
graph_data::GraphData{GDB,elV,elE}
history::Th # for ODE + Static case: nothing, for DDE + Static Delay case: Th
graph_data::GraphData{GDB, elV, elE}
parallel::Bool # enables multithreading for the core loop
end

Expand All @@ -132,20 +140,27 @@ function (d::NetworkDE)(dx, x, p, t)

@assert size(dx) == size(x) "Sizes of dx and x do not match"

# Pass nothing, because here we have only Static Edges or Static Delay Edges (if we
# include the ODE ODE case than we have to pass dx)
# Pass nothing instead of the history function
component_loop!(d.unique_edges!, d.unique_e_indices,
dx, p, t, gd, gs, d.history, d.parallel)
dx, p, t, gd, gs, nothing, d.parallel)

component_loop!(d.unique_vertices!, d.unique_v_indices,
dx, p, t, gd, gs, d.history, d.parallel)
dx, p, t, gd, gs, nothing, d.parallel)
return nothing
end
# for DDE case
function (d::NetworkDE)(dx, x, h!, p, t)
# History computation happens beforehand and is cached in d.history
h!(d.history, p, t - p[end])
d(dx, x, p, t)
function (d::NetworkDE)(dx, x, h::H, p, t) where H
gs = d.graph_structure
checkbounds_p(p, gs.num_v, gs.num_e)
gd = prep_gd(dx, x, d.graph_data, gs)

@assert size(dx) == size(x) "Sizes of dx and x do not match"

component_loop!(d.unique_edges!, d.unique_e_indices,
dx, p, t, gd, gs, h::H, d.parallel)

component_loop!(d.unique_vertices!, d.unique_v_indices,
dx, p, t, gd, gs, h::H, d.parallel)
return nothing
end

Expand All @@ -157,7 +172,7 @@ function (d::NetworkDE)(x, p, t, ::Type{GetGD})
if size(x) == (gs.dim_v,)
checkbounds_p(p, gs.num_v, gs.num_e)
component_loop!(d.unique_edges!, d.unique_e_indices,
nothing, p, t, gd, gs, d.history, d.parallel)
nothing, p, t, gd, gs, nothing, d.parallel)
end
gd
end
Expand Down
13 changes: 3 additions & 10 deletions src/NetworkDynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,11 @@ function _network_dynamics(vertices!::Union{Vector{T},T},
hasDelay = any(v -> v isa DDEVertex, unique_vertices!) ||
any(e -> e isa StaticDelayEdge, unique_edges!)

if initial_history === nothing && hasDelay
initial_history = ones(sum(v_dims))
end

graph_stucture = GraphStruct(graph, v_dims, e_dims, symbols_v, symbols_e)

graph_data = GraphData(v_array, e_array, graph_stucture)

nd! = NetworkDE(unique_vertices!, unique_v_indices, unique_edges!, unique_e_indices, graph, graph_stucture,
graph_data, initial_history, parallel)
nd! = NetworkDE(unique_vertices!, unique_v_indices, unique_edges!, unique_e_indices, graph, graph_stucture, graph_data, parallel)
mass_matrix = construct_mass_matrix(mmv_array, graph_stucture)

return if hasDelay
Expand All @@ -196,8 +191,7 @@ end

## ODEEdge

function _network_dynamics(vertices!::Union{Vector{T},T}, edges!::Union{Vector{U},U}, graph; initial_history=nothing,
x_prototype=zeros(1), parallel=false) where {T<:ODEVertex,U<:ODEEdge}
function _network_dynamics(vertices!::Union{Vector{T},T}, edges!::Union{Vector{U},U}, graph; x_prototype=zeros(1), parallel=false) where {T<:ODEVertex, U<:ODEEdge}

warn_parallel(parallel)

Expand All @@ -223,8 +217,7 @@ function _network_dynamics(vertices!::Union{Vector{T},T}, edges!::Union{Vector{U

graph_data = GraphData(v_array, e_array, graph_stucture)

nd! = NetworkDE(unique_vertices!, unique_v_indices, unique_edges!, unique_e_indices, graph, graph_stucture,
graph_data, initial_history, parallel)
nd! = NetworkDE(unique_vertices!, unique_v_indices, unique_edges!, unique_e_indices, graph, graph_stucture, graph_data, parallel)

mass_matrix = construct_mass_matrix(mmv_array, mme_array, graph_stucture)

Expand Down
Loading