Skip to content

Light-sensitive robots with sensory delay

Light-sensitive robots in a circular well.

  • Class: LightSeekingRobotsSystem
  • Import: from skcomplex.systems.engineering.robotics import LightSeekingRobotsSystem
  • Field: engineering/robotics
  • Topics: robotics, active matter, delay, sde
  • References: Mijalkov et al. 2016; Argun et al. 2021

Use this system

from skcomplex.core.engine.runtime import simulate
from skcomplex.systems.engineering.robotics import LightSeekingRobotsSystem

trajectory = simulate(LightSeekingRobotsSystem())

Parameters

Parameter Default
n_robots 20
delay 5.0
V0 1.0
V_inf 0.1
I0 1.0
r0 1.0
tau 1.0
R 2.0

Tags

Tag Values
Time continuous
Space none
Laws noise, rate, update
Randomness stochastic
Domains real
Structure fixed

Class

class LightSeekingRobotsSystem(BaseSystem):
    """Light-sensitive robots in a circular well.

    Their sensory delay decides whether they gather in the light. Each robot moves at
    `V_inf + (V0 - V_inf) exp(-I(t - delay))` in a direction that diffuses with time scale `tau`, in the Gaussian light
    `I = I0 exp(-(x^2 + y^2) / r0^2)` at the centre of a well of radius `R`. The robots start at the centre, in
    directions spread around the circle, and do not interact. A positive delay drives them out of the light: a robot
    crosses the light at the fast speed it measured in the dark and lingers outside at the slow speed it measured in the
    light, so robots with a delay spend less time in the light than robots without one.

    The system has the group `robots` of positions `x`, `y` and directions `phi`.

    Args:
        n_robots:
            The number of robots.

        delay:
            The sensory delay.

        V0:
            The speed in the dark.

        V_inf:
            The speed in strong light.

        I0:
            The intensity at the centre of the light.

        r0:
            The width of the light.

        tau:
            The time scale of rotational diffusion.

        R:
            The radius of the well.

        label:
            The label of the system, which names it when it is nested in another one.

    Examples:
        >>> from skcomplex.systems.engineering.robotics import LightSeekingRobotsSystem
        >>> LightSeekingRobotsSystem(4).prepare().system_.elements[0].prepare().size_
        4
    """

    field = 'engineering/robotics'
    topics = ('robotics', 'active matter', 'delay', 'sde')
    references = ('Mijalkov et al. 2016', 'Argun et al. 2021')
    until = 100.0
    dt = 0.5

    def __init__(
        self,
        n_robots: int = 20,
        *,
        delay: float = 5.0,
        V0: float = 1.0,
        V_inf: float = 0.1,
        I0: float = 1.0,
        r0: float = 1.0,
        tau: float = 1.0,
        R: float = 2.0,
        label: str = 'system',
    ) -> None:
        super().__init__(label)
        self.n_robots = n_robots
        self.delay = delay
        self.V0 = V0
        self.V_inf = V_inf
        self.I0 = I0
        self.r0 = r0
        self.tau = tau
        self.R = R

    def _build_system(self) -> System:
        """Return the parts of the system built from its model parameters."""
        headings = np.linspace(0.0, 2 * np.pi, self.n_robots, endpoint=False)
        robots = GroupElement(
            'robots',
            element=LightSensitiveRobotElement,
            size=self.n_robots,
            values={
                'x': np.zeros(self.n_robots),
                'y': np.zeros(self.n_robots),
                'phi': headings,
                'V0': self.V0,
                'V_inf': self.V_inf,
                'I0': self.I0,
                'r0': self.r0,
                'tau': self.tau,
                'R': self.R,
                'delay': self.delay,
            },
        )
        return System(self.label, elements=[robots])

Components

Elements:

  • LightSensitiveRobotElement, from Element
class LightSensitiveRobotElement(Element):
    """Robot in a Gaussian light field that slows down in light.

    It reacts to the light it measured a delay ago. The robot moves at the speed
    `v = V_inf + (V0 - V_inf) exp(-I(t - delay))` in the direction `phi`, which diffuses with time scale `tau`,
    `dphi = sqrt(2 / tau) dW`. The light is `I(x, y) = I0 exp(-(x^2 + y^2) / r0^2)`, and a wall at radius `R` reflects
    the robot back into a circular well.

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

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

        x:
            The initial horizontal position.

        y:
            The initial vertical position.

        phi:
            The initial direction of motion.

        V0:
            The speed in the dark.

        V_inf:
            The speed in strong light.

        I0:
            The intensity at the centre of the light.

        r0:
            The width of the light.

        tau:
            The time scale of rotational diffusion.

        R:
            The radius of the well.

        delay:
            The sensory delay, 0 for a robot that reacts to the light it measures now.

    Examples:
        >>> from skcomplex.systems.engineering.robotics import LightSensitiveRobotElement
        >>> LightSensitiveRobotElement('robot', delay=5.0).delay
        5.0
    """

    law_classes = (RateLaw, NoiseLaw, BoundaryLaw)

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        x: Annotated[float, StateSpec(role='position')] = 0.0,
        y: Annotated[float, StateSpec(role='position')] = 0.0,
        phi: Annotated[float, StateSpec(role='direction')] = 0.0,
        V0: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        V_inf: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
        I0: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
        r0: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        tau: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
        R: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
        delay: float = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.x = x
        self.y = y
        self.phi = phi
        self.V0 = V0
        self.V_inf = V_inf
        self.I0 = I0
        self.r0 = r0
        self.tau = tau
        self.R = R
        self.delay = delay

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the motion at the light-dependent speed.

        It includes the rotational noise and the reflecting wall.

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

        Returns:
            laws:
                The laws of the component.
        """
        sensed = (
            LightSensitiveRobotElement._light(s, delayed(s.x, self.delay), delayed(s.y, self.delay))
            if self.delay
            else LightSensitiveRobotElement._light(s, s.x, s.y)
        )
        speed = s.V_inf + (s.V0 - s.V_inf) * exp(-sensed)
        reach = where(sqrt(s.x**2 + s.y**2) > s.R, sqrt(s.x**2 + s.y**2), s.R)
        return [
            RateLaw(s.x, speed * cos(s.phi)),
            RateLaw(s.y, speed * sin(s.phi)),
            NoiseLaw(s.phi, sqrt(2 / s.tau)),
            BoundaryLaw(s.x, s.x * s.R / reach),
            BoundaryLaw(s.y, s.y * s.R / reach),
        ]

    @staticmethod
    def _light(s: SimpleNamespace, x: Expression, y: Expression) -> Expression:
        """Return the Gaussian light intensity `I0 exp(-(x^2 + y^2) / r0^2)` at a point."""
        return s.I0 * exp(-(x**2 + y**2) / s.r0**2)

Equations

robots.x[i]: diff(x_robots(t), t) == ((V0_robots - V_inf_robots)*e^(-I0_robots*e^(-(x_robots(t - 5.0)^2 + y_robots(t - 5.0)^2)/r0_robots^2)) + V_inf_robots)*cos(phi_robots(t))
robots.y[i]: diff(y_robots(t), t) == ((V0_robots - V_inf_robots)*e^(-I0_robots*e^(-(x_robots(t - 5.0)^2 + y_robots(t - 5.0)^2)/r0_robots^2)) + V_inf_robots)*sin(phi_robots(t))
robots.phi[i]: diff(phi_robots(t), t) == 0