Inertial active Brownian particles
Active Brownian particles with inertia, which lag behind their orientation as they speed up and turn.
- Name:
inertial_active_brownian_particles - Field:
physics/active_matter - Formalism: stochastic differential equations
- References: Löwen 2020; Scholz et al. 2018; Bechinger et al. 2016
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('inertial_active_brownian_particles')
entry = describe('inertial_active_brownian_particles')
trajectory = create_simulation('inertial_active_brownian_particles').prepare().run().trajectory_
Factory
@register_system(
field='physics/active_matter',
tags=('active matter', 'sde', 'self-propulsion', 'inertia', 'second order'),
references=('Löwen 2020', 'Scholz et al. 2018', 'Bechinger et al. 2016'),
defaults={'until': 20.0, 'dt': 0.5, 'solver': EulerMaruyamaSolver(dt=0.005)},
)
def inertial_active_brownian_particles(
n_particles: int = 200,
*,
speed: float = 3.0,
mass: float = 0.5,
moment: float = 0.1,
D_t: float = 0.1,
D_r: float = 1.0,
size: float = 50.0,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return active Brownian particles with inertia, which lag behind their orientation as they speed up and turn.
With unit friction, every particle follows `m x'' = -x' + v0 n + sqrt(2 D_t) xi` and
`J theta'' = -theta' + sqrt(2 D_r) xi_r`, the inertial active Brownian particles of macroscopic swimmers, flyers,
and vibrated grains. The velocity relaxes over the time `m`, so it trails the orientation, and the mean projection
of the velocity on the orientation falls below `v0`. With rotational inertia alone, the orientation correlation is
`exp(-D_r (t - J (1 - exp(-t / J))))`, which gives the steady projection
`v0 / m ∫ exp(-t / m) exp(-D_r (t - J (1 - exp(-t / J)))) dt`, and `v0 / (1 + m D_r)` without rotational inertia.
The particles start at rest at uniform positions and orientations.
Args:
n_particles:
The number of particles.
speed:
The self-propulsion speed `v0`.
mass:
The mass `m`, in units of the friction.
moment:
The moment of inertia `J`, in units of the rotational friction.
D_t:
The translational diffusion coefficient.
D_r:
The rotational diffusion coefficient.
size:
The side of the square in which the particles start.
random_state:
The seed or generator of the initial positions and orientations.
Returns:
system:
The system, with the positions `swimmers.x`, the velocities `swimmers.x_d1`, the orientations
`swimmers.theta`, and the angular velocities `swimmers.theta_d1`.
Examples:
>>> from skcomplex.systems.physics.active_matter import inertial_active_brownian_particles
>>> inertial_active_brownian_particles(10).elements[0].prepare().size_
10
"""
positions, angles = _swimmers(n_particles, size, random_state)
group = GroupElement(
'swimmers',
element=InertialActiveParticleElement,
size=n_particles,
values={
'x': positions,
'theta': angles,
'speed': speed,
'mass': mass,
'moment': moment,
'D_t': D_t,
'D_r': D_r,
},
)
return System(elements=[group], space=ContinuousSpace(n_dim=2))
Components
Elements:
InertialActiveParticleElement, fromElement
class InertialActiveParticleElement(Element):
"""Active Brownian particle with mass and moment of inertia, whose velocity and spin relax with friction.
With unit friction, the position follows `m x'' = -x' + v0 n + sqrt(2 D_t) xi` along the orientation
`n = (cos theta, sin theta)`, and the orientation follows `J theta'' = -theta' + sqrt(2 D_r) xi_r`. The velocity
relaxes over the time `m` and the angular velocity over the time `J`, and both vanish to give the overdamped
active Brownian particle.
Args:
label:
The label of the particle or the group.
visual:
How the particle is drawn.
x:
The initial position.
x_d1:
The initial velocity.
theta:
The initial orientation.
theta_d1:
The initial angular velocity.
speed:
The self-propulsion speed `v0`.
mass:
The mass `m`, in units of the friction.
moment:
The moment of inertia `J`, in units of the rotational friction.
D_t:
The translational diffusion coefficient.
D_r:
The rotational diffusion coefficient.
Examples:
>>> from skcomplex.systems.physics.active_matter import InertialActiveParticleElement
>>> InertialActiveParticleElement('swimmer').mass
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = POINTS,
*,
x: Annotated[npt.ArrayLike, StateSpec(vector=True, order=2, role='position')] = 0.0,
x_d1: npt.ArrayLike = 0.0,
theta: Annotated[float, StateSpec(order=2, role='direction')] = 0.0,
theta_d1: float = 0.0,
speed: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
mass: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.5,
moment: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
D_t: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
D_r: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.x_d1 = x_d1
self.theta = theta
self.theta_d1 = theta_d1
self.speed = speed
self.mass = mass
self.moment = moment
self.D_t = D_t
self.D_r = D_r
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the accelerations of the position and the orientation, and their noises.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
heading = vector(SR, [cos(s.theta), sin(s.theta)])
return [
RateLaw(s.x, (s.speed * heading - s.x_d1) / s.mass),
NoiseLaw(s.x, sqrt(2 * s.D_t) / s.mass),
RateLaw(s.theta, -s.theta_d1 / s.moment),
NoiseLaw(s.theta, sqrt(2 * s.D_r) / s.moment),
]
Equations
swimmers.x[i, 0]: diff(x_swimmers_0(t), t, t) == (speed_swimmers*cos(theta_swimmers(t)) - diff(x_swimmers_0(t), t))/mass_swimmers
swimmers.x[i, 1]: diff(x_swimmers_1(t), t, t) == (speed_swimmers*sin(theta_swimmers(t)) - diff(x_swimmers_1(t), t))/mass_swimmers
swimmers.theta[i]: diff(theta_swimmers(t), t, t) == -diff(theta_swimmers(t), t)/moment_swimmers