エージェントシミュレーション2（噂の広まり方）

# 前回は・・・
コンビニなどの小売店内を想像したエージェントシミュレーションをしました。リンクを張っておきます。
＜エージェントシミュレーション（コンビニ）＞
https://qiita.com/chutake_exe/items/7906fa90f8272f326895

# テーマ：噂の広まり方
大学が夏休みに入り、自由な時間が増えました。前回のコンビニのシミュレーションをもとに、今回は噂の広まり方をシミュレーションしてみようと思います。

今回は噂を伝える人、噂を受け取る人を用意します。ここではそれぞれAとBと名付けます。

### ・レイアウト
前回と同様に、pythonのTKinterを使います。30×30マスでフィールドを構築します。また観察を行うために必要なボタンもいくつか実装しました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/f250098f-b260-a2dd-f78b-80cd10484101.png" width=400>
ここで赤い人物はA（噂を知っている人）、青い人物はB（噂を知らない人）としています。

### ・現実に近づけるために
今回のシミュレーションでは、噂の広まる方法は近くで聞こえてしまった場合に限ります。
__また皆さんは、聞こえてしまった噂を鵜呑みにはしないですよね？（笑）__
これらを踏まえてエージェントに2つの変数を用意し、その変数を調整することで観察をします。
##### 変数1・・・Aが噂を発信する範囲（マス）
##### 変数2・・・Bが噂を信じるまでの回数

少し前の噂や事実は忘れられていくように、情報には期限があります。よってあるステップ経過後、自動的にAはBに変化する（噂を広めなくなる）ようにしました。

### ・実験方法
変数を1つだけ変えて以下の試行をし、①～③で対照実験をする。
```
1, Bを10人単位でフィールドに追加していきます。
2, Aを1人追加する。（噂の発信源）
3, 全員に噂が広まる（全員Aになる）
4, 3になるまでのステップ数を計算する
```

① 人数を変えてみる。11人（A=1,B=10）と21人（A=1,B=20）
② 噂を広める範囲を変更してみる。
③ 噂を信じる回数を変更する。

「たまたま人が集まっていて、噂が広まるのが早すぎた」などが起こると、終了状態になるまでのステップ数が異常値になってしまいます。今回は各5回ずつ試行し、その平均値をとることで解決とします。（各5回では少ない気もしますが...）
## 結果
①～③の結果をグラフにしてみました。
##### ① 人数を変える。左：11人（A=1,B=10）と右：21人（A=1,B=20）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/e01f583c-2135-a32c-a552-d30fd18644d8.png" width=300>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/e4a1b4a4-238a-951c-dffb-698a06fd494b.png" width=320>

全員がAになるまで、それぞれ平均 __1503.6ステップ__、__639.6ステップ__でした。まあ、人数が多ければ多いほど噂って広まりますよね。

##### ② 噂の広まる範囲を変える。(左：2マス→右：4マス)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/e01f583c-2135-a32c-a552-d30fd18644d8.png" width=300>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/0d24b2df-fc6f-8774-8799-1010b526ca83.png" width=310>

全員がAになるまで、それぞれ平均 __1503.6ステップ__、__476.6ステップ__ でした。

##### ③ 噂を信じるまでの回数を変える。（左：5回→右：3回）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/e01f583c-2135-a32c-a552-d30fd18644d8.png" width=300>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632237/5ddaf14c-85c8-2500-c5c5-d18bfebe0425.png" width=310>

全員がAになるまで、それぞれ平均 __1503.6ステップ__ 、__936.6ステップ__ でした。

#### ・まとめ
1番ステップ数が短いのが、②の噂を広める範囲を変えることでした。
結果をみて自分で「そりゃそうだろ、広い範囲に影響を与えるのが早く広まるじゃん」と思いました（笑）。よってこの観察の結果から、噂を早く広めたいとするならば
__・有名人やインフルエンサーが発信源となることで、効率的に噂を広めることができる__
ということが分かりました。

## 最後に
前回同様、現実世界になるべく近づけることが課題になります。フィールド上のA(噂を広める人)は一定期間ではありますが、その間は噂をし続けるという条件です。他にも噂を知る手段としてSNSなどがあります。このシミュレーションと同時並行で、エージェントがSNSを見ていて影響を受けるというのも面白そうですね。今回もプログラムを載せておきます。


<details><summary>プログラム（シミュレーション本体）</summary>

