Skip to content

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, from Element
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