이번 시간에는 스타벅스 시뮬레이션 예제를 만들어보며 SimPy 패키지 활용법을 알아보고자 한다.
* 그냥 카페 시뮬레이션인데, 스타벅스 예제라고 이름 붙였다. (●'◡'●)
1. Python SimPy 패키지
프로세스 기반의 이산-이벤트 시뮬레이션(discrete-event simulation) 모델을 생성할 수 있는 Python 패키지이며, 기존 다른 패키지처럼 pip로 설치가 가능하다. 시뮬레이션을 위한 가상환경을 구성하고 설치를 해 주는 것이 좋다. 관련 내용은 아래의 SimPy document 페이지를 참고하면 된다.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2. Starbucks-01 시뮬레이션 예제 구현
기본 모델부터 시작해서 상황을 계속 추가하며 구현할 것이기 때문에 번호를 붙였다. Starbucks-01 예제에서 카페의 상황은 아래와 같으며, VSCode로 구현했다.
- 고객의 프로세스
- 고객은 10초에 1명씩 총 10명이 도착한다.
- 도착한 고객은 주문받는 직원이 있는 경우, 30초 동안 커피를 주문한다.
- 고객이 주문을 완료하면, 음료를 제조하는 직원은 30초 동안 커피를 준비한다.
- 커피가 완성되면 고객은 커피를 들고 카페를 나간다.
- 카페 상황
- 주문을 받는 직원은 2명 뿐이며, 음료를 제조하는 직원은 충분히 많다.
- 주문 받는 직원이 없는 경우 고객은 주문을 하지 못한다.
2.1 Customer 클래스
class Customer(object):
def __init__(self, env, number):
self.env = env
self.number = number
#분포에 따라 customer 도착
self.action = env.process(self.customer_generate())
def customer_generate(self):
~
def order_coffee(self, name, staff):
~
def wait_for_coffee(self, name):
~
Customer 라는 클래스를 만들고, 고객의 프로세스대로 함수를 생성했다.
customer_generate 함수는 고객의 도착과 관련되어 있으며, order_coffee 함수는 커피 주문, wait_for_coffee 함수는 커피를 수령을 위해 대기하는 행동과 관련되어 있다. 함수부터 천천히 살펴보자.
2.2 customer_generate 함수
def customer_generate(self):
for i in range(self.number):
name = 'Customer-%s'%i
arrive = self.env.now
print(name, '%8.3f' %arrive,'카페도착')
#도착한 고객은 주문하러 이동
self.env.process(self.order_coffee(name, staff))
#고객은 10초마다 카페에 도착
interval_time = 10
yield self.env.timeout(interval_time)
self.number 값만큼의 인원이 스타벅스에 온다. 카페에 도착하면 고객 ID가 부여되며, 도착한 그 순간에 고객 ID, 도착한 시간, '카페도착' 값이 함께 출력된다. 카페에 들어온 고객은 주문하러 이동하고 order_coffee 함수가 수행된다.
또 다른 고객은 10초 후에 도착하는데, 이는 env 객체에 timeout 10초를 두어, 시간 값을 지연시켜 표현한다. 고객 도착 시간에 상수 값이 아닌 분포를 적용하고 싶다면 interval_time 변수에 random.expovaiate(1.0/3) 이런 식으로 분포를 넣어주면 된다.
2.3 order_coffee 함수
def order_coffee(self, name, staff):
#직원 요청
with staff.request() as req:
yield req
#직원에게 30초동안 주문
ordering_duration = 30
yield self.env.timeout(ordering_duration)
print(name, '%8.3f'%self.env.now, '주문완료')
#주문한 고객은 커피 수령을 위해 대기
yield self.env.process(self.wait_for_coffee(name))
staff변수는 주문을 받는 직원 수를 의미한다. 고객은 커피 주문을 위해 직원을 요청하는데, 이는 with staff.request() as req: yield req로 표현한다. 위처럼 with 구문을 쓰면, 직원은 자동적으로 점유 상태에서 해제된다.
즉, 앞 고객의 주문이 끝나서, 커피 주문을 받을 수 있는 직원이 생기면, 바로 다음 고객의 주문을 받을 수 있게 된다는 의미이다. 만약 request()를 with 없이 쓰는 경우는, release()를 사용하여 직접 자원(여기서는 직원)을 해제해야 한다.
고객에게 직원이 할당되면, 고객은 30초 동안 커피 주문을 하고, 주문이 완료되면 고객 ID, 완료된 시간, '주문완료' 값을 함께 출력한다. 주문 후 wait_for_coffee 함수가 실행되도록 해서, 주문이 끝난 고객이 음료가 제조되기를 기다리는 상황을 표현하면 된다.
2.4 wait_for_coffee 함수
def wait_for_coffee(self, name):
#30초 대기 후 커피 수령
waiting_duration = 30
yield(self.env.timeout(waiting_duration))
print(name, '%8.3f' %self.env.now,'커피수령')
주문 받는 직원은 2명이고, 음료를 제조하는 직원은 충분히 많기 때문에 wait_for_coffee 함수에서는 직원을 요청하는 부분을 제외한다. 커피를 준비하는 30초가 지나면 고객ID, 제조가 완료된 시간, '커피수령' 값이 함께 출력된다.
2.5 Simulator 실행하는 부분
print('Starbucks_Example-01')
env = simpy.Environment()
staff = simpy.Resource(env, capacity=2)
customer = Customer(env, 10)
env.run()
env 객체를 생성해주고, 주문받는 직원 2명을 생성한다. Customer 클래스에 env와 총 고객 수를 파라미터로 입력하여 customer 객체를 생성한 후, run 함수를 이용하여 실행한다. run()의 until 파라미터에 시간 값을 입력하여 총 수행 시간을 조정할 수도 있다.
2.6 전체 코드 및 수행 결과
import simpy
class Customer(object):
def __init__(self, env, number):
self.env = env
self.number = number
#분포에 따라 customer 도착
self.action = env.process(self.customer_generate())
def customer_generate(self):
for i in range(self.number):
name = 'Customer-%s'%i
arrive = self.env.now
print(name, '%8.3f' %arrive,'카페도착')
#도착한 고객은 주문하러 이동
self.env.process(self.order_coffee(name, staff))
interval_time = 10
yield self.env.timeout(interval_time)
def order_coffee(self, name, staff):
#직원 요청
with staff.request() as req:
yield req
#직원에게 30초동안 주문
ordering_duration = 30
yield self.env.timeout(ordering_duration)
print(name, '%8.3f'%self.env.now, '주문완료')
#주문한 고객은 커피 수령을 위해 대기
yield self.env.process(self.wait_for_coffee(name))
def wait_for_coffee(self, name):
#30초 대기 후 커피 수령
waiting_duration = 30
yield(self.env.timeout(waiting_duration))
print(name, '%8.3f' %self.env.now,'커피수령')
print('Starbucks_Example-01')
env = simpy.Environment()
staff = simpy.Resource(env, capacity=2)
customer = Customer(env, 10)
env.run()

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3. Starbucks-01 시뮬레이션 예제 수행 결과 정리
고객ID별로 각 단계를 언제 수행했는지, 다음 단계까지 얼마나 소요되었는지 정리해보면 위와 같다. 주문 완료 단계의 테이블의 (s1), (s2)는 주문 받는 직원의 ID를 의미한다.
주문에 소요되는 시간은 분명 30초였는데, Customer-2 고객부터는 주문에 걸리는 시간이 40초 이상인 것을 확인할 수 있다.
이러한 이유는, 현재 주문 받는 직원이 s1, s2 총 두 명 뿐이기 때문이다. Customer-2가 카페에 도착한 시점에 직원 s1은 Customer-0의 주문을 받고 있고, 직원 s2는 Customer-1의 주문을 받고 있기 때문에, Customer-2는 직원을 기다려야 한다. Customer-3~9도 이러한 이유로 주문 완료까지 소요시간이 30초보다 오래 걸린다.
원하는 상황을 그려두고, SimPy document 페이지에서 소개하는 방법에 따라 시뮬레이터를 만들어보았는데, 생각보다 재미있는 작업이었다. 앞으로 얼마나 더 확장해서 구현할 수 있을지 기대가 된다. 항상 주피터 노트북만 사용하다가, 이번에 VSCode로 갈아탔는데, 잘 사용하면 정말 유용할 것 같다.
다음 포스팅에서는 클래스를 더 구분한 예제를 올려볼 것이다 🔥🔥🔥
'Python' 카테고리의 다른 글
[Python] list 정렬 - sort, sorted의 차이 (0) | 2023.06.13 |
---|---|
[Python] Visual Studio Code에서 가상환경 만들기 (0) | 2022.03.02 |
[Python] Numpy Broadcasting 개념 (0) | 2022.02.24 |
[Simulation] SimPy 패키지로 스타벅스 예제 만들기 (2) (0) | 2021.08.29 |
[Python] Jupyter notebook에서 .Rdata파일을 .csv 파일로 변환 (1) | 2021.05.18 |
댓글