Home>

The BOID model is implemented using Python.

Error message

I use Python standard tkinter, but the bounce process doesn't work well when there are multiple birds and converges to the lower right corner.

Applicable source code
import math
import random
from statistics import mean
from tkinter import *

class Coordinate ():
    def __init __ (self):
        self.x = 0
        self.y = 0
    def reset (self):
        self.x = 0
        self.y = 0

class Bird ():
    "" "
    Bird class
    "" "
    def __init __ (self, x, y, vx, vy, identifier, field_info, others, view):
        "" "Bird Class Constructor
        Agent for BOID
        : param x: x coordinate
        : param y: y coordinate
        : param vx: amount of movement in the x direction
        : param vy: amount of movement in y direction
        : param id: ID for identification
        : param field_info: (field_width, field_height, fish_radius, fish_speed)
        : param others: other fish
        : param view: Field of view (radius)
        "" "
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.identifier = identifier
        self.others = others
        self.view = view
        self.field_info = field_info
        self.inverse_x = self.field_info [0]-self.x
        self.inverse_y = self.field_info [1]-self.y
        self.r1 = 2.0 # cohesion coefficient
        self.r2 = 0.5 # separation coefficient
        self.r3 = 1.0 # alignment coefficient
        self.neighbors = [] # nearby agents
        self.v1 = Coordinate ()
        self.v2 = Coordinate ()
        self.v3 = Coordinate ()
    def set_others (self, others):
        self.others = others
        self.find_neighbors ()
    # Joindef cohesion (self):
        center_pull_coeff = 10
        if len (self.neighbors) == 0:
            return
        self.v1.x = mean ([agent.x for agent in self.neighbors if not agent.identifier == self.identifier])
        self.v1.y = mean ([agent.y for agent in self.neighbors if not agent.identifier == self.identifier])
        self.v1.x = (self.v1.x-self.x)/center_pull_coeff
        self.v1.y = (self.v1.y-self.y)/center_pull_coeff
    # Withdrawal
    def separation (self):
        personal_space = 50
        for agent in self.neighbors:
            if self.identifier == agent.identifier:
                continue
            dist = self.calc_distance (agent, self)
            if dist<= personal_space:
                self.v2.x-= (agent.x-self.x)/dist
                self.v2.y-= (agent.y-self.y)/dist
    def _separation (self):
        if len (self.neighbors) == 0:
            return
        self.v2.x = mean ((agent.x for agent in self.neighbors if not agent.identifier == self.identifier])-self.x
        self.v2.y = mean ([agent.y for agent in self.neighbors if not agent.identifier == self.identifier])-self.y
    # Distance calculation
    def calc_distance (self, a, b):
        return min (math.sqrt ((a.x-b.x) ** 2 + (a.y-b.y) ** 2),
                   math.sqrt ((a.inverse_x-b.x) ** 2 + (a.inverse_y-b.y) ** 2))
    # Alignment
    def alignment (self):
        if len (self.neighbors) == 0:
            return
        self.v3.x = mean ([agent.x for agent in self.neighbors if not self.identifier == agent.identifier])
        self.v3.y = mean ([agent.y for agent in self.neighbors if not self.identifier == agent.identifier])
        self.v3.x = (self.v3.x-self.vx)/2
        self.v3.y = (self.v3.y-self.vy)/2
    def _collision_detection (self):
        if self.x-self.field_info [2]<= 0:
            self.vx * = -1
            self.x = self.field_info [2]
        if self.x + self.field_info [2]>= self.field_info [0]:
            self.vx * = -1
            self.x = self.field_info [0]-self.field_info [2]
        if self.y-self.field_info [2]<= 0:
            self.vy * = -1
            self.y = self.field_info [2]
        if self.y + self.field_info [2]>= self.field_info [1]:
            self.vy * = -1
            self.y = self.field_info [1]-self.field_info [2]
    def step (self):
        self.cohesion ()
        self.separation ()
        self.alignment ()
    def find_neighbors (self):
        self.neighbors = (neighbor for neighbor in self.others
                          if self.calc_distance (self, neighbor)<= self.view andnot self.identifier == neighbor.identifier]
    def update (self):
        self.vx + = self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
        self.vy + = self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
        distance = math.sqrt (self.vx ** 2 + self.vy ** 2)
        if distance>self.field_info [3]:
            self.vx = (self.vx/distance) * self.field_info [3]
            self.vy = (self.vy/distance) * self.field_info [3]
        self.x + = int (self.vx)
        self.y + = int (self.vy)
        self._collision_detection ()
        print (self.vx, self.vy)
        self.inverse_x = self.field_info [0]-self.x
        self.inverse_y = self.field_info [1]-self.y
    def clear_movement (self):
        self.v1.reset ()
        self.v2.reset ()
        self.v3.reset ()
    def draw (self):
        self.clear_movement ()
        self.step ()
        self.update ()
        return self.x, self.y, self.vx, self.vy
root = Tk ()
WIDTH = 1000
HEIGHT = 1000
fish_radian = 10
bird_num = 3
SPEED = 5
VIEW = 50
c = Canvas (root, width = WIDTH, height = HEIGHT)
c.pack ()

fish_list = (Bird (random.randint (0, WIDTH), random.randint (0, HEIGHT), random.randint (-SPEED, SPEED),
                  random.randint (-SPEED, SPEED), i, (WIDTH, HEIGHT, fish_radian, SPEED), [],

 VIEW) for i in range (bird_num)]
[fish.set_others (fish_list) for fish in fish_list]

