Time-delayed network dynamics

Topics covered in this tutorial include:

  • modeling time-delays with Delay Differential Equations (DDEs)
  • handling parameters for DDEs
  • time-delayed vertex variables
  • time-delayed coupling

Delayed network diffusion

We revisit the introductory example on Network diffusion, this time with time-delayed vertex dynamics. In this section our goal is not to model real physical phenomena but rather to learn how time-delays can be modeled in NetworkDynamics.jl.

Let $g$ be a graph with $N$ nodes and adjacency matrix $A$. Let $v = (v_1, \dots, v_n)$ be a vector of (abstract) temperatures or concentrations at each node $i = 1, \dots, N$. The dynamics of state $v_i$ at time $t$ are determined by its past value $v_i(t-\tau)$, where $\tau$ is a constant time lag and its difference with its neighbors with coupling strength $\sigma$.

\[\begin{aligned} \dot v_i(t) = - v_i(t-\tau) - \sigma * \sum_{i=1}^N A_{ij} (v_i(t) - v_j(t)) \end{aligned}\]

Modeling diffusion with NetworkDynamics.jl

The sum on the right hand side plays the role of a (discrete) gradient. If the temperature at node $i$ is higher than at its neighboring node $j$, it will decrease along that edge.

The coupling function is the same as in the previous example

function diffusionedge!(e, v_s, v_d, p, t)
   e .= .1 * (v_s .- v_d)
   nothing
end

However, the internal vertex dynamics are now determined by the time-delayed equation $\dot v_i(t) = - v_i(t-\tau)$ and are described in the vertex function with help of the history array $h_v$ containing the past values of the vertex variables.

function delaydiffusionvertex!(dv, v, edges, h_v, p, t)
   dv .= -h_v
   sum_coupling!(dv, edges)
   nothing
end

The Watts-Strogatz algorithm constructs a regular ring network with $N$ nodes connected to $k$ neighbors and a probability $p$ of rewiring links. Since $p=0$ no rewiring happens and g is a simple ring network.

using LightGraphs

### Defining a graph

N = 10 # number of nodes
k = 4  # average degree
g = watts_strogatz(N, k, 0.) # ring-network

While the EdgeFunction is constructed via StaticEdge just as before, the vertex functions is wrapped in DDEVertex to account for the time-lag in the internal dynamics. network_dynamics then returns a DDEFunction compatible with the solvers of DifferentialEquations.jl. Combining those into a DDEFunction via network_dynamics is then straightforward.

using NetworkDynamics

nd_diffusion_vertex = DDEVertex(f! = delaydiffusionvertex!, dim = 1)
nd_diffusion_edge = StaticEdge(f! = diffusionedge!, dim = 1)

