ハト派とタカ派の比率が50対50の時のシミュレーションをC言語で書く

現在、「利己的な遺伝子」を読み進めている。読み進めていく内に、面白いシミュレーションが紹介されていた。それをC言語で実現してみる。その前に、簡単に本書やそのシミュレーションについて述べていこうと思う。

利己的な遺伝子 40周年記念版

利己的な遺伝子 40周年記念版

以前、「攻撃」を読んだ。

攻撃―悪の自然誌

攻撃―悪の自然誌

過去に、「攻撃」を参考にして私が書いた記事がある。
nmzfish.hatenablog.com

しかし、「利己的な遺伝子」の中では、「攻撃」の内容に関して、全面的にかつ完全に間違っていると評されている。
「攻撃」の内容を要約すると、「動物は互いの優劣を儀式めいた行為で決め、互いを傷つけない。それは、種の個体数を減らさない工夫であり、そのために動物は進化してきた」というもの。動物は、種の為に利他的に振舞っているとしている。
一方、「利己的な遺伝子」ではこう反論している。「動物は互いの優劣を、互いを傷つけない方法で決める。しかし、それは互いにとって最も利益の高い方法だからである。あくまでも利己的に振舞っているだけ」というものである。
面白い。同じ内容に関して書かれた別著者の本を読み比べたことはある*1。しかし、反対の論理を展開している2冊を読んだのは初めてかもしれない。まだ半分も読み進めていないが、比喩表現が絶妙で、その面でも楽しめている。

Gene.png
By Courtesy: National Human Genome Research Institute - [1] (file), パブリック・ドメイン, Link

利己的な遺伝子」では、13章ある内、5章では安定性について述べられている。安定性とは、自然界における動物種の数のバランスのことである。例えば、ある草を餌とする草食動物がいるとする。すると、その草食動物を餌とする肉食動物もいるだろう。草食動物が増えれば、餌の草が無くなり、肉食動物は増える。しかし、草が減れば、草食動物が減るので肉食動物も減る。自然界では、これらの数がそれ以上増減しない一定の比率で安定している。ここまでで述べたのは、捕食者と被捕食者の場合である。つまり、別種の動物間の関係である。では同種ではどうか。例えば、AとBとCという同種の動物がいるとする。この3者はオスであり、互いにメスや餌を争うライバルである。ある時、AがBと会ったとする。AがBを殺せばライバルが1人減る。しかし、それはCも同じである。Bを放っておけば、Cと殺しあうかもしれない。何もせずとも利益を得られる。AがBを殺すことは一見利益だが、間接的には他のライバルを助けることになる。平和主義の方が利益にも思えるかもしれない。しかし、戦いで被る不利益よりも、得られる利益の方が大きい場合もある。例えば、巨大なハーレムである。戦いに挑むかどうかを決定するために、これらの利益不利益に対して、損得勘定をすればいい。損得勘定、およびどのように戦うかについてを含めて「戦略」とする。「戦略」とは、例えば「相手を攻撃する。反撃してきたら攻撃をやめて逃げる。」というものである。メイナード・スミスは「戦略」について、「進化的に安定な戦略(ESS:Evolutionarily Stable Strategy)」を定義した。これは、その戦略を個体群のメンバーの大多数が採用すれば、他に取って代わられることのない戦略である。ESSの概念を、攻撃に当てはめるために、最も単純な攻撃パターンについて考察する。それが「ハト派」と「タカ派」である。「ハト派」とは、いわゆる儀式めいた行為によって優劣を決め、互いを傷つけないタイプである。彼らの勝敗は、五分五分であるとする。勝つと利益になるが、負けても不利益はない。しかし、儀式めいた行為には時間がかかる。これは損失になり、両者に発生する。一方、「タカ派」とは、徹底的に戦うタイプである。相手に深い傷を与えるまで戦い続ける。勝敗はこちらも同様に五分五分であるとする。しかし、負ければ負傷という大きな不利益を被る。
この2種類の戦略について、どちらが優れているかをシミュレートする。また、「優れた戦略」を評価するために、得点をつける。得点の高い戦略の方が優れているとする。本記事では、「利己的な遺伝子」において採用された配点を利用する。それは以下のようなものだ。

  • 勝てば+50点
  • 負ければ0点
  • ハト派同士の場合、儀式には時間がかかる。その損失として勝者敗者共に-10点
  • タカ派同士の場合、負ければ傷を負うので損失として-100点

Hawk on a ceremonial stand.jpg
パブリック・ドメイン, Link

以上がシミュレーションの条件である。これをC言語で書いていく。

早速だが、書いたプログラムを示す。コンパイラBCC (Borland C++ Compiler)である。なお、こちらのプログラムでは、ハト派タカ派の比率が50対50の場合の、ハト派タカ派の1羽辺りの1戦の平均得点を計算する。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int GetRandom(int min, int max);

int A,B;
int bird[100] = {};

