---
# System prepended metadata

title: SRC워크샵(ABM By Python)
tags: [Python]

---

SRC워크샵(ABM By Python)
===
###### tags: `Python`
#### <목차>
1. [Python에서 animation 구현하는 법](#animation)
2. [개체들간의 거리행렬 계산](#distance)
3. [배열](#array)
4. [SIR 모델에서 거리에 따른 감염확률 계산](#prob)
5. [SIR 모델 시뮬레이션](#simul)
6. [KDTree 최적화](#kdtree)












### 1. Python에서 animation 구현하는 법 <a id = 'animation'></a>

- 애니메이션 구현에 주로 사용되는 패키지
: from matploitlib.animation import ***FuncAnimation, ArtistAnimation***

    - FuncAnimation
        : 사용자 정의 함수로 그래프를 계속해서 업데이트 해나가는 구조
        참고) [FuncAnimation 공식문서](https://matplotlib.org/stable/api/_as_gen/matplotlib.animation.FuncAnimation.html)
    - ArtistAnimation
        : 변화하는 그래프를 리스트를 인자로 주어 그려 나가는 구조
       참고) [ArtistAnimation 공식문서](https://matplotlib.org/stable/api/_as_gen/matplotlib.animation.ArtistAnimation.html://)

``` python
def moving_agent(n):
    from matplotlib.animation import ArtistAnimation
    from IPython.display import HTML
    
    location = rand(2, n) # 위치 좌표 설정
    
    
    fig, ax = plt.subplots()
    ax.set(xlim = (-10, 10), ylim = (-10, 10)) # 범위 미리 고정
    
    
    loc_lst = [] # 변화하는 그래프를 담을 리스트 생성
    for frame in range(100):
        location += 0.2 * randn(2, n)
        loc = ax.scatter(location[0], location[1], color = 'blue', s = 10)
        loc_lst.append([loc]) # 변화하는 그래프를 리스트에 추가
        
    
    anim = ArtistAnimation(fig, loc_lst, interval = 5)
    return HTML(anim.to_jshtml())
        
```

### 2. 개체들간의 거리행렬 계산<a id = "distance"></a>

julia와 비슷하게 rand, randn 메서드를 사용할 수 있다.
```python
from numpy.random import rand, randn
```
for문을 통해 거리행렬을 계산하는 과정, 코드 또한 julia와 일치하는 것을 확인할 수 있다

```python
from numpy import zeros
from math import pi, sqrt

D = zeros([10, 10])
for i in range(n):
    for j in range(n):
        D[i, j] = sqrt(sum((location[:, i] - location[:, j]) ** 2))

D       
```

### 3. 배열 <a id = "array"></a>
이 부분 또한 julia에서의 코드와 거의 일치한다. julia에서의 push!가 python에서는 np.append()와 동일한 의미로 이를 대체하는 것을 제외하면 동일하다.

```python
## 배열

state = np.array(['S' for _ in range(10)])
state = np.append(state, 'I')
state = np.append(state, 'I')


bit_S = state == 'S'
bit_I = state == 'I'
n_S = np.sum(bit_S)
n_I = np.sum(bit_I)

n = 12
location = rand(2, n)

D = zeros([n_S, n_I])
for i in range(n_S):
    for j in range(n_I):
        D[i, j] = sqrt(sum((location[:, i] - location[:, 10 + j]) ** 2))

D
```

for문을 돌리지 않고 distance메서드를 통해서 간단한 계산도 가능하다
```python
from scipy.spatial import distance
pair = distance.cdist(location[:, bit_S].T, location[:, bit_I].T, 'euclidean')
contact = np.sum(pair < k, axis = 1)
print(pair)

```
for문을 이용해 계산하는 방법과 distance메서드에 의한 계산 결과를 비교해보면 동일함을 알 수 있다

- distance.cdist() : 두 배열간의 거리를 계산하기 위한 메서드
    참고) [distance.cdist 관련 공식문서](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html)
    
### 4. 거리에 따른 감염확률 계산 <a id = "prob"></a>
contact 변수가 정상인이 감염자와 일정 거리안에 들어오는 횟수, 즉 접촉 횟수이다. 접촉하면 감염되는 확률을 $\beta$ = 0.5라고 할 때, 한 사람이 감염되는 확률을 $1 - (1-\beta) ^{contact}$로 표현가능하다.

```python
b = 0.5 # beta

# 감염자가 되는 확률
1 - (1 - b) ** contact
infected = 1 - (1-b) ** contact < rand(n_S) # 난수를 발생시키고 이보다 작다면 감염자로 특정한다
infected

np.put(state, np.where(bit_S)[0][infected], 'I') # infected = True인 사람들의 상태를 'S' -> 'I'로 변환, np.where(bit_S) -> 튜플반환이므로 [0]으로 array만 뽑아옴
state

```

### 5. SIR 모델 Simulation <a id = "simul"></a>
거리가 변화함에 따라 정상인, 감염자들이 변화하는 SIR 모델을 정의한다
- S : Supseptible(정상인)
- I : Infected(감염자)
- R : Recovered(감염 후 회복 된사람)

```python
def SIR(n):
    beta = 0.3
    sigma = 0.1
    mu = 0.1
    
    S_ = []
    I_ = []
    R_ = []
    t = 0
    
    ID = range(n)
    state = np.array(['S' for _ in range(n)])
    state[0] = 'I'
    location = rand(2, n)
    
    while sum(state == 'I') > 0:
        t += 1
        location += randn(2, n)
        bit_S = state == 'S'; n_S = np.sum(bit_S); ID_S = np.array(ID)[bit_S]; S_.append(n_S)
        bit_I = state == 'I'; n_I = np.sum(bit_I); ID_I = np.array(ID)[bit_I]; I_.append(n_I)
        bit_R = state == 'R'; n_R = np.sum(bit_R); ID_R = np.array(ID)[bit_R]; R_.append(n_R)
        
        D = distance.cdist(location[:, bit_S].T, location[:, bit_I].T, 'euclidean')
        contact = np.sum(D < k, axis = 1)
        infected = rand(n_S) < (1 - (1 - beta) ** contact)
        
        np.put(state, ID_S[infected], 'I')
        np.put(state, ID_I[rand(n_I) < mu], 'R')
        
    return S_, I_, R_

fig, ax = plt.subplots()

def animate(t):
    x = np.arange(t_end)
    y1 = S
    y2 = I
    y3 = R
        
    ax.plot(x[:t], y1[:t], color = 'blue', label = 'S')
    ax.plot(x[:t], y2[:t], color = 'red', label = 'I')
    ax.plot(x[:t], y3[:t], color = 'green', label = 'R')
    ax.set(xlim = (0, t_end), ylim = (0, 50000))
    
    
ani = FuncAnimation(fig, animate, np.arange(t_end), interval = 100)

HTML(ani.to_jshtml())
```







### 6. KDTree 최적화 <a id = "kdtree"></a>
KDTree는 데이터에서 가장 가까운 점을 탐색하는데 최적화된 트리 자료 구조로, 공간을 분할하여 자료를 저장한다. 이는 특정 점 (또는 그 주변의 점)을 탐색하는데 $O(log\ n)$의 시간복잡도가 소요되어, 일반적인 자료구조 $O(n)$보다 효율적이다.

1. ***KDTree.query()*** 
    : 데이터 점들간의 거리 계산
```python
## 최적화
import scipy
from scipy.spatial import KDTree

kdtree = KDTree(location[:, bit_I])
print(kdtree)
kdtree.query(location[:, bit_S].T, distance_upper_bound=k)[0] < k
# k보다 작은 거리에 True, 아니면 False 반환
```

2. ***cKDTree.queryballpoint***
    : 데이터와 거리를 인자로 주면 거리보다 작은 데이터들을 list들의 array꼴로 반환

```python!
from scipy.spatial import cKDTree

tree = cKDTree(location[:, bit_I])
contact = tree.query_ball_point(location[:, bit_S].T, k, return_sorted=False)
for i, lst in enumerate(contact):
    if lst == [0]:
        contact[i] = 1
    else:
        contact[i] = 0
contact
    
```

SIR모델에 적용
```python!

def SIR2(n):
    beta = 0.3
    sigma = 0.1
    mu = 0.1
    
    S_ = []
    I_ = []
    R_ = []
    t = 0
    
    ID = range(n)
    state = np.array(['S' for _ in range(n)])
    state[1] = 'I'
    location = rand(2, n)
    
    while sum(state == 'I') > 0:
        t += 1
        location += randn(2, n)
        bit_S = state == 'S'; n_S = np.sum(bit_S); ID_S = np.array(ID)[bit_S]; S_.append(n_S)
        bit_I = state == 'I'; n_I = np.sum(bit_I); ID_I = np.array(ID)[bit_I]; I_.append(n_I)
        bit_R = state == 'R'; n_R = np.sum(bit_R); ID_R = np.array(ID)[bit_R]; R_.append(n_R)
        
        KDtree_I = KDTree(location[:, bit_I].T)
        contact = KDtree_I.query(location[:, bit_S].T, distance_upper_bound=k)[0] < k
        # contact = tree.query_ball_point(location[:, bit_S].T, k, return_sorted=False)
        # for i, lst in enumerate(contact):
        #    if lst == [0]:
        #       contact[i] = 1
        #   else:
        #      contact[i] = 0
        infected = rand(n_S) < (1 - (1 - beta) ** contact)
        
        np.put(state, ID_S[infected], 'I')
        np.put(state, ID_I[rand(n_I) < mu], 'R')        
    return S_, I_, R_

fig, ax = plt.subplots()

def animate(t):
    x = np.arange(t_end)
    y1 = S
    y2 = I
    y3 = R
        
    ax.plot(x[:t], y1[:t], color = 'blue', label = 'S')
    ax.plot(x[:t], y2[:t], color = 'red', label = 'I')
    ax.plot(x[:t], y3[:t], color = 'green', label = 'R')
    ax.set(xlim = (0, t_end), ylim = (0, 50000))
    
    
ani = FuncAnimation(fig, animate, np.arange(t_end), interval = 100)

HTML(ani.to_jshtml())
```


- 직접거리를 찾아주는 경우의 SIR모델 시뮬레이션 time
    : <span style='background-color:#fff5b1'>6.2s</span>

- KDTree를 이용한 SIR모델 시뮬레이션 time
    : <span style='background-color:#fff5b1'>1.8s</span>
    