Skip to content

Robotics

Robotics: autonomous robots and robot swarms.

LightSensitiveRobotElement(label, visual=None, *, x=0.0, y=0.0, phi=0.0, V0=1.0, V_inf=0.1, I0=1.0, r0=1.0, tau=1.0, R=2.0, delay=0.0)

Bases: Element

Robot in a Gaussian light field that slows down in light, reacting 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.

Parameters:

Name Type Description Default
label str

The label of the robot or the group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
x Annotated[float, StateSpec(role=position)]

The initial horizontal position.

0.0
y Annotated[float, StateSpec(role=position)]

The initial vertical position.

0.0
phi Annotated[float, StateSpec(role=direction)]

The initial direction of motion.

0.0
V0 Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The speed in the dark.

1.0
V_inf Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The speed in strong light.

0.1
I0 Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The intensity at the centre of the light.

1.0
r0 Annotated[float, ParameterSpec(domain=open(0, oo))]

The width of the light.

1.0
tau Annotated[float, ParameterSpec(domain=open(0, oo))]

The time scale of rotational diffusion.

1.0
R Annotated[float, ParameterSpec(domain=open(0, oo))]

The radius of the well.

2.0
delay float

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

0.0

Examples:

>>> from skcomplex.models.engineering.robotics import LightSensitiveRobotElement
>>> LightSensitiveRobotElement('robot', delay=5.0).delay
5.0
Source code in src/skcomplex/models/engineering/robotics/_light_sensitive_robot.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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

SwarmRobotElement(label, visual=VisualSpec('agents', {'color_by': 'illuminated', 'color_range': (0.0, 1.0)}), *, heading=0.0, connections=0.0, illuminated=1.0, speed=0.2, radio_range=4.0, alpha=20.0, avoid_illuminated=1.5, avoid_shadowed=1.0, shadow_angle=0.5, beacon_x=95.0, beacon_y=50.0, arena=100.0)

Bases: Element

Robot of the swarmtaxis algorithm, which keeps in radio contact with the swarm and avoids its neighbours.

Every step the robot counts the robots within its radio range. When it loses a connection and has fewer than alpha connections, it turns around to stay with the swarm, and when it gains one it turns at random. A robot closer than its avoidance radius to another turns away from it. A robot is shadowed when another robot lies between it and the beacon, within the radio range and shadow_angle of the bearing to the beacon, and it is illuminated otherwise. Illuminated robots avoid at a larger radius than shadowed ones, so they move away from the swarm towards the beacon, and the swarm follows. The robot then moves forward at its speed.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

VisualSpec('agents', {'color_by': 'illuminated', 'color_range': (0.0, 1.0)})
heading Annotated[float, StateSpec(role=direction)]

The direction of motion.

0.0
connections Annotated[float, StateSpec(domain=NN)]

The number of robots within radio range at the last step.

0.0
illuminated Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the robot sees the beacon.

1.0
speed Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The distance moved per step.

0.2
radio_range Annotated[float, ParameterSpec(domain=open(0, oo))]

The range of the radio pings.

4.0
alpha Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The number of connections below which a robot that loses one turns around.

20.0
avoid_illuminated Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The avoidance radius of illuminated robots.

1.5
avoid_shadowed Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The avoidance radius of shadowed robots.

1.0
shadow_angle Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The half angle of the cone towards the beacon in which another robot casts a shadow.

0.5
beacon_x Annotated[float, ParameterSpec()]

The horizontal position of the beacon.

95.0
beacon_y Annotated[float, ParameterSpec()]

The vertical position of the beacon.

50.0
arena Annotated[float, ParameterSpec(domain=open(0, oo))]

The side of the square arena, whose walls stop the robot.

