This program simulates the phenomenon of aggregation of a swarm of robots with no cooperation between each other and each robot following a very simple rule:
The velocity of this robot has a random direction and the absolute value of this velocity is inversely proportional to the number of robots inside of a threshold
distance from this robot.
This means that the robot will move slower the more friends it has nearby, so it will spend more time near other robots rather than far away, and this is enough to create aggregation of robots, with no communication nor cooperation needed.
The program does a live representation of the swarm of robots and a live plot of the average number of neighbors of each robot, that is increasing over time.
Screenshot after some time
Graph of average number of neighbors over time
Graph of average number of neighbors over time, higher time limit
Code
import sys
import pygame
from pygame.locals import QUIT
import random
import math
from operator import mul, truediv
from statistics import mean
from itertools import combinations
import matplotlib.pyplot as plt
import numpy as np
class Robot:
def __init__(self, x, y, v=0, v0=None, aggregating_mode=True):
self.x = x
self.y = y
self.v = 0
self.aggregating_mode = aggregating_mode
self.v0 = v0 or (20 if aggregating_mode else 2)
def distance_to_robot(self, other):
return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
def count_nears(self, robots, threshold=100):
return sum(1 for robot in robots if self.distance_to_robot(robot) < threshold)
def update_speed(self, robots, threshold=100):
# Denominator is never 0 because the robot will always count at least itself
operation = truediv if self.aggregating_mode else mul
self.v = self.v0 * operation(1.0, self.count_nears(robots, threshold=threshold))
def update_position(self, width, height):
theta = random.random() * 2 * math.pi
self.x += math.cos(theta) * self.v
self.y += math.sin(theta) * self.v
self.x %= width
self.y %= height
def draw_self(self, SCREEN):
color = (255, 0, 0) if self.aggregating_mode else (0, 255, 0)
pygame.draw.circle(SCREEN, color, (self.x, self.y), 10)
def __repr__(self):
return f"Robot({self.x}, {self.y}, {self.v}"
def main():
pygame.init()
pygame.display.set_caption("Swarm robotics simulation: aggregation and distancing")
FPS = 60
FPS_CLOCK = pygame.time.Clock()
pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
WIDTH, HEIGHT = pygame.display.Info().current_w, pygame.display.Info().current_h
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
def random_pos():
return (random.randint(0, WIDTH), random.randint(0, HEIGHT))
robots = [Robot(*random_pos(), aggregating_mode=True) for _ in range(140)]
# Game loop.
i = 0
average_counts = []
is_ = []
PLOTTING = True
while True:
i += 1
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Update and Draw
for robot in robots:
robot.update_speed(robots)
robot.update_position(WIDTH, HEIGHT)
robot.draw_self(SCREEN)
if i % 100 == 0:
average_near_count = mean((robot.count_nears(robots) for robot in robots))
average_counts.append(average_near_count)
if PLOTTING:
is_.append(i)
plt.axis([0, 15000, 0, 14])
plt.xlabel("Timestep")
plt.ylabel("Average number of neightbours")
plt.scatter(is_, average_counts)
plt.pause(0.1)
plt.show()
print(average_near_count)
pygame.display.flip()
FPS_CLOCK.tick(FPS)
if __name__ == "__main__":
main()