Skip to content

Hydrodynamically coupled particles in blinking traps

Two Brownian spheres in blinking optical traps, whose free motion is correlated through the fluid.

  • Name: hydrodynamically_coupled_traps
  • Field: physics/soft_matter
  • Formalism: discrete map
  • References: Argun et al. 2021; Batchelor 1976; Rotne and Prager 1969; Magazzù et al. 2019

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('hydrodynamically_coupled_traps')
entry = describe('hydrodynamically_coupled_traps')
trajectory = create_simulation('hydrodynamically_coupled_traps').prepare().run().trajectory_

Factory

@register_system(
    field='physics/soft_matter',
    tags=('brownian motion', 'hydrodynamics', 'optical tweezers', 'blinking traps', 'correlated noise'),
    references=('Argun et al. 2021', 'Batchelor 1976', 'Rotne and Prager 1969', 'Magazzù et al. 2019'),
    defaults={'until': 20000, 'dt': 1},
)
def hydrodynamically_coupled_traps(
    separation: float = 3.0,
    *,
    tensor: str = 'rotne-prager-yamakawa',
    root: str = 'cholesky',
    radius: float = 1.0,
    viscosity: float = 1e-3,
    kT: float = 4.1e-3,
    k: float = 0.2,
    on_time: float = 0.3,
    off_time: float = 0.1,
    dt: float = 5e-3,
) -> System:
    """Return two Brownian spheres in blinking optical traps, whose free motion is correlated through the fluid.

    The traps hold the spheres at a distance `separation` along `x`, and switch off for `off_time` after every
    `on_time`. While they are off the spheres diffuse freely, and the force on one moves the other through the
    mobility, so their displacements correlate as `M_12 / M_11`. That is about `3 a / (2 r)` along the line of centres
    and `3 a / (4 r)` across it. The units are micrometres, seconds, and piconewtons, so the defaults are spheres of
    radius 1 um in water at room temperature, and each step of the system lasts `dt` seconds.

    Args:
        separation:
            The distance between the centres of the traps.

        tensor:
            `'free-draining'`, `'oseen'`, or `'rotne-prager-yamakawa'`.

        root:
            `'cholesky'` or `'lanczos'`.

        radius:
            The radius of the spheres.

        viscosity:
            The viscosity of the fluid.

        kT:
            The thermal energy.

        k:
            The stiffness of the traps.

        on_time:
            The time the traps stay on in every period.

        off_time:
            The time the traps stay off in every period.

        dt:
            The length of a step.

    Returns:
        system:
            The system, with the positions `spheres.x`.

    Examples:
        >>> from skcomplex.systems.physics.soft_matter import hydrodynamically_coupled_traps
        >>> hydrodynamically_coupled_traps(4.0).elements[0].values['center']
        [[-2.0, 0.0], [2.0, 0.0]]
    """
    centers = [[-separation / 2, 0.0], [separation / 2, 0.0]]
    spheres = GroupElement(
        'spheres',
        element=TrappedSphereElement,
        size=2,
        values={'x': centers, 'center': centers},
    )
    traps = BlinkingTrapsInteraction(
        'tweezers',
        target='spheres',
        k=k,
        on_time=on_time,
        off_time=off_time,
        tensor=tensor,
        root=root,
        radius=radius,
        viscosity=viscosity,
        temperature=kT,
        dt=dt,
    )
    return System(elements=[spheres], interactions=[traps], space=ContinuousSpace(n_dim=2), time=DiscreteTime())

Components

Elements:

  • TrappedSphereElement, from Element

Interactions:

  • BlinkingTrapsInteraction, from MobilityInteraction
class TrappedSphereElement(Element):
    """Colloidal sphere at the position `x`, held by an optical trap centred at `center`.

    The sphere has no friction of its own. A mobility interaction moves it through the fluid it shares with the other
    spheres, and its trap is one of the forces the mobility turns into velocities.

    Args:
        label:
            The label of the sphere or the group.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        x:
            The initial position.

        center:
            The centre of the trap of the sphere.

    Examples:
        >>> from skcomplex.systems.physics.soft_matter import TrappedSphereElement
        >>> TrappedSphereElement('bead', center=[3.0, 0.0]).center
        [3.0, 0.0]
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = SPHERES,
        *,
        x: Annotated[npt.ArrayLike, StateSpec(vector=True, role='position')] = 0.0,
        center: Annotated[npt.ArrayLike, ParameterSpec(vector=True)] = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.x = x
        self.center = center

class BlinkingTrapsInteraction(MobilityInteraction):
    """Blinking optical traps on spheres that move through a shared fluid, in steps of Brownian dynamics.

    Every sphere feels the harmonic force `-k (x - center)` of its own trap while the traps are on, for `on_time` of
    every period `on_time + off_time`, and no force while they are off. The mobility turns the forces on all spheres
    into their velocities, and the thermal energy `temperature` adds Brownian displacements whose correlations between
    spheres follow the same mobility, so spheres released from their traps diffuse together (Batchelor 1976). Every
    step lasts `dt`, and the time of the system counts steps.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the group of spheres.

        k:
            The stiffness of every trap.

        on_time:
            The time the traps stay on in every period.

        off_time:
            The time the traps stay off in every period.

        tensor:
            `'free-draining'`, `'oseen'`, or `'rotne-prager-yamakawa'`.

        root:
            `'cholesky'` or `'lanczos'`.

        radius:
            The radius of the spheres.

        viscosity:
            The viscosity of the fluid.

        temperature:
            The thermal energy `kT`.

        dt:
            The length of a step.

    Examples:
        >>> from skcomplex.systems.physics.soft_matter import BlinkingTrapsInteraction
        >>> BlinkingTrapsInteraction('tweezers', target='beads', off_time=0.2).form
        'map'
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        k: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        on_time: float = 1.0,
        off_time: float = 0.0,
        tensor: str = 'rotne-prager-yamakawa',
        root: str = 'cholesky',
        radius: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        viscosity: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        temperature: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        dt: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.01,
    ) -> None:
        super().__init__(
            label,
            target=target,
            tensor=tensor,
            form='map',
            force=self._get_trap_force,
            root=root,
            radius=radius,
            viscosity=viscosity,
            temperature=temperature,
            dt=dt,
        )
        self.k = k
        self.on_time = on_time
        self.off_time = off_time

    def _get_trap_force(self, namespace: SimpleNamespace) -> FreeModuleElement:
        """Return the force of the trap of a sphere, switched off for `off_time` of every period."""
        s = namespace
        on = 1 if self.off_time == 0 else pulse_train(s.t * s.dt, self.on_time + self.off_time, self.on_time)
        return -s.k * on * (s.target.x - s.target.center)

Equations