nd = network_dynamics(nd_diffusion_vertex, nd_diffusion_edge, g)
[ Info: Reconstructing EdgeFuntions with :undefined coupling type.

Simulation

When simulating a time-delayed system the initial conditions have to be specified on the whole interval $[-\tau, 0 ]$. This is usually done by specifying initial conditions at t=0 and a history function h for extrapolating these values to time $t= - \tau$. For simplicity h is often chosen constant. When solving a DDE with DifferentialEquations.jl h is later overloaded to interpolate between time-steps, for details see the docs.

const x0 = randn(N) # random initial conditions
tspan = (0., 10.) # time interval

# history function keeps the initial conditions constant and is in-place to save allocations
h(out, p, t) = (out .= x0)

We still have to specify the constant time-lag $\tau$. At the moment this is only possible by using the ND-specific tuple syntax. For DDEs these tuples have to contain a third argument specifying the delay time.

# parameters p = (vertex parameters, edge parameters, delay time)
p = (nothing, nothing, 1.)

We solve the problem on the time interval $[0, 10]$ with the Tsit5() algorithm, recommended for solving non-stiff problems. The MethodOfSteps(..) translates an OrdinaryDiffEq.jl solver into an efficient method for delay differential equations. DDEProblem addtional accepts the keyword constant_lags that can be useful in some situation, see their docs for details.

using DelayDiffEq, Plots

dde_prob = DDEProblem(nd, x0, h, tspan, p)
sol = solve(dde_prob, MethodOfSteps(Tsit5()))
plot(sol, vars = syms_containing(nd, "v"), fmt = :png)

Bonus: Two independent diffusions

In this extension of the first example, there are two independent diffusions on the same network with variables $x$ and $\phi$ - hence the dimension is set to dim=2.

nd_diffusion_vertex_2 = DDEVertex(f! = delaydiffusionvertex!, dim = 2, sym = [:x, :ϕ])
nd_diffusion_edge_2 = StaticEdge(f! = diffusionedge!, dim = 2)
nd_2 = network_dynamics(nd_diffusion_vertex_2, nd_diffusion_edge_2, g)
[ Info: Reconstructing EdgeFuntions with :undefined coupling type.

The initial conditions are sampled from (squared) normal distributinos such that the first $N$ values correspond to variable x and the values with indices from $N+1$ to $2N$ belong to variable ϕ, where $x \sim \mathcal{N}(0,1)$; $ϕ \sim \mathcal{N}(0,1)^2$.

const x0_2 = Array{Float64,1}(vec([randn(N).-10 randn(N).^2]')) # x ~ \mathcal{N}(0,1); ϕ ~ \mathcal{N}(0,1)^2

h_2(out, p, t) = (out .= x0_2)

p = (nothing, nothing, 1.) # p = (vertexparameters, edgeparameters, delaytime)

Now we can define the DDEProblem and solve it.

dde_prob_2 = DDEProblem(nd_2, x0_2, h_2, tspan, p)
sol_2 = solve(dde_prob_2, MethodOfSteps(Tsit5()));
plot(sol_2, legend=false, fmt = :png)

Kuramoto model with delay

A common modification of the Kuramoto model is to include a time-lag in the coupling function. In neuroscience such a coupling is used to account for transmission delays along synapses connecting different neurons.

Unlike in the diffusion example, the edges depend on past values of the vertex variables . For this reason the edgefunction has the history arrays of the destination and source vertices as arguments.

function kuramoto_delay_edge!(e, v_s, v_d, h_v_s, h_v_d, p, t)
    e[1] = p * sin(v_s[1] - h_v_d[1])
    nothing
end

The dynamics of vertices in the Kuramoto model are determined by a constant rotation frequency p. Contributions of the edges are summed up according to their destinations.

function kuramoto_vertex!(dv, v, edges, p, t)
    dv[1] = p
    for e in edges
        dv .+= e
    end
    nothing
end

In this case the vertex function does not depend on any past values and is simply constructed as an ODEVertex. Since the edges depend on the history of the vertex variables their calling signature has changed and they are handed to the delay-aware constructor StaticDelayEdge. They are called Static since don't have internal dynamical variables.

kdedge! = StaticDelayEdge(f! = kuramoto_delay_edge!, dim = 1)
kdvertex! = ODEVertex(f! = kuramoto_vertex!, dim = 1)

nd! = network_dynamics(kdvertex!, kdedge!, g)
[ Info: Reconstructing EdgeFuntions with :undefined coupling type.

The remaining steps for simulating the system are the same as above.

const x0_3 = randn(N) # random initial conditions
h_3(out, p, t) = (out .= x0_3)
# p = (vertexparameters, edgeparameters, delaytime)
ω = randn(N)
ω .-= sum(ω)/N # center at 0
p = (ω, 2., 1.)
tspan = (0.,10.)

dde_prob = DDEProblem(nd!, x0_3, h_3, tspan, p)

sol = solve(dde_prob, MethodOfSteps(Tsit5()));

### Plotting

plot(sol, vars = syms_containing(nd, "v"), legend=false, fmt = :png)

Delay differential equations with algebraic constraints

The vertex dynamics may in principle contain algebraic equations in mass matrix form. For an experimental test case have a look at the last section of this example on github.