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がプログラミングを人間に教える時代になっていくのかな~、とそんなことをまた妄想した。