Skip to content

Overdamped particle in a tilted periodic potential

Overdamped particles in the tilted periodic potential V(x) = U0 cos(2 pi x / L) - f x.

  • Name: tilted_periodic_potential
  • Field: physics/soft_matter
  • Formalism: stochastic differential equations
  • References: Bertin 2021; Risken 1996; Reimann 2002

Factory

@register_model(
    field='physics/soft_matter',
    tags=('brownian motion', 'sde', 'nonequilibrium steady state', 'current'),
    references=('Bertin 2021', 'Risken 1996', 'Reimann 2002'),
    defaults={'until': 50.0, 'dt': 0.25, 'solver': EulerMaruyamaSolver(dt=0.005)},
)
def tilted_periodic_potential(
    n_particles: int = 500,
    *,
    f: float = 1.0,
    U0: float = 0.5,
    L: float = 1.0,
    gamma: float = 1.0,
    kT: float = 0.5,
) -> System:
    """Return overdamped particles in the tilted periodic potential `V(x) = U0 cos(2 pi x / L) - f x`.

    The force `f` breaks the left and right symmetry, so the particles reach a nonequilibrium steady state with a
    uniform probability current `J`. Their mean velocity is
    `J L = L (1 - exp(-f L / kT)) / int_0^L dx int_0^L dy exp((V(x) - V(x - y)) / kT) kT / gamma`, which is
    `f / gamma` without the potential and exponentially small in the barrier for a weak force. The particles start
    at the origin.

    Args:
        n_particles:
            The number of independent particles.

        f:
            The constant force that tilts the potential.

        U0:
            The amplitude of the periodic potential.

        L:
            The period of the potential.

        gamma:
            The friction coefficient.

        kT:
            The thermal energy.

    Returns:
        system:
            The model, with the positions `particles.x`.

    Examples:
        >>> from skcomplex.models.physics.soft_matter import tilted_periodic_potential
        >>> tilted_periodic_potential(10, f=2.0).interactions[0].f
        2.0
    """
    particles = GroupElement(
        'particles',
        element=BrownianParticleElement,
        size=n_particles,
        values={'x': np.zeros((n_particles, 1)), 'gamma': gamma, 'temperature': kT},
    )
    washboard = TiltedWashboardInteraction('washboard', target='particles', U0=U0, L=L, f=f)
    bath = ThermalNoiseInteraction('bath', target='particles')
    return System(elements=[particles], interactions=[washboard, bath], space=ContinuousSpace(n_dim=1))

Components

class BrownianParticleElement(Element):
    """Overdamped Brownian particle at the position `x`, with the friction `gamma` and the thermal energy `temperature`.

    The particle moves by `dx = F / gamma dt + sqrt(2 temperature / gamma) dW`, with the thermal noise of
    `ThermalNoiseInteraction` and the forces of other interactions, which divide by `gamma`.

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

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

        x:
            The initial position.

        gamma:
            The friction coefficient.

        temperature:
            The thermal energy.

    Examples:
        >>> from skcomplex.models.physics.soft_matter import BrownianParticleElement
        >>> BrownianParticleElement('bead', temperature=2.0).temperature
        2.0
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = POINTS,
        *,
        x: Annotated[npt.ArrayLike, StateSpec(vector=True, role='position')] = 0.0,
        gamma: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        temperature: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.x = x
        self.gamma = gamma
        self.temperature = temperature

class TiltedWashboardInteraction(GradientInteraction):
    """Tilted periodic potential `U(x) - f x`, with `U(x) = U0 cos(2 pi x / L)`, on overdamped particles on a line.

    The force divides by the friction `gamma` of the target.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the particle or the group.

        U0:
            The amplitude of the periodic potential, half its barrier height.

        L:
            The period of the potential.

        f:
            The constant non-conservative force that tilts the potential.

    Examples:
        >>> from skcomplex.models.physics.soft_matter import TiltedWashboardInteraction
        >>> TiltedWashboardInteraction('washboard', target='beads', f=0.5).f
        0.5
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        U0: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        L: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        f: Annotated[float, ParameterSpec()] = 0.0,
    ) -> None:
        super().__init__(label, target=target, states='x', divisor='gamma')
        self.U0 = U0
        self.L = L
        self.f = f

    def _get_potential(self, s: SimpleNamespace) -> Expression:
        """Return the tilted potential `U0 cos(2 pi x / L) - f x`.

        Args:
            s:
                The namespace of the symbols the potential can use.

        Returns:
            potential:
                The potential.
        """
        position = s.target.x[0]
        return s.U0 * cos(2 * math.pi * position / s.L) - s.f * position

class ThermalNoiseInteraction(RateInteraction):
    """Thermal noise of overdamped particles, the random force `sqrt(2 gamma temperature) dW` divided by `gamma`.

    The friction `gamma` and the thermal energy `temperature` are parameters of the target, so every component of the
    position diffuses with the coefficient `temperature / gamma`.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the particle or the group.

        state:
            The name of the state that holds the positions.

    Examples:
        >>> from skcomplex.models.physics.soft_matter import ThermalNoiseInteraction
        >>> ThermalNoiseInteraction('bath', target='beads').divisor
        'gamma'
    """

    def __init__(self, label: str, *, target: Annotated[str, RoleSpec()], state: str = 'x') -> None:
        super().__init__(label, target=target, state=state, divisor='gamma')

    def _get_noise(self, namespace: SimpleNamespace) -> object:
        """Return the amplitude `sqrt(2 gamma temperature)` of the random force.

        Args:
            namespace:
                The namespace of the symbols the amplitude can use.

        Returns:
            amplitude:
                The noise amplitude, before the division by the friction.
        """
        target = namespace.target
        return sqrt(2 * target.gamma * target.temperature)

Equations

particles.x[i, 0]: diff(x_particles_0(t), t) == (f_washboard + 6.283185307179586*U0_washboard*sin(6.283185307179586*x_particles_0(t)/L_washboard)/L_washboard)/gamma_particles