Behaviors
Everything a microbe does beyond its motility pattern is expressed by behaviors: values passed to add_agent! through the behaviors keyword, as a Tuple or a NamedTuple. Names are arbitrary labels, useful to access the behaviors later; the order of the behaviors is the order of execution.
model = StandardABM(Microbe{2}, space, dt; properties)
add_agent!(model;
motility = RunReverseFlick([30.0], 0.5, [30.0], 0.5),
radius = 0.5,
behaviors = (
chemotaxis = BrownBerg(gain = 600),
kinesis = Chemokinesis(threshold = 0.05, factor = 1.3),
),
)
model[1].behaviors.chemotaxis.state # internal state of the chemotaxis behaviorEach agent receives its own copy of the behaviors. Struct behaviors are deepcopy'd, including any data they reference (large arrays, obstacle lists, shared fields): to share such data, reference it from the model properties, or opt out of copying for a type with MicrobeAgents.copybehavior(b::MyType) = b. Plain functions and Behavior objects are shared across agents, so any mutable state captured by their closures is shared too.
Hooks
During each step the microbe calls these hooks on each behavior. A behavior only defines the hooks it needs; the others are neutral.
MicrobeAgents.initialize! — Function
initialize!(behavior, microbe, model)Hook called once when microbe is added to model. Default: no-op.
MicrobeAgents.affect! — Function
affect!(behavior, microbe, model)Hook called at every step right after translation, to update internal state or apply arbitrary per-step logic. Default: no-op. A plain Function f used as a behavior is called as f(microbe, model); a callable struct needs its own affect! method.
MicrobeAgents.bias — Function
bias(behavior, microbe, model)Hook returning a multiplicative factor on the rate of switching out of biased motile states (see biased). Default: 1.0.
bias(microbe, model)Total bias of microbe, i.e. the product of bias over its behaviors.
MicrobeAgents.speed_factor — Function
speed_factor(behavior, microbe)Hook returning a multiplicative factor on the microbe speed. Default: 1.0. Must only depend on state stored in behavior or microbe.
MicrobeAgents.transition_weights! — Function
transition_weights!(w, behavior, microbe, model)Hook to modify w, a scratch copy of the transition weights out of the current motile state, right before the next state is sampled. Default: no-op. Modify w by indexing (w[i] = x), which keeps sum(w) consistent; do not edit w.values directly.
The step is: translation and rotational diffusion (move_step!), then affect! for each behavior (affect_step!), then active reorientation and motile state switching (reorient_step!), where the switching rate is multiplied by the product of all bias values (in biased motile states) and the transition weights pass through every transition_weights!.
Defining behaviors
Per-step logic without parameters or memory: a plain function, called as f(microbe, model) right after translation.
bounce!(microbe, model) = position(microbe)[1] > 90 && microbe.vel[1] > 0 && (microbe.vel = -microbe.vel)
add_agent!(model; motility, behaviors = (bounce!,))Stateless modulations of any hook: Behavior.
MicrobeAgents.Behavior — Type
Behavior(; affect!, bias, speed_factor, transition_weights!)Behavior built from functions, for stateless behaviors without defining a type. Each keyword is optional; signatures are those of the hooks without the behavior argument, e.g. bias = (microbe, model) -> 2.0.
add_agent! copies behaviors for each agent: struct behaviors are deepcopy'd (including any data they reference), while plain functions and Behavior objects are shared, so mutable state captured by their closures is shared too. To share data between agents, store it in the model properties, or opt out of copying for a type with MicrobeAgents.copybehavior(b::MyType) = b.
light(pos) = exp(-pos[2] / 50)
phobic = Behavior(bias = (microbe, model) -> 1 + 2 * light(position(microbe)))
add_agent!(model; motility, behaviors = (BrownBerg(), phobic))Behaviors with memory: a struct plus methods for the hooks it uses.
@kwdef mutable struct Fatigue
rate::Float64 = 0.01
level::Float64 = 0.0
end
MicrobeAgents.affect!(f::Fatigue, microbe, model) = (f.level += f.rate * abmtimestep(model))
MicrobeAgents.speed_factor(f::Fatigue, microbe) = exp(-f.level)Sharing measurements
concentration, gradient, time_derivative and chemoattractant_diffusivity are evaluated at most once per microbe per step, however many behaviors ask for them. For other expensive quantities, define a sensor behavior placed before the behaviors that use it; it stores the value in its own field, and the others read it by name or through findbehavior.
Cached values refer to the position at the start of affect_step!. A behavior that moves the microbe (e.g. a wall rule) should call MicrobeAgents.invalidate_field_cache!(model) afterwards, so later behaviors and bias hooks see fresh values.
MicrobeAgents.behaviors — Function
behaviors(microbe)Return the behaviors (Tuple or NamedTuple) of microbe.
MicrobeAgents.findbehavior — Function
findbehavior(microbe, T)Return the first behavior of microbe of type T, or nothing.
Chemotaxis models
MicrobeAgents.BrownBerg — Type
BrownBerg(; gain=660, receptor_binding_constant=100, memory=1)Chemotaxis behavior from 'Brown and Berg (1974) PNAS'.
Parameters:
gain = 660sreceptor_binding_constant = 100μMmemory = 1s
Internal state: state, the weighted dPb/dt of the paper.
MicrobeAgents.Brumley — Type
Brumley(; memory=1.3, gain_receptor=50, gain=50, chemotactic_precision=6)Chemotaxis behavior from 'Brumley et al. (2019) PNAS', with gaussian sensing noise in the gradient measurement. Requires a microbe radius > 0 when chemotactic_precision > 0.
Parameters:
memory = 1.3s → 'τₘ'gain_receptor = 50μM⁻¹ → 'κ'gain = 50→ 'Γ'chemotactic_precision = 6→ 'Π'
Internal state: state → 'S'.
MicrobeAgents.Celani — Type
Celani(; gain=50, memory=1, chemotactic_precision=0)Chemotaxis behavior using the response kernel from 'Celani and Vergassola (2010) PNAS', extracted from experiments on E. coli. Optional sensing noise follows the Berg-Purcell formula, scaled by chemotactic_precision as in 'Brumley et al. (2019) PNAS'; it requires a microbe radius > 0. Internal markovian variables are initialized at steady state with the local concentration when the microbe is added.
Parameters:
gain = 50memory = 1schemotactic_precision = 0
Internal state: state, markovian_variables.
MicrobeAgents.Xie — Type
Xie(; adaptation_time_m=1.29, adaptation_time_z=0.28,
gain_forward=2.7, gain_backward=1.6, binding_affinity=0.39,
chemotactic_precision=0)Chemotaxis behavior adapted from 'Xie et al. (2019) Biophys J', based on the response function of V. alginolyticus. With a 4-state motility (RunReverseFlick), gain_backward applies in the backward run (state 3) and gain_forward otherwise. Optional Berg-Purcell sensing noise (chemotactic_precision) requires a microbe radius > 0.
Parameters:
adaptation_time_m = 1.29sadaptation_time_z = 0.28sgain_forward = 2.71/sgain_backward = 1.61/sbinding_affinity = 0.39μMchemotactic_precision = 0
Internal state: state, state_m, state_z.
MicrobeAgents.SonMenolascina — Function
SonMenolascina(; gain=660, receptor_binding_constant=100, memory=1,
eta=-0.55, ζ=-0.35, θ=1.0, vT=18.88, threshold=0.05, factor=1.3)Behaviors of the chemotaxis model from 'Son, Menolascina and Stocker (2016) PNAS': returns the NamedTuple (chemokinesis = Chemokinesis(...), chemotaxis = BrownBerg(...), turnrate = SpeedDependentTurnRate(...), flick = SpeedDependentFlick()), to be passed as behaviors (use with a RunReverseFlick motility).
MicrobeAgents.Chemokinesis — Type
Chemokinesis(; threshold=0.05, factor=1.3)Multiply the microbe speed by factor while the local concentration is at least threshold (μM).
MicrobeAgents.SpeedDependentTurnRate — Type
SpeedDependentTurnRate(; eta=-0.55, ζ=-0.35, θ=1.0, vT=18.88)Bias the switching rate by f(v)/f(0) with f(v) = 1 / (eta / (1 + exp(ζ*(v - vT))) + θ) and v = speed(microbe) (from 'Son, Menolascina and Stocker (2016) PNAS').
MicrobeAgents.SpeedDependentFlick — Type
SpeedDependentFlick(; p0=0.055, amplitude=0.72, steepness=0.25, v_half=36.0)With a 4-state motility (RunReverseFlick), make the probability of flicking after a backward run depend on speed: p = p0 + amplitude / (1 + exp(-steepness*(speed - v_half))); otherwise the run ends in a reversal.
Performance
StandardABM(Microbe{D}, ...) has a non-concrete agent type (Agents.jl warns, and dispatch is dynamic). For a homogeneous population, declare the concrete type; N must match the number of states of the motility.
bs = (chemotaxis = BrownBerg(),)
model = StandardABM(Microbe{2,2,typeof(bs)}, space, dt; properties)
add_agent!(model; motility = RunTumble([30.0], 0.67, Isotropic(2)), behaviors = bs)For mixed populations, keep Microbe{D} and pass warn = false to silence the warning.