def animate ():
    c.delete ("all")
    for fish in fish_list:
        fish.draw ()
        c.create_oval (fish.x-fish_radian, fish.y-fish_radian, fish.x + fish_radian, fish.y + fish_radian)
        c.create_line (fish.x, fish.y, fish.x + fish.vx * 3, fish.y + fish.vy * 3)
    [fish.set_others (fish_list) for fish in fish_list]
    root.after (20, animate)
animate ()
root.mainloop ()

I tried changing various conditional expressions.
In almost all conditions, it converged to the lower right of the window as well.

Supplemental information (FW/tool version etc.)

MacOS Catalina
Python 3.6.8

  • Answer # 1

    self.vx + = self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
            self.vy + = self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y

    I think that self.vx and self.vy will change more and more quickly. Is this the intended behavior?

    I tried refactoring to understand the source.
    I tried to fix the problem part.

    import random
    from statistics import mean
    from tkinter import *
    class Field:
        WIDTH = 300
        HEIGHT = 200
    class Coordinate ():
        def __init __ (self):
            self.reset ()
        def reset (self):
            self.x = 0
            self.y = 0
    class Bird ():
        NUM = 3
        RADIAN = 10
        SPEED = 5
        VIEW = 50
        @staticmethod
        def setup ():
            Bird.birds = [Bird () for _ in range (Bird.NUM)]
        def __init __ (self):
            self.x = random.randint (0, Field.WIDTH)
            self.y = random.randint (0, Field.HEIGHT)
            self.vx = random.randint (-self.SPEED, self.SPEED)
            self.vy = random.randint (-self.SPEED, self.SPEED)
            self.view = self.VIEW
            self.inverse_x = Field.WIDTH-self.x
            self.inverse_y = Field.HEIGHT-self.y
            self.r1 = 2.0 # cohesion coefficient
            self.r2 = 0.5 # separation coefficient
            self.r3 = 1.0 # alignment coefficient
            self.v1 = Coordinate ()
            self.v2 = Coordinate ()
            self.v3 = Coordinate ()
        def neighbors (self):
            return [agent for agent in self.birds if agent! = self]
        # Join
        def cohesion (self):neighbors = self.neighbors ()
            if not neighbors:
                return
            center_pull_coeff = 10
            self.v1.x = mean ([agent.x for agent in neighbors])
            self.v1.y = mean ([agent.y for agent in neighbors])
            self.v1.x = (self.v1.x-self.x)/center_pull_coeff
            self.v1.y = (self.v1.y-self.y)/center_pull_coeff
        # Withdrawal
        def separation (self):
            personal_space = 50
            for agent in self.neighbors ():
                dist = self.calc_distance (agent)
                if dist and dist<= personal_space:
                    self.v2.x-= (agent.x-self.x)/dist
                    self.v2.y-= (agent.y-self.y)/dist
        def _separation (self):
            neighbors = self.neighbors ()
            if not neighbors:
                return
            self.v2.x = mean ([agent.x for agent in neighbords])-self.x
            self.v2.y = mean ([agent.y for agent in neighbords])-self.y
        # Distance calculation
        def calc_distance (self, target):
            return ((target.x-self.x) ** 2 + (target.y-self.y) ** 2) ** 0.5
        # Alignment
        def alignment (self):
            neighbors = self.neighbors ()
            if not neighbors:
                return
            self.v3.x = mean ([agent.x for agent in neighbors])
            self.v3.y = mean ([agent.y for agent in neighbors])
            self.v3.x = (self.v3.x-self.vx)/2
            self.v3.y = (self.v3.y-self.vy)/2
        def _collision_detection (self):
            if self.x-self.RADIAN<0:
                self.vx * = -1
                self.x = self.RADIAN
            if self.x + self.RADIAN>Field.WIDTH:
                self.vx * = -1
                self.x = Field.WIDTH-self.RADIAN
            if self.y-self.RADIAN<0:
                self.vy * = -1
                self.y = self.RADIAN
            if self.y + self.RADIAN>Field.HEIGHT:
                self.vy * = -1self.y = Field.HEIGHT-self.RADIAN
        def step (self):
            self.cohesion ()
            self.separation ()
            self.alignment ()
        def update (self):
            dx = self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
            dy = self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
            self.vx + = dx if self.vx>= 0 else -dx
            self.vy + = dy if self.vy>= 0 else -dy
            distance = (self.vx ** 2 + self.vy ** 2) ** 0.5
            if distance>self.SPEED:
                self.vx = (self.vx/distance) * self.SPEED
                self.vy = (self.vy/distance) * self.SPEED
            self.x + = int (self.vx)
            self.y + = int (self.vy)
            self._collision_detection ()
            print (self.vx, self.vy)
            self.inverse_x = Field.WIDTH-self.x
            self.inverse_y = Field.HEIGHT-self.y
        def clear_movement (self):
            self.v1.reset ()
            self.v2.reset ()
            self.v3.reset ()
        def draw (self, drawer):
            self.clear_movement ()
            self.step ()
            self.update ()
            drawer.create_oval (self.x-self.RADIAN, self.y-self.RADIAN, self.x + self.RADIAN, self.y + self.RADIAN)
            drawer.create_line (self.x, self.y, self.x + self.vx * 3, self.y + self.vy * 3)
    def main ():
        Bird.setup ()
        root = Tk ()
        canvas = Canvas (root, width = Field.WIDTH, height = Field.HEIGHT)
        canvas.pack ()
        def animate ():
            canvas.delete ("all")
            for bird in Bird.birds:
                bird.draw (canvas)
            root.after (20, animate)
        animate ()
        root.mainloop ()
    if __name__ == '__main__':
        main ()

Related articles