def handle(self, model): """ If request is waiting more then N minutes or request does not like a menu, we will lost it When request is leaving, it makes its table free :param model: current state of model """ if self.request.state == RequestState.WAITING_FOR_WAITER or self.request.state == RequestState.LEAVING_BAD_MENU: self.request.table.available = True self.request.table.owner = None if self.request.state == RequestState.WAITING_FOR_WAITER: st.stay_times_long_waiting_leave.append( model.global_time - self.request.income_time) st.long_waiting_leave_counter += 1 logging.info("%s: Request %d left because of too long waiting", human_readable_date_time(model.global_time), self.request.id) elif self.request.state == RequestState.LEAVING_BAD_MENU: st.stay_times_bad_menu_leave.append(model.global_time - self.request.income_time) st.disliked_menu_counter += 1 logging.info("%s: Request %d left because of disliking a menu", human_readable_date_time(model.global_time), self.request.id)
def handle(self, model): """ Delivered dishes decremented (ate). If there are no dish we wait, we will call a waiter for an extra order with reorder_probability. On the other way, request will call a waiter for a bill. If there are no waiter we will wait while he comes. :param model: current state of model """ self.request.dish_count -= 1 self.request.state = RequestState.OK self.request.waiting_start_time = model.global_time logging.info("%s: Request %d ate a dish %d. Remaining dishes: %d", human_readable_date_time(model.global_time), self.request.id, self.dish.id, self.request.dish_count) # to fix a bug with negative dishes if self.request.dish_count < 1: self.request.dish_count = 0 waiters = list( filter(lambda wait: wait.state == WaiterState.FREE, model.restaurant.waiters)) reorder = choices([False, True], [ 1 - self.request.reorder_probability, self.request.reorder_probability ])[0] if reorder and model.global_time <= model.restaurant.last_entrance_time: self.request.state = RequestState.WAITING_FOR_WAITER self.request.waiting_start_time = model.global_time logging.info("%s: Request %d will make a reorder.", human_readable_date_time(model.global_time), self.request.id) if not self.request.reorder: st.reorder_counter += 1 self.request.reorder = True if waiters: waiter = waiters[0] logging.info( "%s: Waiter %d is taking a reorder of request %d.", human_readable_date_time(model.global_time), waiter.id, self.request.id) waiter.service(model, self.request) else: self.request.state = RequestState.WAITING_FOR_BILL logging.info("%s: Request %d is going to leave.", human_readable_date_time(model.global_time), self.request.id) if waiters: waiter = waiters[0] waiter.invoice(model) self.request.reorder_probability *= 0.5
def invoice(self, model): """ Check if somebody is waiting for a bill. If somebody is waiting, waiter change state to BILLINg and request's state to OK. Increase working hours. Call WaiterFree and TableFree events. :param model: current state of model """ waiting_for_bill = list( map( lambda t: t.owner, filter( lambda table: not table.available and table.owner.state == RequestState.WAITING_FOR_BILL, model.restaurant.tables ) ) ) if waiting_for_bill: self.state = WaiterState.BILLING leaving = waiting_for_bill[0] st.request_waiting[leaving.id] += model.global_time - leaving.waiting_start_time logging.info("%s: Request %d is billing by waiter %d", human_readable_date_time(model.global_time), leaving.id, self.id) leaving.state = RequestState.OK service_time = expovariate(1 / self.service_time) st.service_time.append(service_time) st.waiter_hours[self.id][model.current_request_interval()] += round(service_time) model.next_events.append(Event(model.global_time + service_time, WaiterFreeEvent(self, leaving))) model.next_events.append(Event(model.global_time + service_time, TableFreeEvent(leaving.table)))
def service(self, model, request): """ Change waiter's state to SERVICING, generate dishes for each human in request's size. Call CookerCallEvent for each generated dish. After that generate WaiterFreeEvent. Increase working time. :param self: who will work :param model: current state of model :param request: """ self.state = WaiterState.SERVICING st.request_waiting[request.id] += model.global_time - request.waiting_start_time request.state = RequestState.OK # because request already has a waiter logging.info("%s: Waiter %d is started servicing request %d", human_readable_date_time(model.global_time), self.id, request.id) service_time = 0 dish_count = 0 for human in range(request.size): service_time += expovariate(1 / self.service_time) dish_count += randrange(1, 3, 1) request.dish_count = dish_count request.billed_dish_counter += dish_count st.dish_counter.append(dish_count) logging.info("%s: Request %d ordered %d dishes", human_readable_date_time(model.global_time), request.id, dish_count) for dish in range(dish_count): model.next_events.append( Event(model.global_time + service_time, CookerCallEvent(Dish(request))) ) model.next_events.append( Event(model.global_time + service_time, WaiterFreeEvent(self, request))) st.service_time.append(round(service_time)) st.waiter_hours[self.id][model.current_request_interval()] += round(service_time) request.waiting_start_time = model.global_time + service_time
def handle(self, model): """ Check if there are ready dishes, if not check if there are requests waiting for waiter, if not check if there requests waiting for bill, if not become free. :param model: current state of model """ if self.waiter.state == WaiterState.DELIVERING_DISH: logging.info("%s: Waiter %d delivered dish %d to request %d", human_readable_date_time(model.global_time), self.waiter.id, self.dish.id, self.request.id) elif self.waiter.state == WaiterState.BILLING: logging.info("%s: Waiter %d finished billing request %d", human_readable_date_time(model.global_time), self.waiter.id, self.request.id) elif self.waiter.state == WaiterState.SERVICING: logging.info("%s: Waiter %d finished servicing request %d", human_readable_date_time(model.global_time), self.waiter.id, self.request.id) self.waiter.state = WaiterState.FREE dishes = model.restaurant.ready_dishes if dishes: self.waiter.state = WaiterState.DELIVERING_DISH dish = dishes[0] self.waiter.deliver(model, dish) else: tables = list( filter( lambda t: not t.available and t.owner.state == RequestState.WAITING_FOR_WAITER, model.restaurant.tables ) ) if tables: request = tables[0].owner self.waiter.service(model, request) else: self.waiter.invoice(model)
def handle(self, model): """ Make cooker available again. If there are dishes to cook, cooker will be cook them now. :param model: current state of model """ waiting_dishes = model.restaurant.waiting_dishes self.cooker.available = True logging.info("%s: Cooker %d finished cooking dish %d for request %d", human_readable_date_time(model.global_time), self.cooker.id, self.dish.id, self.dish.request.id) if waiting_dishes: self.cooker.cook(model, waiting_dishes[0])
def handle(self, model): """ Billed dishes counter increased. Serviced counter incremented. Stay times calculated. Free table. :param model: current state of model """ logging.info("%s: Request %d is leaving table %d", human_readable_date_time(model.global_time), self.table.owner.id, self.table.id) st.billed_dish_counter.append(self.table.owner.billed_dish_counter) st.serviced_counter += 1 st.stay_times_normal_leave.append(model.global_time - self.table.owner.income_time) self.table.available = True self.table.owner = None
def cook(self, model, dish): """ Cooker isn't available for cooking time. Dish will be ready in cooking time. :param model: current state of model :param dish: dish obj which cooker is going to cook """ self.available = False logging.info("%s: Cooker %d started cooking dish %d for request %d", human_readable_date_time(model.global_time), self.id, dish.id, dish.request.id) # cooking_time = expovariate(1 / self.cooking_time) cooking_time = uniform(10 * 60, 20 * 60) st.cook_time.append(cooking_time) st.cooker_hours[self.id][ model.current_request_interval()] += cooking_time model.restaurant.waiting_dishes.remove(dish) model.next_events.append( Event(model.global_time + cooking_time, DishEvent(dish, self)))
def handle(self, model): """ Ready dish appends to ready dishes queue. If there any free waiter, he will deliver this dish now. CookerFreeEvent is called here. :param model: current state of model """ waiters = list( filter(lambda wait: wait.state == WaiterState.FREE, model.restaurant.waiters)) model.restaurant.ready_dishes.append(self.dish) if waiters: waiter = waiters[0] waiter.deliver(model, self.dish) else: logging.info( "%s: No free waiter for cooker %d and dish %d for request %d", human_readable_date_time(model.global_time), self.cooker.id, self.dish.id, self.dish.request.id) model.next_events.append( Event(model.global_time, CookerFreeEvent(self.cooker, self.dish)))
]) pretty_table.add_row([ "Average ready dishes queue length", stats.avg_len_dict( stats.avg_dishes_queue, restaurant_model.restaurant.work_time_to - restaurant_model.restaurant.work_time_from) ]) print(pretty_table) header = ["Cooker id"] header.extend([ utils.human_readable_time(period.fromInterval) + "-" + utils.human_readable_time(period.toInterval) if not period.last else utils.human_readable_time(period.fromInterval) + "-" + utils.human_readable_date_time(restaurant_model.global_time) for period in restaurant_model.intervals ]) header.append("Total") pretty_table = PrettyTable(header) for key, value in stats.multi_period_worker_load( stats.cooker_hours, restaurant_model.global_time).items(): row = [key] row.extend(value) row.append( stats.total_worker_load(key, stats.cooker_hours, restaurant_model.global_time)) pretty_table.add_row(row)
def handle(self, model): """ Generating new request (exponential with custom meaning) with N people. Take a table and waiting for the waiter or check menu and leave with leaving probability. If there are no free table, request is going to leave. Increment counter of visitors or lost. :param model: current state of model """ st.total_counter += 1 # trying to seize table tables = list( filter(lambda t: t.size >= self.request.size and t.available, model.restaurant.tables)) logging.info("%s: Income request %d", human_readable_date_time(model.global_time), self.request.id) if tables: self.request.table = tables[0] self.request.table.available = False self.request.table.owner = self.request st.seated_counter += 1 logging.info("%s: Request %d took a table %d", human_readable_date_time(model.global_time), self.request.id, self.request.table.id) # here we have some time to read a menu and make a decision about state here or not leaving = choices([False, True], [ 1 - model.restaurant.leaving_probability, model.restaurant.leaving_probability ])[0] if leaving: self.request.state = RequestState.LEAVING_BAD_MENU model.next_events.append( e.Event( model.global_time + expovariate(1 / model.restaurant.thinking_time), LeaveEvent(self.request))) else: self.request.waiting_start_time = model.global_time model.next_events.append( e.Event( model.global_time + expovariate(1 / model.restaurant.thinking_time), w.WaiterEvent(self.request))) else: st.no_seat_counter += 1 logging.info( "%s: Request %d left because there are not free table", human_readable_date_time(model.global_time), self.request.id) people_count = int( choices(list(model.class_probability.keys()), list(model.class_probability.values()))[0]) """ Generating next event in seconds according to current_mean and until last_entrance_time. Increment counter of requests """ next_request_time = expovariate(1 / model.current_request_mean()) if model.global_time < model.restaurant.last_entrance_time \ and model.global_time + next_request_time < model.current_request_interval().toInterval: model.next_events.append( e.Event( model.global_time + next_request_time, RequestEvent( Request(people_count, model.restaurant.reorder_probability, model.global_time + next_request_time))))
mean, error = confidence_interval( [a / b for (a, b) in zip(diffs, avg_total_dish_count)], False) pretty_table.add_row([ "Part of not billed dishes", str(round(mean, 3)) + " ± " + str(round(error, 3)) ]) print(pretty_table) header = ["Cooker id"] header.extend([ utils.human_readable_time(period.fromInterval) + "-" + utils.human_readable_time(period.toInterval) if not period.last else utils.human_readable_time(period.fromInterval) + "-" + utils.human_readable_date_time(rest_model.global_time) for period in rest_model.intervals ]) header.append("Total") pretty_table = PrettyTable(header) for key, value in stats.multi_period_worker_load( stats.cooker_hours, rest_model.global_time).items(): if key <= len(rest_model.restaurant.cookers): row = [key] row.extend(value) row.append( stats.total_worker_load(key, stats.cooker_hours, rest_model.global_time)) pretty_table.add_row(row)