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>