パソコンで動かせる人工生命

[ユニークビジョン株式会社 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/uniquevision) 12月17日の記事です。

---

そういえば3年ほど積んでいる本で『作って動かすALife』というのがあったんだったと思い出したので、遊んでみます。

![ALife 表紙.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/63a298de-0291-9a60-1125-8fa4dfc92526.jpeg)

https://www.oreilly.co.jp/books/9784873118475/

ずっと読まずに積んでいた本ですが、少し思い入れのある本です。
 おそらく、初めて買った技術書です。当時プログラミングをまったくやったことのなかった私は環境構築でつまづいて、この本を押入れの奥底にしまったのでした。

 ## ALifeとは

 Artificial Life(人工生命)の略です。ALife(エーライフ)と読みます。

 コンピュータを使って生命をシミュレートし、そこから生命とはなにかを探る分野です。

 ## やってみる

 本書で使用するプログラムはすべてGitHubで公開されています。

 https://github.com/alifelab/alife_book_src

 これに従って実際にALifeを動かしてみます。

 ### Gray-Scott モデル

 ```
$ cd chap02
$ python gray_scott.py
 ```

 これを実行するとビジュアライザが開き、以下のような模様が描画されます

 ![ScSho 2024-12-29 19.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/905733d4-dfe6-4914-c7b7-ca6bb91e99c8.png)

![ScSho 2024-12-29 19.08.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/6e7201a6-1896-1ac6-da8d-ecafa14436c2.png)

![ScSho 2024-12-29 19.09.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/228d56ff-abed-9f7f-89b8-e1fc182350b7.png)

はじめ、中心に花のような模様が描画され、それが次第に枝を伸ばすように広がっていきます。

Gray-Scott モデルは単純な化学反応をモデル化したものです。「反応拡散系」と呼ばれる化学反応のひとつです

![ScSho 2024-12-29 19.18.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/f47035b3-4de6-9508-f3e8-52f77e93db69.png)
(https://www.karlsims.com/rd.html より)

これを二次元のグリッドで表現したものが先ほどのパターンです。
グリッドの各マスは物質a,bの濃度を持っています。


さて、このパターンを描画しているプログラムを見ていきます。

```py
SPACE_GRID_SIZE = 256
dx = 0.01
dt = 1
VISUALIZATION_STEP = 8  # 何ステップごとに画面を更新するか。
```

まず、初期条件を決めます。
SPACE_GRID_SIZE はシミュレーション空間のグリッド数です。
dx はグリッド1目盛りに対するモデル空間の長さです。小さければ細かくシミュレーションでき、大きければ粗いシミュレーションになります。
dtはdx同様、シミュレーション1ステップごとのモデルのステップ数です。

```py
Da = 2e-5
Db = 1e-5
f, k = 0.04, 0.06
```

さらに定数を設定します。
Da, Db は物質a,bの拡散の速度です。先ほどの化学反応の図の2つの物質にあたります。
そしてこのモデルで最も重要な定数f,kはfeedとkillを示し、物質aの補充と物質bの減量に関わります。

```py
# 初期化
a = np.ones((SPACE_GRID_SIZE, SPACE_GRID_SIZE))
b = np.zeros((SPACE_GRID_SIZE, SPACE_GRID_SIZE))
# 中央にSQUARE_SIZE四方の正方形を置く
SQUARE_SIZE = 20
a[SPACE_GRID_SIZE//2-SQUARE_SIZE//2:SPACE_GRID_SIZE//2+SQUARE_SIZE//2,
  SPACE_GRID_SIZE//2-SQUARE_SIZE//2:SPACE_GRID_SIZE//2+SQUARE_SIZE//2] = 0.5
b[SPACE_GRID_SIZE//2-SQUARE_SIZE//2:SPACE_GRID_SIZE//2+SQUARE_SIZE//2,
  SPACE_GRID_SIZE//2-SQUARE_SIZE//2:SPACE_GRID_SIZE//2+SQUARE_SIZE//2] = 0.25
# 対称性を壊すために、少しノイズを入れる
a += np.random.rand(SPACE_GRID_SIZE, SPACE_GRID_SIZE)*0.1
b += np.random.rand(SPACE_GRID_SIZE, SPACE_GRID_SIZE)*0.1
```

コメントの通りですが、初期条件として中央に正方形を置き、そこから反応・拡散する様子を見ていきます

```py
def update(frame):
    global a, b
    for i in range(VISUALIZATION_STEP):
        # ラプラシアンの計算
        laplacian_a = (np.roll(u, 1, axis=0) + np.roll(u, -1, axis=0) +
                       np.roll(u, 1, axis=1) + np.roll(u, -1, axis=1) - 4*u) / (dx*dx)
        laplacian_b = (np.roll(v, 1, axis=0) + np.roll(v, -1, axis=0) +
                       np.roll(v, 1, axis=1) + np.roll(v, -1, axis=1) - 4*v) / (dx*dx)
        # Gray-Scottモデル方程式
        dudt = Du*laplacian_u - a*b*b + f*(1.0-a)
        dvdt = Dv*laplacian_v + a*b*b - (f+k)*b
        a += dt * dadt
        b += dt * dbdt
```

ここがGray-Scott モデルの本質です。
コメントにあるGray-Scottモデル方程式をまず確認します

![ScSho 2024-12-29 21.50.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/ace0969e-c87d-1b58-e93a-68c07c0343e7.png)
(https://www.karlsims.com/rd.html より)

プログラムでは第1項は省略されています。
第2項は拡散を表しています。先ほど設定した拡散係数とラプラシアンを掛け算しています。
第3項は化学反応です。物質Aが失われて物質Bが生成される反応です。
第4項はAの補充とBの減量です。上式では1-Aとなっていることからわかるように、Aが増えると補充される量が減り、減ると補充量は増えるような、バランスをとっています。下式ではBに比例していることからBは増えすぎると減量される量も増えるようになっています。

AとBは、拡散しつつ、反応により増減し、さらに全体量に応じて補充・減量される、というモデルです。

### アレンジ

Gray-Scott モデルのロジックはこれで以上です。
このモデルのおもしろいところは先ほど設定した係数を少し変えるだけで振る舞いを大きく変えるところです

```py
f, k = 0.04, 0.06  # amorphous
```

aの補充とbの減量を司るこの係数をいくつか変えてみましょう。
先ほどの例では`amorphous`でした

```py
f, k = 0.035, 0.065  # spots
```

シンプルな斑点です。

![ScSho 2024-12-29 22.16.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/22584464-275e-fd55-090a-0e374db7b296.png)

```py
f, k = 0.012, 0.05  # wandering bubbles
```

動かすとわかりますが、泡が生まれては弾け、見たいな動きをしています。

![ScSho 2024-12-29 22.17.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/e844b11a-4d90-1a28-ab2a-86b996c08213.png)

```py
f, k = 0.025, 0.05  # waves
```

わかりにくいですが、中心から波が出ているように見えなくもないです。

![ScSho 2024-12-29 22.20.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/561bfe29-09ad-f0c3-dde9-68d662f6271f.png)

```py
f, k = 0.022, 0.051 # stripe
```

縞...っぽい。

![ScSho 2024-12-29 22.24.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841668/bf4018bf-ecdc-4fe2-650e-bcaa396c0973.png)

aの補充とbの減量のパラメータを少しいじるだけで全く異なるパターンが描き出されます。

## まとめ

読んでみると、コンピュータサイエンスの方面というより、生物・化学・数学方面の欲が満たされるような本でした。

実装せずともシミュレーションは動くし、軽くコードを確認するくらいで読み進めていけるので手軽でいい本です。