100.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.engineering.robotics import SwarmRobotElement
>>> GroupElement('robots', element=SwarmRobotElement, size=2, location=[(1.0, 1.0), (2.0, 1.0)]).size
2
Source code in src/skcomplex/models/engineering/robotics/_swarm_robot.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'illuminated', 'color_range': (0.0, 1.0)}),
    *,
    heading: Annotated[float, StateSpec(role='direction')] = 0.0,
    connections: Annotated[float, StateSpec(domain=NN)] = 0.0,
    illuminated: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 1.0,
    speed: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    radio_range: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 4.0,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 20.0,
    avoid_illuminated: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.5,
    avoid_shadowed: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    shadow_angle: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    beacon_x: Annotated[float, ParameterSpec()] = 95.0,
    beacon_y: Annotated[float, ParameterSpec()] = 50.0,
    arena: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 100.0,
) -> None:
    super().__init__(label, visual=visual)
    self.heading = heading
    self.connections = connections
    self.illuminated = illuminated
    self.speed = speed
    self.radio_range = radio_range
    self.alpha = alpha
    self.avoid_illuminated = avoid_illuminated
    self.avoid_shadowed = avoid_shadowed
    self.shadow_angle = shadow_angle
    self.beacon_x = beacon_x
    self.beacon_y = beacon_y
    self.arena = arena

move(agent, model)

Update the connections, the shadow, and the heading, and move forward.

Parameters:

Name Type Description Default
agent SwarmRobotView

The robot.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/engineering/robotics/_swarm_robot.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def move(self, agent: SwarmRobotView, model: ModelView) -> None:
    """Update the connections, the shadow, and the heading, and move forward.

    Args:
        agent:
            The robot.

        model:
            The model.
    """
    neighbors = agent.neighbors(agent.radio_range)
    count = len(neighbors)
    agent.illuminated = 0.0 if self._is_shadowed(agent, neighbors) else 1.0
    heading = self._avoid(agent, neighbors, self._turn(agent, model, count))
    agent.connections = count
    agent.heading = heading % (2 * math.pi)
    x, y = SwarmRobotElement._position(agent)
    edge = agent.arena * (1 - EDGE_FRACTION)
    x = min(max(x + agent.speed * math.cos(heading), 0.0), edge)
    y = min(max(y + agent.speed * math.sin(heading), 0.0), edge)
    agent.move_to((x, y))

light_seeking_robots(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)

Return light-sensitive robots in a circular well, whose 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.

Parameters:

Name Type Description Default
n_robots int

The number of robots.

20
delay float

The sensory delay.

5.0
V0 float

The speed in the dark.

1.0
V_inf float

The speed in strong light.

0.1
I0 float

The intensity at the centre of the light.

1.0
r0 float

The width of the light.

1.0
tau float

The time scale of rotational diffusion.

1.0
R float

The radius of the well.

2.0

Returns:

Name Type Description
system System

The model, with the group robots of positions x, y and directions phi.

Examples:

>>> from skcomplex.models.engineering.robotics import light_seeking_robots
>>> light_seeking_robots(4).elements[0].check().prepare().size_
4
Source code in src/skcomplex/models/engineering/robotics/_robots.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@register_model(
    field='engineering/robotics',
    tags=('robotics', 'active matter', 'delay', 'sde'),
    references=('Mijalkov et al. 2016', 'Argun et al. 2021'),
    defaults={'until': 100.0, 'dt': 0.5},
)
def light_seeking_robots(
    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,
) -> System:
    """Return light-sensitive robots in a circular well, whose 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.

    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.

    Returns:
        system:
            The model, with the group `robots` of positions `x`, `y` and directions `phi`.

    Examples:
        >>> from skcomplex.models.engineering.robotics import light_seeking_robots
        >>> light_seeking_robots(4).elements[0].check().prepare().size_
        4
    """
    headings = np.linspace(0.0, 2 * np.pi, n_robots, endpoint=False)
    robots = GroupElement(
        'robots',
        element=LightSensitiveRobotElement,
        size=n_robots,
        values={
            'x': np.zeros(n_robots),
            'y': np.zeros(n_robots),
            'phi': headings,
            'V0': V0,
            'V_inf': V_inf,
            'I0': I0,
            'r0': r0,
            'tau': tau,
            'R': R,
            'delay': delay,
        },
    )
    return System(elements=[robots])

