Skip to content

Commit

Permalink
add example: drift in exponential ramp with brumley model (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
mastrof authored Dec 31, 2023
1 parent 6b75b6b commit 658e85b
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 0 deletions.
144 changes: 144 additions & 0 deletions docs/src/examples/Chemotaxis/5_drift_exponential.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
```@meta
EditURL = "../../../../examples/Chemotaxis/5_drift_exponential.jl"
```

# Chemotactic drift in exponential ramp with noisy sensing

We will peform simulations of chemotaxis in an exponential concentration
ramp using the `Brumley` model of chemotaxis and measure their drift velocity
along the gradient.
The concentration field has the form
```math
C(x) = C_0\exp(x/λ)
```
and its gradient is
```math
\nabla C(x) = \dfrac{C_0}{\lambda}\exp(x/\lambda)\hat{\mathbf{x}}.
```

In the `Brumley` model, the microbe estimates the local gradient in the form of
the convective derivative of the concentration field (``U\nabla C``).
The measurement is affected by noise, represented by an intrinsic
noise term ``\sigma = \sqrt{3C/(\pi a D_c T^3)}`` (Mora & Wingreen (Phys Rev Lett 2010),
where ``a`` is the microbe radius, ``T`` the chemotactic sensory timescale
and ``D_c`` the thermal diffusivity of the chemoattractant compound)
and a pre-factor ``\Pi`` which represents the "chemotactic precision".
Higher values of ``\Pi`` imply higher levels of noise in chemotaxis pathway.

Therefore, with each measurement the bacterium samples from a Normal distribution
with mean ``U\nabla C`` and standard deviation ``\Pi\sigma``.
The measurement, ``\mathcal{M}``, determines the evolution of an internal state
``S`` which obeys an excitation-relaxation dynamics:
```math
\dot{S} = \kappa M -\dfrac{S}{\tau_M}.
```
This internal state, in turn, determines the tumbling rate.

We will study how ``Pi`` affects the drift velocity of bacteria.

````@example 5_drift_exponential
using MicrobeAgents
function concentration_field(pos, model)
x = first(pos)
C0 = model.C0
λ = model.λ
concentration_field(x, C0, λ)
end
concentration_field(x, C0, λ) = C0*exp(x/λ)
function concentration_gradient(pos, model)
x = first(pos)
C0 = model.C0
λ = model.λ
concentration_gradient(x, C0, λ)
end
concentration_gradient(x, C0, λ) = SVector{3}(i==1 ? C0/λ*exp(x/λ) : 0.0 for i in 1:3)
# wide rectangular channel confined in the z direction
Lx, Ly, Lz = 6000, 3000, 100
space = ContinuousSpace((Lx,Ly,Lz); periodic=false)
dt = 0.1
C0 = 10.0
λ = Lx/2
properties = Dict(
:concentration_field => concentration_field,
:concentration_gradient => concentration_gradient,
:C0 => C0,
:λ => λ,
)
model = StandardABM(Brumley{3}, space, dt; properties, container=Vector)
# add n bacteria for each value of Π
# all of them initialized at x = 0
Πs = [1.0, 2.0, 5.0, 10.0, 25.0]
n = 200
for Π in Πs
for i in 1:n
pos = SVector{3}(0.0, rand()*Ly, rand()*Lz)
add_agent!(pos, model; chemotactic_precision=Π)
end
end
# also store Π for grouping later
adata = [position, velocity, :chemotactic_precision]
nsteps = 2000
adf, = run!(model, nsteps; adata)
# evaluate drift velocity along x direction
target_direction = SVector(1.0, 0.0, 0.0)
Analysis.driftvelocity_direction!(adf, target_direction)
# we now want to first average the drift velocities in each group of Π values
# and then average each group over time to obtain a mean drift
# the calculation is much easier to perform with the DataFrames package
using DataFrames, StatsBase
gdf = groupby(adf, :chemotactic_precision)
drift_velocities = zeros(1+nsteps, length(Πs))
for (i,g) in enumerate(gdf)
for h in groupby(g, :id)
drift_velocities[:,i] .+= h.drift_direction
end
drift_velocities[:,i] ./= n
end
avg_drift = vec(mean(drift_velocities; dims=1))
# we also apply a smoothing function to remove some noise
function moving_average(y::AbstractVector, m::Integer)
@assert isodd(m)
out = similar(y)
R = CartesianIndices(y)
Ifirst, Ilast = first(R), last(R)
I1 = m÷2 * oneunit(Ifirst)
for I in R
n, s = 0, zero(eltype(out))
for J in max(Ifirst, I-I1):min(Ilast, I+I1)
s += y[J]
n += 1
end
out[I] = s/n
end
return out
end
smoothing_window = 51
smooth_drift_velocities = mapslices(
y -> moving_average(y, smoothing_window), drift_velocities;
dims = 1
)
using Plots
t = axes(smooth_drift_velocities, 1) .* dt
plot(layout=(1,2), size=(760,440),
plot(t, smooth_drift_velocities, lab=Πs',
xlab="t (s)", ylab="drift (μm/s)"
),
plot(Πs, avg_drift, lw=4, m=:c, ms=8, lab=false,
xlab="Π", ylab="avg drift (μm/s)"
)
)
````

Increasing Π significantly reduces the ability of bacteria to drift
along the gradient.

141 changes: 141 additions & 0 deletions examples/Chemotaxis/5_drift_exponential.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# # Chemotactic drift in exponential ramp with noisy sensing

#=
We will peform simulations of chemotaxis in an exponential concentration
ramp using the `Brumley` model of chemotaxis and measure their drift velocity
along the gradient.
The concentration field has the form
```math
C(x) = C_0\exp(x/λ)
```
and its gradient is
```math
\nabla C(x) = \dfrac{C_0}{\lambda}\exp(x/\lambda)\hat{\mathbf{x}}.
```
In the `Brumley` model, the microbe estimates the local gradient in the form of
the convective derivative of the concentration field (``U\nabla C``).
The measurement is affected by noise, represented by an intrinsic
noise term ``\sigma = \sqrt{3C/(\pi a D_c T^3)}`` (Mora & Wingreen (Phys Rev Lett 2010),
where ``a`` is the microbe radius, ``T`` the chemotactic sensory timescale
and ``D_c`` the thermal diffusivity of the chemoattractant compound)
and a pre-factor ``\Pi`` which represents the "chemotactic precision".
Higher values of ``\Pi`` imply higher levels of noise in chemotaxis pathway.
Therefore, with each measurement the bacterium samples from a Normal distribution
with mean ``U\nabla C`` and standard deviation ``\Pi\sigma``.
The measurement, ``\mathcal{M}``, determines the evolution of an internal state
``S`` which obeys an excitation-relaxation dynamics:
```math
\dot{S} = \kappa M -\dfrac{S}{\tau_M}.
```
This internal state, in turn, determines the tumbling rate.
We will study how ``\Pi`` affects the drift velocity of bacteria.
=#

using MicrobeAgents

function concentration_field(pos, model)
x = first(pos)
C0 = model.C0
λ = model.λ
concentration_field(x, C0, λ)
end
concentration_field(x, C0, λ) = C0*exp(x/λ)

function concentration_gradient(pos, model)
x = first(pos)
C0 = model.C0
λ = model.λ
concentration_gradient(x, C0, λ)
end
concentration_gradient(x, C0, λ) = SVector{3}(i==1 ? C0/λ*exp(x/λ) : 0.0 for i in 1:3)

## wide rectangular channel confined in the z direction
Lx, Ly, Lz = 6000, 3000, 100
space = ContinuousSpace((Lx,Ly,Lz); periodic=false)
dt = 0.1

C0 = 10.0
λ = Lx/2
properties = Dict(
:concentration_field => concentration_field,
:concentration_gradient => concentration_gradient,
:C0 => C0,
=> λ,
)
model = StandardABM(Brumley{3}, space, dt; properties, container=Vector)

## add n bacteria for each value of Π
## all of them initialized at x = 0
Πs = [1.0, 2.0, 5.0, 10.0, 25.0]
n = 200
for Π in Πs
for i in 1:n
pos = SVector{3}(0.0, rand()*Ly, rand()*Lz)
add_agent!(pos, model; chemotactic_precision=Π)
end
end

## also store Π for grouping later
adata = [position, velocity, :chemotactic_precision]
nsteps = 2000
adf, = run!(model, nsteps; adata)

## evaluate drift velocity along x direction
target_direction = SVector(1.0, 0.0, 0.0)
Analysis.driftvelocity_direction!(adf, target_direction)

## we now want to first average the drift velocities in each group of Π values
## and then average each group over time to obtain a mean drift
## the calculation is much easier to perform with the DataFrames package
using DataFrames, StatsBase
gdf = groupby(adf, :chemotactic_precision)
drift_velocities = zeros(1+nsteps, length(Πs))
for (i,g) in enumerate(gdf)
for h in groupby(g, :id)
drift_velocities[:,i] .+= h.drift_direction
end
drift_velocities[:,i] ./= n
end
avg_drift = vec(mean(drift_velocities; dims=1))

## we apply a smoothing function to remove some noise for better visualization
function moving_average(y::AbstractVector, m::Integer)
@assert isodd(m)
out = similar(y)
R = CartesianIndices(y)
Ifirst, Ilast = first(R), last(R)
I1 = m÷2 * oneunit(Ifirst)
for I in R
n, s = 0, zero(eltype(out))
for J in max(Ifirst, I-I1):min(Ilast, I+I1)
s += y[J]
n += 1
end
out[I] = s/n
end
return out
end
smoothing_window = 51
smooth_drift_velocities = mapslices(
y -> moving_average(y, smoothing_window), drift_velocities;
dims = 1
)

using Plots
t = axes(smooth_drift_velocities, 1) .* dt
plot(layout=(1,2), size=(760,440),
plot(t, smooth_drift_velocities, lab=Πs',
xlab="t (s)", ylab="drift (μm/s)"
),
plot(Πs, avg_drift, lw=4, m=:c, ms=8, lab=false,
xlab="Π", ylab="avg drift (μm/s)"
)
)

#=
Increasing Π significantly reduces the ability of bacteria to drift
along the gradient.
=#

0 comments on commit 658e85b

Please sign in to comment.