From 658e85bbcf22883cb49f8964ab80fe3bb5bd9981 Mon Sep 17 00:00:00 2001 From: Riccardo Foffi Date: Sun, 31 Dec 2023 16:17:31 +0100 Subject: [PATCH] add example: drift in exponential ramp with brumley model (#66) --- .../Chemotaxis/5_drift_exponential.md | 144 ++++++++++++++++++ examples/Chemotaxis/5_drift_exponential.jl | 141 +++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 docs/src/examples/Chemotaxis/5_drift_exponential.md create mode 100644 examples/Chemotaxis/5_drift_exponential.jl diff --git a/docs/src/examples/Chemotaxis/5_drift_exponential.md b/docs/src/examples/Chemotaxis/5_drift_exponential.md new file mode 100644 index 0000000..4c7d795 --- /dev/null +++ b/docs/src/examples/Chemotaxis/5_drift_exponential.md @@ -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. + diff --git a/examples/Chemotaxis/5_drift_exponential.jl b/examples/Chemotaxis/5_drift_exponential.jl new file mode 100644 index 0000000..477a0a5 --- /dev/null +++ b/examples/Chemotaxis/5_drift_exponential.jl @@ -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. +=#