Agent Grow Advent Calendar 2018 21日目は私、江嵜が担当です!
最初にお礼と、今回の方針
皆さんこんにちは!
今回は皆さんに、お礼を言わなければならないことがあります。
それは、去年の私のアドベントカレンダー記事のことなのですが…
なんと!Google 検索で「excel シミュレーション」で検索トップになってます!(2018 年 12 月現在)
そもそも Excel でシミュレーションという事自体がニッチですけど、
結構多くの方に読んでいただいたのではなかろうか…と思うと胸が熱くなりますね。
(タイトルにパンチがあったから開いたダケ、みたいな方もそこそこいたんじゃないかと思いますが。)
…なのですが、多分シミュレーションと聞いて開いた方にはこんなことを感じられた方もいらっしゃったのではないでしょうか。
「いや、シミュレーションと言うなら動かせよ!なんかパラパラ漫画作ってるけど、ごまかしてるだろ!」
ハイ、そうです。アレもシミュレーションなのですが、見た目があんまりおもしろくないですよね。
ということで、今回はその反省を生かしまして、きちんと動くものを用意いたしました!
さすがに動くものをとなるとちょっとプログラム書かないといけないのですが、コピペで出来るので皆さんも試していただきたいところです!
今回シミュレーションする題材はこちら!
心臓の鼓動をシミュレーションしてみよう
ちょっとレベル高そう…難しそう…と思いましたか?
今回はかなりシンプルな考え方でのシミュレーションなので大丈夫ですよ。
むしろ前回のような数式は出てこないので、前回より簡単かもしれないですね。
(ちなみに今回はシミュレーションのお話がメインなので、以下で扱う心臓の動きのルールは、かなり簡略的なものです。
あくまでシミュレーションのお話として読んでくださいね。)
シミュレーションのルールを決めよう
まず最初に、実際のシミュレーションを始める前にやらなければならないことがあります。
それはシミュレーションのルール決めですね!
シミュレーションとは、そもそもモノの動きを、本物を使わずに再現することですね。
ということで、例えば車の動きをシミュレーションするのであれば、
「ハンドルを右に回すと → 車が右に曲がる」といった、本物ならどうなるかというルールを決めることが必要です。
このルールが正しく・細かい物であるほど、本物に似た動きのシミュレーションができるわけですね!
正直、「シミュレーション = ルール決め」と言って差し支えないほど、ルール決めはシミュレーションのキモです。
ちょっと複雑ですけど、ここを乗り越えたら後は簡単なのでちょっと頑張ってくださいね。
そもそも心臓ってどうやって鼓動しているんだ?
心臓のシミュレーションのルールを決めるにあたって、知っておかなければならないことは、
「そもそも心臓はどうやって鼓動しているのだろうか?」ということです。
ここではとってもカンタンに説明しますね。
まず、心臓はそのほとんどが筋肉(心筋)で出来ています。
この筋肉がそれぞれ伸びたり、縮んだりを繰り返して鼓動をし、全身に血液を送っているのですね。
ここで注意しなければならないのは、「心臓の筋肉はそれぞれバラバラに動いているのだろうか?」ということです。
もちろん、この答えは No です。
なぜなら、バラバラに動いていれば、ある筋肉は縮んで心臓から血液を送り出そうとしているのに、隣の筋肉は伸びてカラダから血液を受け取ろうとしている、みたいな状態になってしまいます。
これでは血液をきちんとカラダに送り出すことはできないですよね。
(ちなみに、後で出てきますが、このバラバラに動く状態が、いわゆる「心室細動」と言われるものですね。)
心臓の筋肉は一斉に伸び縮みする必要がある、という事が分かりました。
では次に、どうやって筋肉は一斉に動くのでしょうか?
脳が「縮め!」「伸びろ!」と命令しているのでしょうか?
いやいや、心臓は生物が生きている間、常に動いていなければならないのです。
常に命令し続けるのはちょっとツライですよね。
実はここに、今回のシミュレーションのポイントがあるのです。
心臓は、隣の筋肉の動きに合わせて動いている!
実は心臓には、リズミカルに命令を電気信号として出す器官があります。
そこから電気信号を受け取った筋肉が縮むのですね。
しかし、すべての筋肉に同時に電気信号を送れるわけではないのです。
では、どのようにして全体に信号を送っているのかというと、
– 最初、心臓の、ある一点がその電気信号を受け取って、筋肉が縮む
– 筋肉が縮みながら、その信号を隣の細胞に伝えて、隣の筋肉も縮む
– さらに隣に伝えて…
と、リレーのように信号を伝えていきます。
実際に信号が伝わる速度は筋肉が伸び・縮みするのにかかる時間に比べてとても速いので、
一瞬で信号が心臓全体に伝わり、ほぼ同時に伸び・縮みできるようになる、ということなのです。
「縮む信号を隣に伝える」。
そうです、これこそが心臓の鼓動のルールであり、今回のシミュレーションのルールです!
シミュレーションのルールを整理しよう
とりあえず今回のシミュレーションの大まかなルールは決まりました。
「信号を受け取ったら、それを隣に伝える」ということですね。
ではここで、実際のシミュレーションに利用できるルールにするため、もうすこし細かいところまで決めていきましょう。
シミュレーションを実施する環境を決める
まず、シミュレーションを実施する前に、どんな環境でシミュレーションをするかを決めなければなりません。
本気でやるならば、心臓の形の 3D モデルを用意して立体的に実施する必要があるのですが、
今回はそこまで細かいことをするわけではないので、平面でやってみましょう。
信号の伝わり方を見たいだけなので、平面でも十分ですね。
と、ここで…
秘技!エクセル方眼紙!
エクセルは最初からマス目があるので、
こんな感じで、マス目を正方形にして、この一つずつのマスをそれぞれの心臓の細胞だと見立てれば、
これだけでシミュレーションの舞台が用意できましたね!
エクセルはシミュレーションソフトではないのに、こんなに簡単に環境が用意できるなんて、
やっぱり神ですね。
ちなみに、このように細かい区切りを作って、区切りごとに色々な動きをさせるシミュレーションを、
セル・オートマトンと言います。
とても有名なシミュレーションの 1 手法なので、覚えておきましょう。
信号が伝わるルールを決める1
さて、環境が決まったので、ここから伝わり方のルールを決めましょう。
大原則は「信号を受け取ったら、それを隣に伝える」ですね。
では、この「隣」とはどこのことでしょうか。
今回のような方眼紙の場合は、隣の取り方は 2 パターンが考えられますね。
こんな感じ。(赤色が中心で、その隣に当たる部分が青色ですよ!)
パターン1 だと隣は上下左右の 4 つだけですね。
パターン2 は四隅も増えて、 8 つになります。
どちらも隣りであることに変わりはないので、どちらのルールを使ってもよいです。
今回はパターン2 の方法で行きましょう。
信号が伝わるルールを決める1
もう一つ、それぞれの細胞が、どんな状態になり得るかを決めなければなりません。
これまでの話で、細胞の状態は少なくとも
- 電気信号を受け取っていなくて、伸びている(休止期)
- 電気信号を受け取って、縮んでいる(興奮期)
以上の 2 つの状態があることが分かりますね。
(少し難しいですが、説明を簡単に書くために、休止期・興奮期という言葉を使いますね)
しかし、この 2 つの状態だけでうまくシミュレーションができるでしょうか。
まず、休止期を白、興奮期を赤として、
単純に「信号を受け取ったらは隣に伝える」とだけ決めてみます。すると何が起こるかというと…
1 ~ 5 の順に時間が進んでいくと考えます。
1 → 2 はそのまま隣に信号が伝わっているだけですね。
2 → 3 を見ると、2 で興奮期の両側に休止期があるので、3 で信号が両側に伝わってしまっています。
さらに、 3 → 4 でも、3 の興奮期の両側が休止期なので、4 ではまた両側に信号をが伝わっています。
結果として、このルールでは、一度興奮期になった細胞は
興奮期と休止期を交互に繰り返すことになってしまいました。
ところで、元々やりたかったのは、リレーのように順番に信号を伝えることでしたね。
興奮期と休止期を繰り返してしまうモデルでは、ちょっと困ります。
ルールをどのように修正したらよいでしょうか。
こんな感じで…
- 興奮期が終わったばかりで、周りから信号を受け取らない(不応期)
グレーで、信号を受け取らない不応期というものを追加してみました。
こうすると興奮期からすぐに休止期に戻らないので、最初に考えていたように
信号が一方向に流れるルールになりましたね!
これを踏まえて、心臓のそれぞれの細胞は
- 電気信号を受け取っていなくて、筋肉が伸びている(休止期)
- 電気信号を受け取って、筋肉が縮んでいる(興奮期)
- 興奮期が終わったばかりで、周りから信号を受け取らない(不応期)
の 3 つの状態のうち、どれかの状態となる、というルールにしましょう。
シミュレーションしてみよう!
長かったですね!お疲れ様です!
これでシミュレーションの準備は整ったので、実際に Excel でシミュレーションしてみましょう。
今回は動く必要があるので、 VBA という、エクセルで使えるプログラムを書いてみました。
(ちょっと長いので、読む必要はないですよ)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'—– 定数 —– | |
'計算回数 | |
Const LOOP_TIMES As Integer = 50 | |
'休止期状態時色 | |
Const DEFAULT_COLOR As Integer = 2 | |
'興奮期状態時色 | |
Const ACTIVE_COLOR As Integer = 3 | |
'不応期状態時色 | |
Const REFRACTORY_COLOR As Integer = 16 | |
'阻害物色 | |
Const BLOCK_COLOR As Integer = 1 | |
'開始行数 | |
Const MIN_ROW As Integer = 5 | |
'開始列数 | |
Const MIN_COLUMN As Integer = 5 | |
'終了行数数 | |
Const MAX_ROW As Integer = 40 | |
'終了列数 | |
Const MAX_COLUMN As Integer = 40 | |
'ムーア近傍を使用するか(False の場合ノイマン近傍を使用) | |
Const IS_MOORE = True | |
'—– 関数 —– | |
'対象セルの色を取得 | |
Function colorPick(ByVal row As Integer, ByVal column As Integer) As Integer | |
colorPick = Cells(row, column).Interior.colorIndex | |
End Function | |
'対象セルに色を設定 | |
Sub drawCell(ByVal row As Integer, ByVal column As Integer, ByVal color As Integer) | |
Cells(row, column).Interior.colorIndex = color | |
End Sub | |
'現在のセルの色を二次元配列として取得 | |
Function getCullentColorArray() As Integer() | |
Dim arr(MIN_ROW To MAX_ROW, MIN_COLUMN To MAX_COLUMN) As Integer | |
For i = MIN_ROW To MAX_ROW | |
For j = MIN_COLUMN To MAX_COLUMN | |
arr(i, j) = colorPick(i, j) | |
Next j | |
Next i | |
getCullentColorArray = arr() | |
End Function | |
'セルの色を次の時間へ進める | |
Sub nextTick(ByRef curArr() As Integer) | |
Dim colorIndex As Integer, nextArr(MIN_ROW To MAX_ROW, MIN_COLUMN To MAX_COLUMN) As Integer | |
For i = MIN_ROW To MAX_ROW | |
For j = MIN_COLUMN To MAX_COLUMN | |
colorIndex = curArr(i, j) | |
'興奮気なら次の時間は不応期へ | |
If colorIndex = ACTIVE_COLOR Then | |
nextArr(i, j) = REFRACTORY_COLOR | |
'不応期なら次の時間は休止期へ | |
ElseIf colorIndex = REFRACTORY_COLOR Then | |
nextArr(i, j) = DEFAULT_COLOR | |
'阻害物なら変化なし | |
ElseIf colorIndex = BLOCK_COLOR Then | |
nextArr(i, j) = BLOCK_COLOR | |
'休止期の次の時間を設定する | |
Else | |
'一旦休止期として設定 | |
nextArr(i, j) = DEFAULT_COLOR | |
'左隣が興奮期なら興奮期へ | |
If i > MIN_ROW Then | |
If curArr(i – 1, j) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
'右隣が興奮期なら興奮期へ | |
If i < MAX_ROW Then | |
If curArr(i + 1, j) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
If j > MIN_COLUMN Then | |
'上が興奮期なら興奮期へ | |
If curArr(i, j – 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
'ムーア近傍 | |
If IS_MOORE Then | |
'左上が興奮期なら興奮期へ | |
If i > MIN_ROW Then | |
If curArr(i – 1, j – 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
'右上が興奮期なら興奮期へ | |
If i < MAX_ROW Then | |
If curArr(i + 1, j – 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
End If | |
End If | |
If j < MAX_COLUMN Then | |
'下が興奮期なら興奮期へ | |
If curArr(i, j + 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
'ムーア近傍 | |
If IS_MOORE Then | |
'左下が興奮期なら興奮期へ | |
If i > MIN_ROW Then | |
If curArr(i – 1, j + 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
'右下が興奮期なら興奮期へ | |
If i < MAX_ROW Then | |
If curArr(i + 1, j + 1) = ACTIVE_COLOR Then | |
nextArr(i, j) = ACTIVE_COLOR | |
End If | |
End If | |
End If | |
End If | |
End If | |
Next j | |
Next i | |
'計算した次の時間の状態をもとに色を塗る | |
For i = MIN_ROW To MAX_ROW | |
For j = MIN_COLUMN To MAX_COLUMN | |
Call drawCell(i, j, nextArr(i, j)) | |
Next j | |
Next i | |
End Sub | |
'メイン関数 | |
Sub main() | |
'開始時の設定 | |
Application.Calculation = xlCalculationManual | |
'実際にセルを更新する作業 | |
For i = 1 To LOOP_TIMES | |
DoEvents 'ハングアップしないように操作を受け付ける | |
Application.ScreenUpdating = False '一度に色を更新させるために一旦画面更新を停止 | |
Application.StatusBar = "シミュレーション中…" & i & "/" & LOOP_TIMES | |
Dim l As Integer, curArr() As Integer | |
curArr() = getCullentColorArray() | |
Call nextTick(curArr()) | |
Application.ScreenUpdating = True '色を更新させるために画面更新を実施 | |
Next i | |
'設定復帰処理 | |
Application.Calculation = xlCalculationAutomatic | |
Application.StatusBar = False | |
End Sub |
では、このプログラムを使って実際にシミュレーションしてみましょう。
手元で動かす場合は、以下の手順で操作してくださいね。
- Excel を起動します
- 「空白のブック」を作成し、セルを正方形にします
(やり方が分からない場合は「excel 方眼紙」で調べてくださいね) - 今回のプログラムでは E5 ~ AN40 の正方形の中でシミュレーションをします
わかりやすいように、この範囲を選択して、枠線を引いておいてください - 上の範囲の中のセルのどこかに、適当に 1 点だけ背景色を赤色にします
(これが最初の興奮期になります。赤色は「標準の色」の中にあります) - 「開発」タブ Visual Basic を選択します
(「開発」タブが表示されていない場合は、「Excel 開発」で調べて、表示させてください)
- 左側の Sheet1(Sheet1) をダブルクリックし、右側に表示された記入スペースに先ほどのプログラムを貼り付けます
- 右上のプルダウンメニューに「main」と書かれていることを確認します
もし「main」以外になっている場合は、main を選択してください - 上のツールバーから緑色の三角形のボタンを押して、元の Excel の画面を見てみます
すると…
1 点の興奮期から周りに信号が伝播して、広がっていく様子が見れましたね!
プログラムが読める方はこちらを見ていただくと分かるのですが、
実際にやっていることとしては、全てのマス目について
- 現在の色を見て、状態を確認する
- 次の状態を決める
- 現在が興奮期 → 次は不応期になる
- 現在が不応期 → 次は休止期になる
- 現在が休止期 → 周りに興奮期があれば次は興奮期になる・なければ休止期のまま
- 決まった状態に従って色を塗る
たったこれだけの作業をしているだけなんですね。
つまり、誰かが全体を管理することなく、それぞれのマス目が自分と周りの状態だけを見て
次の自分の状態を決定する、という事を繰り返しているだけなのです。
これが、心臓が全体を誰かに管理されなくても一斉に伸び・縮みを繰り返すことができるメカニズム、なのですね!
ちなみに…
ちなみに、この動き、アレに似てますよね。
そう、波です。
今回のモデルでは壁にぶつかったときに反射しないのでちょっと波とは性質が違いますが、
障害物を置くとちょっと似た動きになります
(今回は黒を塗るとそこが障害物になるように設定してみました)
広がり方がカクカクで波っぽくないですか?
でしたら、少し前に決めた「隣」の定義を、上下左右だけにしてみるとちょっとそれっぽくなりますよ。
(ちなみに隣の定義の切り替えは、プログラムの上の方にある IS_MOORE を
True にしておくか、 False にしておくかで変えられますよ。
True だと 8 方向、 False だと 4 方向です)
すこし発展的なシミュレーションをしてみる(鼓動を再現する)
ここまでで心臓の伸び・縮みの命令が伝わる仕組みはなんとなくわかったかと思いますが、
折角なのでもっと鼓動っぽく、一定時間おきに伸び縮みするようにしたいですよね。
先程、一か所だけ興奮期の箇所があれば、その信号は周りに伝わっていくことが分かったので、
今回は、ある一か所だけが定期的に興奮期になるようにしてみましょう。
一応先ほどと同じ場所に、cellular_automaton2.vba としてプログラムをアップロードしていますが、
実際変わったのはこれだけ
'実際にセルを更新する作業 For i = 1 To LOOP_TIMES DoEvents 'ハングアップしないように操作を受け付ける Application.ScreenUpdating = False '一度に色を更新させるために一旦画面更新を停止 Application.StatusBar = "シミュレーション中…" & i & "/" & LOOP_TIMES '----ここから---- '10 回に一度だけ一か所を興奮期にする If i Mod 10 = 1 Then Call drawCell(10, 10, ACTIVE_COLOR) End If '----ここまで---- Dim l As Integer, curArr() As Integer curArr() = getCullentColorArray() Call nextTick(curArr()) Application.ScreenUpdating = True '色を更新させるために画面更新を実施 Next i
main の中に 3 行だけ足しました。
これで 10 回に 1 回だけ、ある一点が興奮期になります。
動かしてみると…
定期的に信号が伝わるようになりましたね!
本来の心臓のモデルとして考えれば、本当ならば信号が全体にいきわたってから、
少し間を開けて次の信号が出なければいけませんが、
(そうでなければ心臓が一度縮んだ後、十分に伸びることが出来ないので、血液があまり心臓に帰ってこれないですね!)
今回は見た目が分かりやすいようにという事で、興奮期になる頻度を高めにしてみました。
すこし発展的なシミュレーションをしてみる(心室細動を再現する)
さて、ここまで心臓のシミュレーションを作ってみましたが、
これが何の役に立つのかといわれると、特に何の役にも立たなさそうですか?
それに、思っていたほどカッコイイようなシミュレーションではなかったかもしれないですね。
しかし、このシミュレーションの素晴らしいところは、心室細動という現象も模擬的に再現できるところなのです。
上の方で少し出てきましたが、改めて心室細動を簡単にご説明しますと、
ある拍子に心臓の筋肉の伸び・縮みが一定でなく、細かくバラバラに動いてしまう(これが細動ですね)ようになることであり、
こうなると、心臓はうまく血液を送れないようになってしまいます。
最近では AED (自動体外式除細動器) が有名になり、色々なところに設置されるようになりましたが、
これは日本語名にあるように、心臓の細動を、体外からショックを与えて取り除く機械ですね。
少し踏み込んだ話をすると、除細動とは細動をなくして元の鼓動に戻すわけではなく、
細動を取り除いて心臓を 止める ものだそうです。
(以前消防署の講習会でそんな話を聞きました)
よって、必ず AED を使った後は心臓マッサージをして、正常な鼓動に戻してあげる必要があります。
さて、心室細動がなんとなくわかったところで、シミュレーションしてみましょう。
先ほどの正常に鼓動していた状態にちょっと手を加えて…
一か所波を切ってみました。
さて、この状態からシミュレーションを再開させると、どうなるでしょうか…?
答えはこちら。
なんということでしょう!
通常の鼓動の何倍ものスピードで縮む命令が何度も流れるようになったことが分かりますね。
しかも、先程設定していた振動の開始地点も、数秒でこの新しい信号に飲み込まれてしまいました。
これだけの頻度で縮む命令を受け取ってしまうと、心臓の筋肉には縮んてから伸びる余裕が全くありませんから、
縮みっぱなしで血液が送れない状態になってしまう、という事が分かるかと思います。
また、なぜ心室細動が発生した際には AED が必要なのかもこのシミュレーションから分かりますね。
これだけメチャクチャに信号が流れてきている状態で心臓マッサージだけをしても、
元の周期に戻すことはできなさそうです。
やはり、 AED でこの心臓の動きを一旦止めて、改めて正しいリズムを刻めるように心臓マッサージをする必要がありそうですね。
まとめ
いかがでしたでしょうか。
分量も多くて読むのも大変だったかと思います。
お疲れ様でした。
ここまで読まれた皆さんにはもうお分かりの事かと思いますが、
今回のまとめをすると、
- 細かい区切りごとに動きを見るシミュレーションを、セルオートマトンという
- 一件複雑そうなことも、簡単なルールで(ある程度)シミュレーションできる
- シミュレーションはルール決めが 8 割
- 心室細動は怖いから AEDの使い方や心臓マッサージの練習はやっておいた方がいいよね
(各地の消防署で定期的に講座が開かれてますのでゼヒ) - なんでもシミュレーションできる Excel は 2 年連続で神だった
という事です。特に最後のは重要ですからね覚えておいてくださいね。
モデルさえできれば以外に簡単にできてしまうシミュレーションの世界。
皆さんも飛び込んでみてはいかがでしょうか。
21 日担当の江嵜からは以上です!