シンギュラリティ実験ノート

購入した1000ドルPCで何がどこまでできるのか実験した記録です。

Boidsモデルのシミュレーションプログラムと格闘した話

Boidsとは鳥の群れの動きをシミュレーションする人口生命プログラムである。Boidsは鳥のようなもの(Bird-oid)という意味からきている。下記の3つのシンプルな鳥の振る舞いをプログラムで定義することで、生きているような鳥の群れの動きをコンピュータ上で表現できる。

  • 分離:各Boidsは過剰な混雑を避けるために群れの仲間から距離を置く。
  • 連帯:各Boidsは群れの仲間全体の平均的な方向に、平均的なスピードで動く。
  • 凝集:各Boidsは群れの仲間全体の平均的な位置に向かって動く。

前回の記事では、GPT-4oが作ってくれたシミュレーションプログラムを動かした。今回は、前回の記事で動かせなかった、70億パラメタのAI(Sakna-AI)の作成したコードを、なんとか正しく動くまで私がPythonと格闘した記録である。

 

 

主な修正箇所

結果的には大幅な改造になった。主な修正点のみを記す。

  • 変数position、velosity、accelerationがタプルとして宣言されているので、+=で演算するとリストに追加されるだけになるため、numpy配列に変更する。
  • screen宣言時に、”pygame.OPENGL | pygame.DOUBLEBUF”は不要。この部分を削除したらドット(Boids)が表示されるようになった。
  • random.randintによる初期値の設定は、np.random.rand()に変更。

これでBoidsが動くようになったが、分離(separation)、連帯(alignment)、凝集(cohesion)のロジックは定義されているものの呼び出されていない。Boidsはそれぞれ勝手に直線的に動くだけだった。

分離、連帯、凝集の関数を呼び出す必要があるが、良く見ると使えそうなロジックではなかった。cohesion関数などは画面の中心に向かって集まるロジックになっている。

ここで一度あきらめかけたが、GPT-4oが作ってくれた動くサンプルがある。動いた実績のあるプログラムを使えば、開発の生産性が2倍になることは現役時代にも経験してきたことだ。分離、連帯、凝集のロジックは、GPT-4oのロジックをそのまま実装することにした。

プログラム完成版

3日間私がPythonと格闘して作成したプログラムが以下である。

 

import pygame
import numpy as np
from pygame.locals import *

WIDTH, HEIGHT = 800, 600
class Bird:
    def __init__(self, x, y, vx, vy):
        self.position = np.array([x, y], dtype='float64')
        self.velocity = np.array([vx, vy], dtype='float64')
        self.acceleration = np.array([0, 0], dtype='float64')
        self.radius = 4
        self.color = (255, 255, 255)

    def update(self):
        self.velocity += self.acceleration
        self.position += self.velocity
        self.acceleration = np.zeros(2)

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, self.position.astype(int), self.radius)

    def edges(self):
        if self.position[0] > WIDTH:
            self.position[0] = 0
        elif self.position[0] < 0:
            self.position[0] = WIDTH
        if self.position[1] > HEIGHT:
            self.position[1] = 0
        elif self.position[1] < 0:
            self.position[1] = HEIGHT

    def separation(self,force):
        self.acceleration += force

    def alignment(self,force):
        self.acceleration += force

    def cohesion(self,force):
        self.acceleration += force

class Boids:
    def __init__(self, num_birds):
        self.num_birds = num_birds
        self.width = WIDTH
        self.height = HEIGHT
        self.birds = [Bird(np.random.rand() * WIDTH, np.random.rand() * HEIGHT, np.random.rand() *2 -1, np.random.rand() *2 -1) for _ in range(num_birds)]
        self.max_speed = 4
        self.max_force = 0.1
    
    def update(self):
        for bird in self.birds:
            bird.update()

    def draw(self, surface):
        for bird in self.birds:
            bird.draw(surface)

    def edges(self):
        for bird in self.birds:
            bird.edges()

    def separation(self):
        perception_radius = 25
        for bird in self.birds:
            other_birds = [b for b in self.birds if b != bird]
            separation_force = np.zeros(2)
            for other_bird in other_birds:
                distance = np.linalg.norm(other_bird.position - bird.position)
                if distance < perception_radius and distance > 0:
                    diff = bird.position - other_bird.position
                    diff /= distance
                    separation_force += diff
                if len(other_birds) > 0:
                    separation_force /= len(other_birds)
                if np.linalg.norm(separation_force) > 0:
                    separation_force = separation_force / np.linalg.norm(separation_force) * self.max_speed
                if np.linalg.norm(separation_force) > self.max_force:
                    separation_force = separation_force / np.linalg.norm(separation_force) * self.max_force
            bird.separation(separation_force)

    def alignment(self):
        perception_radius = 50
        for bird in self.birds:
            neighbors = [b for b in self.birds if np.linalg.norm(bird.position - b.position) < perception_radius]
            if len(neighbors) > 0:
                alignment_force = np.zeros(2)
                avg_velocity = np.zeros(2)
                for neighbor in neighbors:
                    avg_velocity += neighbor.velocity
            avg_velocity /= len(neighbors)
            avg_velocity = avg_velocity / np.linalg.norm(avg_velocity) * self.max_speed
            alignment_force = avg_velocity - bird.velocity
            if np.linalg.norm(alignment_force) > self.max_force:
                alignment_force = alignment_force / np.linalg.norm(alignment_force) * self.max_force
            bird.alignment(alignment_force)

    def cohesion(self):
        perception_radius = 50
        cohesion_force = np.zeros(2)
        for bird in self.birds:
            neighbors = [b for b in self.birds if np.linalg.norm(bird.position - b.position) < perception_radius]
            if len(neighbors) > 0:
                center_of_mass = np.zeros(2)
                for neighbor in neighbors:
                    center_of_mass += neighbor.position
                center_of_mass /= len(neighbors)
                vector_to_com = center_of_mass - bird.position
                if np.linalg.norm(vector_to_com) > 0:
                    vector_to_com = vector_to_com / np.linalg.norm(vector_to_com) * self.max_speed
                cohesion_force = vector_to_com - bird.velocity
                if np.linalg.norm(cohesion_force) > self.max_force:
                    cohesion_force = cohesion_force / np.linalg.norm(cohesion_force) * self.max_force
            bird.cohesion(cohesion_force)

def main(num_birds=50):
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Boids Simulation")
    clock = pygame.time.Clock()
    
    flock = Boids(num_birds)
    while True:
        screen.fill((0, 0, 0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
        
        flock.edges()        
        flock.separation()
        flock.alignment()
        flock.cohesion()
        flock.update()
        flock.draw(screen)
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()

 

動作結果をWindowsの機能で録画した。画面の動画撮影はWindows11ではWindowsの標準機能でできるようになっていた。なお、MP4の動画はアップできないのでGIFに変換してアップしている。

 

 

まとめ

動かないプログラムがあると動かしたくなるのは、私がプログラミングが好きだからだと思う。しかし昔の人間なのでオブジェクト指向的なプログラミングは実は苦手である。今回のPythonのコードはクラスとしてBirdやBoidsを定義したプログラムなのでその辺りはかなり苦労した。作成したプログラムでは、Boidsの数が50くらいまではサクサク動くが、それを超えると動きがどんどん遅くなる。これは私のコーディングテクニックに問題があるのかもしれない。

今回はPythonのコーディングの良い勉強になった。AIが作ってくれたサンプルを見ながらコーディングの勉強をしたことになる。これからはAIがプログラミングを人間に教える時代になっていくのかな~、とそんなことをまた妄想した。