SimPy('블림피(Blimpie)'와 발음이 비슷합니다)는 프로세스 지향(process-oriented) 이산 이벤트 시뮬레이션(discrete-event simulation)을 위한 파이썬 패키지입니다. 생산 라인, 콜센터, 네트워크 트래픽처럼 이벤트 단위로 흘러가는 시스템을 간단한 코드로 모델링할 수 있어 시뮬레이션 입문용으로 널리 사용됩니다.
설치 방법
SimPy를 가장 쉽게 설치하는 방법은 pip를 이용하는 것입니다.
pip install simpy
명령을 실행하면 다음과 비슷한 출력 결과를 확인할 수 있습니다.
이 글을 작성하는 시점 기준으로 SimPy의 최신 버전은 simpy-3.0.11이며, 아래의 모든 예제는 이 버전을 기준으로 작성되었습니다.
만약 SimPy가 이미 설치되어 있다면 pip의 -U 옵션을 사용해 최신 버전으로 업그레이드할 수 있습니다.
pip install -U simpy
참고: 파이썬 2.7 이상의 버전이 필요하며, Linux/Unix/macOS 환경에서는 SimPy 설치 시 루트(root) 권한이 요구될 수 있습니다.
설치가 정상적으로 완료되었는지 확인하려면 파이썬 셸을 연 뒤 import simpy를 실행해 오류가 없는지 확인하면 됩니다.
기본 개념
SimPy는 이산 이벤트 시뮬레이션 라이브러리입니다. 메시지, 차량, 고객처럼 능동적으로 움직이는 구성 요소는 프로세스(process)로 모델링됩니다. 즉, SimPy에서 능동적인 개체(active entity)를 프로세스라고 부르며, 프로세스는 이산 이벤트를 yield하는 파이썬 제너레이터(generator)입니다.
일반 함수는 값을 return하지만, 제너레이터는 yield를 사용한다는 점이 다릅니다. 이 차이 덕분에 우리는 이벤트를 생성하고, 해당 이벤트가 트리거될 때까지 기다릴 수 있습니다.
프로세스가 이벤트를 yield하면 그 프로세스는 일시 중단(suspend)됩니다. SimPy는 이벤트가 발생하는 시점에 중단된 프로세스를 다시 재개(resume)합니다. 만약 여러 프로세스가 동일한 이벤트를 기다리고 있다면, SimPy는 해당 이벤트를 yield한 순서대로 프로세스를 재개합니다.
def gen(x):
y = yield x + 1
return y
>>> g = gen(1)
>>> next(g)
2
>>> next(g)
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
next(g)
StopIteration
위 예제에서 반복은 변수 x가 처음 yield된 이후 StopIteration 예외와 함께 종료됩니다. 이것이 바로 제너레이터의 동작 방식입니다.
Timeout 타입의 이벤트는 일정량의 (시뮬레이션 상) 시간이 경과한 후에 트리거됩니다. Timeout 이벤트를 사용하면 프로세스가 특정 시간 동안 대기하거나 잠들 수 있습니다. Timeout을 포함한 모든 이벤트는 해당 프로세스가 속한 Environment 객체의 메서드를 호출하여 생성합니다.
예제: 컨퍼런스 참석자 시뮬레이션
아래는 컨퍼런스에 참석한 사람이 강연을 듣고 휴식을 취하는 과정을 시뮬레이션한 전체 코드입니다.
# 필요한 라이브러리 임포트
from random import randint
import simpy
# 설정
TALKS_PER_SESSION = 3 # 세션당 강연 수
TALK_LENGTH = 30 # 강연 길이(분)
BREAK_LENGTH = 15 # 휴식 길이(분)
ATTENDEES = 1 # 참석자 수
def attendee(env, name, knowledge=0, hunger=0):
talks = 0
breaks = 0
# 세션 반복
while True:
# 강연 참석
for i in range(TALKS_PER_SESSION):
print('Talk {0} begins at {1}'.format(talks+1, env.now))
knowledge += randint(0, 3) / (1 + hunger)
hunger += randint(1, 4)
talks += 1
yield env.timeout(TALK_LENGTH)
print(f'Talk {talks} ends at {env.now}')
print('Attendee %s finished talks with knowledge %.2f and hunger %.2f' % (name, knowledge, hunger))
# 휴식, 뷔페 이동
food = randint(3, 12)
hunger -= min(food, hunger)
yield env.timeout(BREAK_LENGTH)
print('Attendee %s finished eating with hunger %.2f' % (name, hunger))
# 시뮬레이션 실행
env = simpy.Environment()
for i in range(ATTENDEES):
env.process(attendee(env, i))
env.run(until=250)
위 프로그램을 실행하면 강연의 시작과 종료 시각, 참석자의 지식(knowledge)과 허기(hunger) 변화가 시뮬레이션 시간과 함께 출력되는 것을 확인할 수 있습니다.
이 코드는 컨퍼런스 홀의 상황을 재현한 것으로, 세션당 3개의 강연(강연 길이 30분)과 15분의 휴식으로 구성된 일정을 참석자가 반복해서 따라가는 시나리오입니다. 참석자 수는 상단의 ATTENDEES 값을 조정해 자유롭게 변경할 수 있습니다.
attendee 프로세스는 새로운 이벤트를 생성하기 위해 Environment 객체(env), 이름(name), 지식(knowledge), 허기(hunger)에 대한 참조가 필요합니다. while True 루프 때문에 세션은 조건이 거짓이 되기 전까지 무한히 반복됩니다. attendee() 함수는 제너레이터이므로 스스로 종료되지 않고, yield 문에 도달할 때마다 제어 흐름을 시뮬레이션 엔진에 다시 넘겨줍니다.
마지막으로 env.run(until=250)을 호출해 시뮬레이션을 시점 250까지 실행합니다. 이 시간 동안 '컨퍼런스 참석자'는 강연 3회 → 휴식 1회 → 강연 3회 → 휴식 1회 → 강연 1회 순서로 활동하게 됩니다.