```python
from tkinter import *
import random
import time
from dataclasses import dataclass
import matplotlib.pyplot as plt


CELLSIZE=20
FIELD_X,FIELD_Y=30,30
WIDTH,HEIGHT=FIELD_X*CELLSIZE,FIELD_Y*CELLSIZE
tk=Tk()
tk.title("噂の広まり方")
canvas=Canvas(tk,width=WIDTH,height=HEIGHT)
canvas.pack()

##変数##
layout=[]#Fieldの座標などが入る
agents=[]#エージェントたち
speed=2#観察の速さ
Allr=False #全員RedになったらTrueに
Allb=False #全員blueになったらTrueに
Start=False #フィールドにREDを追加したらカウント開始
RedAgent=[]#StartがTrueのとき、ステップ毎にREDの増減をadd
##/変数##

##dataclass##
@dataclass
class people:
   i:int
   x:int
   y:int
   status:str #購入前（yet）→購入後（done）
   life:int
   effectnum:int #影響受ける数

@dataclass
class Layout:
   x:int
   y:int
##/dataclass##

##関数##
def make_layout(layout):#レイアウトを作る
   for x in range(FIELD_X):
      for y in range(FIELD_Y):
         layout.append(Layout(x,y))


def create_layout(L):
   for x in range(FIELD_X):
      canvas.create_line(x*CELLSIZE,0,x*CELLSIZE,FIELD_Y*CELLSIZE)
   for y in range(FIELD_Y):
      canvas.create_line(0,y*CELLSIZE,FIELD_X*CELLSIZE,y*CELLSIZE,)

def rectangle_buyer(agents):
   for agent in agents:
      x=agent.x*CELLSIZE
      y=agent.y*CELLSIZE
      if(agent.status=="done"):
         canvas.create_rectangle(x,y,x+CELLSIZE,y+CELLSIZE,fill="red")
      elif(agent.status=="yet"):
         canvas.create_rectangle(x,y,x+CELLSIZE,y+CELLSIZE,fill="blue")

def add():#BLUE追加
   for x in range(10):#客をふやす
      i=len(agents)+1
      x=random.randint(0,29)
      y=random.randint(0,29)
      status="yet"
      life=0
      effe=0
      agents.append(people(i,x,y,status,life,effe))

def add_rumor():#RED追加
   global Start
   i=len(agents)+1
   x=random.randint(0,29)
   y=random.randint(0,29)
   status="done"
   life=200#うわさの寿命
   effe=0
   agents.append(people(i,x,y,status,life,effe))
   Start=True

def move(agents):#エージェントの移動
   for agent in agents:
      while(True):
         rand=random.randint(0,50)#1~5で方向を決める
         if(rand<10):#右
            p=FIELD_X*(agent.x+1)+agent.y
            if(not p>FIELD_X*FIELD_Y):
               agent.x+=1
               break

         elif(rand<20):#下
            p=agent.y+1
            if(not p>FIELD_Y-1):
               agent.y+=1
               break

         elif(rand<30):#左
            p=FIELD_X*(agent.x-1)+agent.y
            if(not p<0):
               agent.x-=1
               break

         elif(rand<40):#上
            p=agent.y-1
            if(not p<0):
               agent.y-=1
               break

         else:#止まる
            break
         ##行動おわり

def check(distance,effectNum):#distance=噂が広まる距離、effectNum=噂が広まるための回数
   for checkagent in agents:
      if(checkagent.status=="done"):
         for agent in agents:
            if(abs(agent.x-checkagent.x)<=distance and abs(agent.y-checkagent.y)<=distance and agent.status=="yet"):#近くにいるとき
               agent.effectnum+=1
               if(agent.effectnum>=effectNum):
                  agent.status="done"
                  agent.effectnum=0
                  agent.life=200
         checkagent.life-=1
         if(checkagent.life<0):
            checkagent.status="yet"
            checkagent.life=0

def checkAllr():##全員redか判定
   if(len(agents)==0):
      return False
   for agent in agents:
      if(agent.status=="yet"):
         return False
   return True

def checkAllb():#全員blueか判定
   if(len(agents)==0):
      return False
   for agent in agents:
      if(agent.status=="done"):
         return False
   return True

def print_all():#エージェント総出力
   for agent in agents:
      print(agent)

def change_speed():#観察のスピード変更
   global speed
   speed=txt.get()

def RedAgentCount():#REDを数える
   count=0
   for agent in agents:
      if(agent.status=="done"):
         count+=1
   return count

def Redprint():#REDの増減をリストで出力
   print(RedAgent)

##/関数##

#レイアウト構成
make_layout(layout)

##UI##
btn1=Button(tk,text="人追加",command=add)
btn1.pack()
btn2=Button(tk,text="出力",command=print_all)
btn2.pack()
btn3=Button(tk,text="重要人物",command=add_rumor)
btn3.pack()
txt=Entry(width=20)
txt.insert(0,"2")
txt.pack()
btn4=Button(tk,text="速さ変更",command=change_speed)
btn4.pack()
btn5=Button(tk,text="REDのデータ",command=Redprint)
btn5.pack()
##/UI##

##main##
def main_loop():
   global Allr,Allb
   canvas.delete("all")#全部消す
   create_layout(layout)#レイアウト描画
   move(agents)#エージェントの移動 (エージェントのリスト)
   check(2,5)#噂が広まるかチェック　(広まる距離、近づいた回数)
   rectangle_buyer(agents)#エージェントの描画

   #全員がREDかBLUEになるまでリストに追加する
   if(checkAllr()):
      Allr=True
      print(f"{RedAgentCount()} : 全員REDになりました。")
   if(checkAllb() and Start):
      Allb=True
      print(f"{RedAgentCount()} : 全員BLUEになりました。")
   if((not Allr) and (not Allb)):
      if(Start):
         RedAgent.append(RedAgentCount())
      tk.after(speed,main_loop)
##/main##

##main呼び出し
main_loop()

```
</details>
<details><summary>プログラム（グラフ書く用）</summary>

```python
import matplotlib.pyplot as plt

#取得した値を入れる
data1=[]
data2=[]
data3=[]
data4=[]
data5=[]
#何ステップで終了したか出力
print(f"data1:{len(data1)}")
print(f"data2:{len(data2)}")
print(f"data3:{len(data3)}")
print(f"data4:{len(data4)}")
print(f"data5:{len(data5)}")

#グラフの描画
plt.title("噂を広める範囲=2,信じるまでの回数=5",fontname="MS Gothic")
plt.xlabel("移動回数（時刻）",fontname="MS Gothic")
plt.ylabel("噂が広まった人数（人）",fontname="MS Gothic")

plt.plot(data1,color="red")
plt.plot(data2,color="blue")
plt.plot(data3,color="black")
plt.plot(data4,color="orange")
plt.plot(data5,color="green")
plt.show()

##5回の平均
print((len(data1)+len(data2)+len(data3)+len(data4)+len(data5))/5)
```
</details>