int main(void){

    int i,pattern;
    int hato_hato = 0;
    int hato_taka = 0;
    int taka_taka = 0;
    float taka_total,hato_total = 0;

    for(i = 0; i < 100000; i++){

        A = GetRandom(0, 99); 
        B = GetRandom(0, 99);

        pattern = (A % 2) + (B % 2);

        switch(pattern){
            case 0:
                if(GetRandom(0, 99) % 2 == 0){
                    bird[A] -= 100;
                    bird[B] += 50;
                    taka_taka++;    
	       }
               else{
                   bird[A] += 50;
                   bird[B] -= 100;
                   taka_taka++;
                }
                break;

            case 1:
                if(A % 2 == 0){
                    bird[A] += 50;
                    hato_taka++;
                }
                else{
                    bird[B] += 50;
                    hato_taka++;
                }
                break;

            case 2:
                if(GetRandom(0, 99) % 2 == 0){
                    bird[A] += 40;
                    bird[B] -= 10;
                    hato_hato++;
                }
                else{
                    bird[A] -= 10;
                    bird[B] += 40;
                    hato_hato++;
                }
                break;
        }
    }

    for(i = 0; i <= 99; i += 2){
        taka_total += bird[i];
    }

    for(i = 1; i <= 99; i += 2){
        hato_total += bird[i];
    }

    printf("average of taka equal %f\n",taka_total/(50 * 1000 * 2));
    printf("average of hato equal %f\n",hato_total/(50 * 1000 * 2));
    printf("\n");
    printf("taka-taka =%d\n",taka_taka);
    printf("hato-taka =%d\n",hato_taka);
    printf("hato-hato =%d\n",hato_hato);

    return 0;
}

int GetRandom(int max, int min){

    static int flag;

    if(flag == 0){
        srand((unsigned int)time(NULL));
        flag = 1;
    }
    return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

出力は以下のようになる。タカ派の得点とハト派の得点は、計算によって得られた期待値とほぼ等しい。

f:id:nmzfish:20190815102426j:plain
出力
プログラムの内容を説明していく。ただし、重要だと思われる部分についてのみ説明する。

GetRondom関数は乱数を生成する。中身については以下の記事を参考にした。minとmaxの範囲の乱数を返す。

int GetRondom(int min, int max);

9cguide.appspot.com


メイン文内のforループである。ループ回数、つまり、行われる総対戦回数は10万回である。bird[0]からbird[99]のそれぞれの鳥が呼び出される回数、つまり、個々の対戦回数はおよそ2000回である。bird[A]で呼び出される場合とbird[B]で呼び出される場合があるため、試行回数を鳥の総数で割った数の2倍となる*2
AとBに乱数を格納する。これらに格納された番号の鳥を対戦させる*3。bird[偶数]の鳥はタカ派とし、bird[奇数]の鳥はハト派とした。pattern変数は、AとBに格納された変数のmod2の和を格納する。どちらもタカ派であれば、和は0であり、タカ派同士の対戦となる。また、和が1であれば、タカ派vsハト派あるいはハト派vsタカ派の対戦となる。和が2の場合は、ハト派同士の対戦となる。pattern変数の値がそのまま対戦形態となるので、switch文で分岐させ、上記の配点を記述する。タカ派同士とハト派同士では、bird[A]とbird[B]のどちらが勝つかは五分五分であるとした。発生させた乱数のmod2が0か否か、つまり、偶数であるかどうかで判定した。0から99までの範囲で乱数を生成させれば、その数が偶数である確率は50%である。

int main(void){

    int i,pattern;
    int hato_hato = 0;
    int hato_taka = 0;
    int taka_taka = 0;
    float taka_total,hato_total = 0;

    for(i = 0; i < 100000; i++){

        A = GetRandom(0, 99); 
        B = GetRandom(0, 99);

        pattern = (A % 2) + (B % 2);

        switch(pattern){
            case 0:
                if(GetRandom(0, 99) % 2 == 0){
                    bird[A] -= 100;
                    bird[B] += 50;
                    taka_taka++;    
	   }
                else{
                    bird[A] += 50;
                    bird[B] -= 100;
                    taka_taka++;
                }
            break;

            case 1:
                if(A % 2 == 0){
                    bird[A] += 50;
                    hato_taka++;
                }
                else{
                    bird[B] += 50;
                    hato_taka++;
                }
                break;

            case 2:
                if(GetRandom(0, 99) % 2 == 0){
                    bird[A] += 40;
                    bird[B] -= 10;
                    hato_hato++;
                }
                else{
                    bird[A] -= 10;
                    bird[B] += 40;
                    hato_hato++;
                }
                break;
        }
    }

上記に貼り付けた結果より、ハト派の1戦辺りの平均得点は7.5639点であり、タカ派は12.5925点である。計算で得られたハト派の期待値は7.5点であり、タカ派の期待値は12.5点である。得点が高いということは、利益が大きいということであり、個体数が増える。つまり、ハト派タカ派の安定的な比率は、タカ派の方が少し大きいことがわかる。今後は、ハト派タカ派のみの集団の場合の安定的な比率をC言語でプログラミングしたシミュレーションで求めてみたい。

利己的な遺伝子 40周年記念版

利己的な遺伝子 40周年記念版

攻撃―悪の自然誌

攻撃―悪の自然誌

*1:銃・病原菌・鉄とサピエンス全史

*2:ここが分かっておらず、結局、任意の1羽が呼び出される回数をカウントして気づいた

*3:AとBに同じ数が格納されることは考えていない