Skip to content

Commit

Permalink
Simplify motility types (#62)
Browse files Browse the repository at this point in the history
* remove `AbstractMotility` from type definitions and box motility field with `motilepattern` calls

* remove `AbstractMotility` from type definitions and box motility field with `motilepattern` calls

* rewrite api and simplify motility types

* introduce `cwbias`, various fixes

* `position` must extend function with same name from Base

* fixes

* update examples and tests
  • Loading branch information
mastrof authored Dec 21, 2023
1 parent 9450c5c commit 482951f
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 387 deletions.
15 changes: 8 additions & 7 deletions examples/Analysis/velocity_autocorrelations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ space = ContinuousSpace((L,L,L))

model = StandardABM(Microbe{3}, space, Δt; container=Vector)
n = 200
for i in 1:n
add_agent!(model; turn_rate, motility=RunTumble(speed=[U]))
add_agent!(model; turn_rate, motility=RunReverse(speed_forward=[U]))
add_agent!(model; turn_rate, motility=RunReverseFlick(speed_forward=[U]))
for Motility in (RunTumble, RunReverse, RunReverseFlick), i in 1:n
add_agent!(model; turn_rate, motility=Motility(speed=[U]))
end
ids_runtumble = 1:n
ids_runreverse = (1:n) .+ n
ids_runrevflick = (1:n) .+ 2n

nsteps = round(Int, 100τ_run / Δt)
adata = [:vel]
adf, = run!(model, nsteps; adata)

adf_runtumble = filter(:id => id -> model[id].motility isa RunTumble, adf; view=true)
adf_runrev = filter(:id => id -> model[id].motility isa RunReverse, adf; view=true)
adf_runrevflick = filter(:id => id -> model[id].motility isa RunReverseFlick, adf; view=true)
adf_runtumble = filter(:id => id -> id in ids_runtumble, adf; view=true)
adf_runrev = filter(:id => id -> id in ids_runreverse, adf; view=true)
adf_runrevflick = filter(:id => id -> id in ids_runrevflick, adf; view=true)
adfs = [adf_runtumble, adf_runrev, adf_runrevflick]

t = range(0, (nsteps-1)*Δt; step=Δt)
Expand Down
3 changes: 1 addition & 2 deletions examples/Chemotaxis/xie_response-function.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ properties = Dict(
:t₂ => t₂,
)

model_step!(model) = model.t += 1
model = StandardABM(Xie{3}, space, timestep; properties, model_step!)
model = StandardABM(Xie{3}, space, timestep; properties)
add_agent!(model; turn_rate_forward=0, motility=RunReverseFlick(motile_state=MotileState(Forward)))
add_agent!(model; turn_rate_backward=0, motility=RunReverseFlick(motile_state=MotileState(Backward)))

Expand Down
2 changes: 1 addition & 1 deletion examples/RandomWalks/randomwalk2D_motilepatterns.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ nsteps = 600
## abm setup
model = StandardABM(Microbe{2}, space, dt)
# add bacteria with different motile properties
add_agent!(model; motility=RunReverse(speed_forward=[55]), rotational_diffusivity=0.2)
add_agent!(model; motility=RunReverse(speed=[55]), rotational_diffusivity=0.2)
add_agent!(model; motility=RunTumble(speed=Normal(30,6)), turn_rate=0.5)
add_agent!(model; motility=RunReverseFlick(speed_backward=[6]), rotational_diffusivity=0.1)

Expand Down
2 changes: 1 addition & 1 deletion examples/RandomWalks/randomwalk3D.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ nsteps = 600
## abm setup
model = StandardABM(Microbe{3}, space, dt)
# add bacteria with different motile properties
add_agent!(model; motility=RunReverse(speed_forward=[55]), rotational_diffusivity=0.2)
add_agent!(model; motility=RunReverse(speed=[55]), rotational_diffusivity=0.2)
add_agent!(model; motility=RunTumble(speed=Normal(30,6)), turn_rate=0.5)
add_agent!(model; motility=RunReverseFlick(speed_backward=[6]), rotational_diffusivity=0.1)

Expand Down
3 changes: 2 additions & 1 deletion src/MicrobeAgents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ All microbe types *must* have at least the following fields:
- `radius::Real` equivalent spherical radius of the microbe
- `state::Real` generic variable for a scalar internal state
"""
abstract type AbstractMicrobe{D} <: AbstractAgent where D end
abstract type AbstractMicrobe{D} <: AbstractAgent where {D} end

include("api.jl")
include("utils.jl")
include("motility.jl")
include("rotations.jl")
Expand Down
30 changes: 30 additions & 0 deletions src/api.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export position, direction, speed, velocity, motilepattern,
turnrate, rotational_diffusivity, radius, state,
cwbias,
distance, distancevector

Base.position(m::AbstractMicrobe) = m.pos
direction(m::AbstractMicrobe) = m.vel
speed(m::AbstractMicrobe) = m.speed
velocity(m::AbstractMicrobe) = direction(m) .* speed(m)
motilepattern(m::AbstractMicrobe) = m.motility
turnrate(m::AbstractMicrobe) = m.turn_rate
rotational_diffusivity(m::AbstractMicrobe) = m.rotational_diffusivity
radius(m::AbstractMicrobe) = m.radius
state(m::AbstractMicrobe) = m.state

distance(a, b, model) = euclidean_distance(position(a), position(b), model)
distancevector(a, b, model) = distancevector(position(a), position(b), model)
function distancevector(a::SVector{D}, b::SVector{D}, model) where D
extent = spacesize(model)
SVector{D}(wrapcoord(a[i], b[i], extent[i]) for i in 1:D)
end

Base.position(a::SVector{D}) where D = a
Base.position(a::NTuple{D}) where D = SVector{D}(a)

## utils
function wrapcoord(x1, x2, d)
a = (x2 - x1) / d
(a - round(a)) * d
end
4 changes: 2 additions & 2 deletions src/bodies/spheres.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export HyperSphere, contact, is_encounter
# dispatch hides call to Point
HyperSphere(center::SVector{D}, radius::Real) where D = HyperSphere(Point(Float64.(center)), Float64(radius))

@inline position(a::HyperSphere{D}) where D = SVector{D}(a.center)
@inline radius(a::AbstractMicrobe) = a.radius
@inline Base.position(a::HyperSphere{D}) where D = SVector{D}(a.center)
#@inline radius(a::AbstractMicrobe) = a.radius
@inline radius(a::HyperSphere) = a.r
@inline contact(a,b,model) = distance(a,b,model) radius(a) + radius(b)

Expand Down
9 changes: 4 additions & 5 deletions src/chemotaxis/brown-berg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Default parameters:
"""
@agent struct BrownBerg{D}(ContinuousAgent{D,Float64}) <: AbstractMicrobe{D}
speed::Float64
motility::AbstractMotility = RunTumble()
motility = RunTumble()
turn_rate::Float64 = 1 / 0.67
rotational_diffusivity::Float64 = 0.035
radius::Float64 = 0.5
Expand Down Expand Up @@ -45,9 +45,8 @@ function affect!(microbe::BrownBerg, model)
chemotaxis!(microbe, model)
end

function turnrate(microbe::BrownBerg, model)
ν₀ = microbe.turn_rate # unbiased
function cwbias(microbe::BrownBerg, model)
g = microbe.gain
S = microbe.state
return ν₀ * exp(-g * S) # modulated turn rate
end # function
return exp(-g*S)
end
11 changes: 5 additions & 6 deletions src/chemotaxis/brumley.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The model is optimized for simulation of marine bacteria and accounts
for the presence of (gaussian) sensing noise in the chemotactic pathway.
Default parameters:
- `motility = RunReverseFlick(speed_forward = [46.5])`
- `motility = RunReverseFlick(speed = [46.5])`
- `turn_rate = 2.22` Hz → '1/τ₀'
- `state = 0.0` → 'S'
- `rotational_diffusivity = 0.035` rad²/s
Expand All @@ -19,7 +19,7 @@ Default parameters:
"""
@agent struct Brumley{D}(ContinuousAgent{D,Float64}) <: AbstractMicrobe{D}
speed::Float64
motility::AbstractMotility = RunReverseFlick(speed_forward = [46.5])
motility = RunReverseFlick(speed = [46.5])
turn_rate::Float64 = 1 / 0.45
rotational_diffusivity::Float64 = 0.035
radius::Float64 = 0.5
Expand Down Expand Up @@ -55,9 +55,8 @@ function affect!(microbe::Brumley, model)
chemotaxis!(microbe, model)
end

function turnrate(microbe::Brumley, model)
ν₀ = microbe.turn_rate # unbiased
function cwbias(microbe::Brumley, model)
Γ = microbe.gain
S = microbe.state
return (1 + exp(-Γ * S)) * ν₀ / 2 # modulated turn rate
end # function
return (1 + exp(-Γ*S))/2
end
9 changes: 4 additions & 5 deletions src/chemotaxis/celani.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Default parameters:
"""
@agent struct Celani{D}(ContinuousAgent{D,Float64}) <: AbstractMicrobe{D}
speed::Float64
motility::AbstractMotility = RunTumble(speed = [30.0])
motility = RunTumble(speed = [30.0])
turn_rate::Float64 = 1 / 0.67
rotational_diffusivity::Float64 = 0.26
radius::Float64 = 0.5
Expand Down Expand Up @@ -53,12 +53,11 @@ function affect!(microbe::Celani, model)
chemotaxis!(microbe, model)
end

function turnrate(microbe::Celani, model)
ν₀ = microbe.turn_rate # unbiased
function cwbias(microbe::Celani, model)
β = microbe.gain
S = microbe.state
return ν₀ * (1 - β * S) # modulated turn rate
end # function
return (1 - β*S)
end

# Celani requires a custom add_agent! method
# to initialize the markovian variables at steady state
Expand Down
49 changes: 20 additions & 29 deletions src/chemotaxis/xie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tuned through a `chemotactic_precision` factor inspired by
'Brumley et al. (2019) PNAS' (defaults to 0, i.e. no noise).
Default parameters:
- `motility = RunReverseFlick(speed_forward = [46.5])`
- `motility = RunReverseFlick(speed = [46.5])`
- `turn_rate_forward = 2.3` Hz
- `turn_rate_backward = 1.9` Hz
- `state = 0.0` s
Expand All @@ -31,7 +31,7 @@ Default parameters:
"""
@agent struct Xie{D}(ContinuousAgent{D,Float64}) <: AbstractMicrobe{D}
speed::Float64
motility::AbstractMotility = RunReverseFlick(speed_forward = [46.5])
motility = RunReverseFlick(speed = [46.5])
turn_rate_forward::Float64 = 2.3
turn_rate_backward::Float64 = 1.9
rotational_diffusivity::Float64 = 0.26
Expand All @@ -56,33 +56,6 @@ function Base.show(io::IO, ::MIME"text/plain", m::Xie{D}) where {D}
print(io, "other properties: " * join(s, ", "))
end

# Xie requires its own turnrate functions
# since it has different parameters for fw and bw states
function turnrate(microbe::Xie, model)
if microbe.motility isa AbstractMotilityTwoStep
return turnrate_twostep(microbe, model)
else
return turnrate_onestep(microbe, model)
end
end
function turnrate_twostep(microbe::Xie, model)
S = microbe.state
if microbe.motility.state == Forward
ν₀ = microbe.turn_rate_forward
β = microbe.gain_forward
else
ν₀ = microbe.turn_rate_backward
β = microbe.gain_backward
end
return ν₀ * (1 + β * S)
end
function turnrate_onestep(microbe::Xie, model)
S = microbe.state
ν₀ = microbe.turn_rate_forward
β = microbe.gain_forward
return ν₀ * (1 + β * S)
end

function chemotaxis!(microbe::Xie, model; ε=1e-16)
Δt = model.timestep
Dc = model.compound_diffusivity
Expand Down Expand Up @@ -110,3 +83,21 @@ end
function affect!(microbe::Xie, model)
chemotaxis!(microbe, model)
end

function cwbias(microbe::Xie, model)
S = state(microbe)
if state(motilepattern(microbe)) == Forward
β = microbe.gain_forward
else
β = microbe.gain_backward
end
return (1 + β*S)
end

function turnrate(microbe::Xie, model)
if state(motilepattern(microbe)) == Forward
return microbe.turn_rate_forward
else
return microbe.turn_rate_backward
end
end
7 changes: 4 additions & 3 deletions src/microbe_step.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Perform an integration step for `microbe`. In order:
function microbe_step!(microbe::AbstractMicrobe, model)
dt = model.timestep # integration timestep
# update microbe position
move_agent!(microbe, model, microbe.speed * dt)
move_agent!(microbe, model, speed(microbe)*dt)
# reorient through rotational diffusion
rotational_diffusion!(microbe, model)
# update microbe state
Expand Down Expand Up @@ -46,12 +46,13 @@ function microbe_pathfinder_step!(microbe::AbstractMicrobe, model)
nothing
end

# exposed to allow overload and customization
"""
turnrate(microbe, model)
Evaluate instantaneous turn rate of `microbe`.
"""
turnrate(microbe::AbstractMicrobe, model) = microbe.turn_rate
turnrate(microbe::AbstractMicrobe, model) = turnrate(microbe) * cwbias(microbe, model)
# no CW bias for generic non-chemotactic microbe
cwbias(microbe::AbstractMicrobe, model) = 1.0
"""
affect!(microbe, model)
Can be used to arbitrarily update `microbe` state.
Expand Down
24 changes: 12 additions & 12 deletions src/microbes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ export Microbe
Microbe{D} <: AbstractMicrobe{D}
Base microbe type for simple simulations.
Default parameters:
- `motility = RunTumble()` motile pattern
- `turn_rate::Float64 = 1.0` frequency of reorientations
- `rotational_diffusivity::Real` coefficient of brownian rotational diffusion
- `radius::Float64 = 0.0` equivalent spherical radius of the microbe
- `state::Float64 = 0.0` generic variable for a scalar internal state
`Microbe` has the additional required fields
`Microbe` has the required fields
- `id::Int` an identifier used internally
- `pos::SVector{D,Float64}` spatial position
- `vel::SVector{D,Float64}` unit velocity vector
- `speed::Float64` magnitude of the velocity vector
and the default parameters
- `motility::AbstractMotility = RunTumble()` motile pattern of the microbe
- `turn_rate::Float64 = 1.0` frequency of reorientations
- `rotational_diffusivity::Float64 = 0.0` coefficient of brownian rotational diffusion
- `radius::Float64 = 0.0` equivalent spherical radius of the microbe
- `state::Float64 = 0.0` generic variable for a scalar internal state
"""
@agent struct Microbe{D}(ContinuousAgent{D,Float64}) <: AbstractMicrobe{D}
speed::Float64
motility::AbstractMotility = RunTumble()
motility = RunTumble()
turn_rate::Float64 = 1.0
rotational_diffusivity::Float64 = 0.0
radius::Float64 = 0.0
Expand All @@ -28,9 +28,9 @@ end

r2dig(x) = round(x, digits=2)
function Base.show(io::IO, ::MIME"text/plain", m::AbstractMicrobe{D}) where D
println(io, "$(typeof(m)) with $(typeof(m.motility)) motility")
println(io, "position (μm): $(r2dig.(m.pos)); velocity (μm/s): $(r2dig.(m.vel.*m.speed))")
println(io, "average unbiased turn rate (Hz): $(r2dig(m.turn_rate))")
println(io, "$(typeof(m)) with $(M)")
println(io, "position (μm): $(r2dig.(position(m))); velocity (μm/s): $(r2dig.(velocity(m)))")
println(io, "average unbiased turn rate (Hz): $(r2dig(turn_rate(m)))")
s = setdiff(fieldnames(typeof(m)), [:id, :pos, :motility, :vel, :turn_rate])
print(io, "other properties: " * join(s, ", "))
end
Loading

0 comments on commit 482951f

Please sign in to comment.