swarmtaxis(n_robots=20, *, size=100.0, spread=3.0, speed=0.2, radio_range=4.0, avoid_illuminated=1.5, avoid_shadowed=1.0, random_state=0)

Return the swarmtaxis algorithm, which moves a swarm of robots towards a beacon that only some of them see.

The robots start in a cluster near the left edge of a square arena, with the beacon at the middle of the right edge. Each robot keeps radio contact with the swarm by turning around when it loses a connection, and avoids its neighbours. Robots that see the beacon avoid at a larger radius than shadowed robots, which moves the centre of the swarm towards the beacon.

Parameters:

Name Type Description Default
n_robots int

The number of robots.

20
size float

The side of the arena.

100.0
spread float

The standard deviation of the initial positions around the start.

3.0
speed float

The distance a robot moves per step.

0.2
radio_range float

The range of the radio pings.

4.0
avoid_illuminated float

The avoidance radius of illuminated robots.

1.5
avoid_shadowed float

The avoidance radius of shadowed robots.

1.0
random_state int | Generator | None

The seed or generator of the initial positions and headings.

0

Returns:

Name Type Description
system System

The model, with the group robots of headings heading and illumination illuminated.

Examples:

>>> from skcomplex.models.engineering.robotics import swarmtaxis
>>> swarmtaxis(5).elements[0].check().prepare().size_
5
Source code in src/skcomplex/models/engineering/robotics/_robots.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@register_model(
    field='engineering/robotics',
    tags=('robotics', 'swarms', 'agents', 'taxis'),
    references=('Bjerknes, Winfield, and Melhuish 2007', 'Varughese et al. 2019'),
    defaults={'until': 300},
)
def swarmtaxis(
    n_robots: int = 20,
    *,
    size: float = 100.0,
    spread: float = 3.0,
    speed: float = 0.2,
    radio_range: float = 4.0,
    avoid_illuminated: float = 1.5,
    avoid_shadowed: float = 1.0,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the swarmtaxis algorithm, which moves a swarm of robots towards a beacon that only some of them see.

    The robots start in a cluster near the left edge of a square arena, with the beacon at the middle of the right edge.
    Each robot keeps radio contact with the swarm by turning around when it loses a connection, and avoids its
    neighbours. Robots that see the beacon avoid at a larger radius than shadowed robots, which moves the centre of the
    swarm towards the beacon.

    Args:
        n_robots:
            The number of robots.

        size:
            The side of the arena.

        spread:
            The standard deviation of the initial positions around the start.

        speed:
            The distance a robot moves per step.

        radio_range:
            The range of the radio pings.

        avoid_illuminated:
            The avoidance radius of illuminated robots.

        avoid_shadowed:
            The avoidance radius of shadowed robots.

        random_state:
            The seed or generator of the initial positions and headings.

    Returns:
        system:
            The model, with the group `robots` of headings `heading` and illumination `illuminated`.

    Examples:
        >>> from skcomplex.models.engineering.robotics import swarmtaxis
        >>> swarmtaxis(5).elements[0].check().prepare().size_
        5
    """
    rng = np.random.default_rng(random_state)
    start = np.array([size / 5, size / 2])
    points = np.clip(start + rng.normal(0.0, spread, (n_robots, 2)), 0.0, size)
    robots = GroupElement(
        'robots',
        element=SwarmRobotElement,
        size=n_robots,
        values={
            'heading': rng.uniform(0.0, 2 * np.pi, n_robots),
            'speed': speed,
            'radio_range': radio_range,
            'alpha': float(n_robots),
            'avoid_illuminated': avoid_illuminated,
            'avoid_shadowed': avoid_shadowed,
            'beacon_x': size - BEACON_MARGIN,
            'beacon_y': size / 2,
            'arena': size,
        },
        location=[tuple(map(float, point)) for point in points],
    )
    return System(elements=[robots], space=ContinuousSpace(extent=(size, size)))