Introduce delayed element in differential equations with Julia's DifferentialEquations.jl

Hello,
I have a set of ordinary differential equations (ODE) that describe the growth of bacteria susceptible and resistant to phage infection:


that generate this plot:

To note that at t=1000 h the resistant strain is introduced.
How can I set the ODEs with Julia’s DifferentialEquations.jl package to accommodate for this delayed introduction? Can I use DDE instead?
I have written the following code:

function dynamo!(du, u, p, t)
    μ, ν, κ, φ, ω, η, β, ρ = p
    #=
    du[1] = susceptible
    du[2] = infected
    du[3] = phages
    du[4] = resistant
    =#
    du[1] = ((μ * u[1]) * (1 - ((u[1]+u[2]+u[4])/κ))) - (φ * u[1] * u[3]) - (ω * u[1])
    du[2] = (φ * u[1] * u[3])
     - (η * u[2]) - (ω * u[2])
    du[3] = (β * η * u[2]) - (ρ * φ * u[1] * u[3]) - (ω * u[3])
    du[4] = ((ν * u[4]) * (1 - ((u[1]+u[2]+u[4])/κ))) - (ω * u[4])
end

# set parameters
mu    = 0.16        # maximum growth rate susceptible strain
nu    = 0.12        # maximum growth rate resistant strain
kappa = 2.2e7       # maximum population density
phi   = 1.0e-9      # adsorption rate
omega = 0.05        # outflow
eta   = 0.25        # lyse rate
beta  = 50.0        # burst size
rho   = 0.6         # reinfection rate
tmax  = 4000.0      # time span 0-tmax
s0    = 50000.0     # initial susceptible population
r0    = 10000.0     # initial resistant population
i0    = 1.0e-9      # initial infected population
v0    = 80.0        # initial phage population
u0 = [s0, i0, v0, r0]
p = [mu, nu, kappa, phi, omega, eta, beta, rho]
tspan = (0.0, tmax)
prob = ODEProblem(dynamo!, u0, tspan, p)
soln = solve(prob)

but I got this graph:
dynSimResist
To note that here I simply set the starting value of the resistant strain at 1/5 of the susceptible’s.
Thank you

You can have a callback change a parameter at time t=1000.

and how would i set up that? would it work also for DDE? Thank you

Message me as a reply so I get an email reminder. I will post some stuff in 40 minutes if I remember.

Here’s an example of using callbacks:

https://docs.juliadiffeq.org/dev/features/callback_functions/#Example-1:-Interventions-at-Preset-Times-1

Everything in DifferentialEquations.jl works across differential equations, so these callbacks work not just for ODEs, but also SDEs, DAEs, DDEs, etc.

Thanks, I switched directly to the DDE and wrote:

function dynDelay!(du, u, h, p, t)
    μ, ν, κ, ϕ, ω, β, ρ, τ = p
    #=
    du[1] = susceptible
    du[2] = infected
    du[3] = phages
    du[4] = resistant
    =#
    history = h(p, t - τ)
    delay_lysis =  ϕ * history[1] * history[3] * exp(-1 * ω * τ)
    du[1] = (μ * u[1]) * (1 - (u[1]+u[2])/κ) - (ϕ * u[1] * u[3]) - (ω * u[1])
    du[2] = (ϕ * u[1] * u[3]) - delay_lysis - (ω * u[2])
    du[3] = (β * delay_lysis) - (ρ * ϕ * u[1] * u[3]) - (ω * u[3])
    du[4] = ((ν * u[4]) * (1 - ((u[1]+u[2]+u[4])/κ))) - (ω * u[4])
end
mu    = 0.16        # maximum growth rate susceptible strain
nu    = 0.12        # maximum growth rate resistant strain
kappa = 2.2e7       # maximum population density
phi   = 1.0e-9      # adsorption rate
omega = 0.05        # outflow
beta  = 50.0        # burst size
rho   = 1.0         # reinfection rate
tau   = 3.62        # latency time
tmax  = 4000.0      # time span 0-tmax
s0    = 50000.0     # initial susceptible population
i0    = 1.0e-9      # initial infected population
v0    = 80.0        # initial phage population
r0    = 10000.0     # initial resistant population
h(t, p) = ones(4)
tspan = (0.0, tmax)
u0 = [s0, i0, v0, r0]
parms = [mu, nu, kappa, phi, omega, beta, rho, tau]
condition(u,t,integrator) = t==1000
affect!(integrator) = integrator.u[4] += r0 
cb = DiscreteCallback(condition,affect!)
prob = DDEProblem(dynDelay!, u0, h, tspan, p=parms; constant_lags = [tau])
algt = MethodOfSteps(Tsit5())
soln = solve(prob, algt, callback=cb)

but the graph does not show the increase in total bacteria at t~1500:
2
What am I missing?

You forgot soln = solve(prob, algt, callback=cb,tstops=[1000]), so the callback didn’t trigger. See the discussion in the example.

I thought the trigger was hardcoded in the call condition(u,t,integrator) = t==1000; I updated. Also, I thought that the solver would have put on hold the data under conditioning (u[4]) so I put starting value at 10000.0; actually makes more sense that the starting valu eis modified at t=1000, so I changed the starting value of u[4] to ~0:

h(t, p) = ones(4)
tspan = (0.0, tmax)
u0 = [s0, i0, v0, 1.0e-9]
parms = [mu, nu, kappa, phi, omega, beta, rho, tau]
# add delay
condition(u,t,integrator) = t==1000
affect!(integrator) = integrator.u[4] += r0
cb = DiscreteCallback(condition,affect!)
# instantiate model
prob = DDEProblem(dynDelay!, u0, h, tspan, p=parms; constant_lags = [tau])
algt = MethodOfSteps(Tsit5())
soln = solve(prob, algt, callback=cb, tstops=[1000])

Now the plot is like this:
dynSimResist
there is a change in population densities although not quite in the original…

How come you aren’t starting with u0 = [s0, i0, v0, 0.0]? Also, why did you make h(t, p) = ones(4)? That contradicts what you’re saying about the initial condition of u0[4] ~ 0, because now you’re saying u(t)[4] ~ 1 for all t < 0. I assume that you want h(t,p) = [s0, i0, v0, 1e-9] or whatever, matching your initial condition?

1 Like

You are right, the ones(4) was a residual from a previous example. The use of 1.0e-9 instead of 0.0 was to avoid the risk of divisions by zero. Now the figure shows a better change at t=1000:
dynSimResist
Thanks