# v2.6 までは、はじめから「先に空いた方」だったため、受け渡し口の横で待つスタッフが多く運んでいた。 # ロボットが出るときは、受け渡し口で待っている料理を段の数までまとめて載せ、近い卓から回る。 def deliver(self, d, fragile=False, counted=True): env, p = self.env, self.p rec = {"d": d, "ready": env.now, "fragile": fragile, "counted": counted, "taken": env.event(), "arrived": env.event()} robot_ok = self.robots is not None and not (fragile and p["fragile_by"] == "staff") if not robot_ok: sreq = self.staff.request(priority=1) yield sreq yield env.process(self._staff_carry(rec, sreq)) return rreq = self.robots.request(priority=0) rec["sreq"], rec["rreq"] = None, rreq self.pass_q.append(rec) yield rreq | rec["taken"] | env.timeout(p["robot_first_wait_s"]) # まずロボットだけを待つ if not (rreq.triggered or rec["taken"].triggered): # 2分たった:ここからはスタッフも運べる sreq = self.staff.request(priority=1) rec["sreq"] = sreq yield rreq | sreq | rec["taken"] sreq = rec["sreq"] if rec["taken"].triggered: # ほかの料理と一緒にロボットに載った yield rec["arrived"] return self.pass_q.remove(rec) if rreq.triggered: # ロボットが運ぶ if sreq is not None: self._drop(sreq, self.staff) batch = [rec] for o in list(self.pass_q): if len(batch) >= p["trays"]: break self.pass_q.remove(o) self._drop(o["rreq"], self.robots) if o["sreq"] is not None: self._drop(o["sreq"], self.staff) o["taken"].succeed() batch.append(o) env.process(self._robot_run(batch, rreq)) yield rec["arrived"] return rreq.cancel() # スタッフが運ぶ yield env.process(self._staff_carry(rec, sreq)) @staticmethod def _drop(req, res): if req.triggered: res.release(req) else: req.cancel() def _robot_run(self, batch, rreq): env, p = self.env, self.p start = env.now batch.sort(key=lambda r: r["d"]) pos = 0.0 for i, rec in enumerate(batch): leg = rec["d"] - pos + (p["hop_m"] if i else 0) yield env.timeout(max(leg, 0) / p["robot_speed"]) if i == 0: yield env.process(self.maybe_stop(rec["d"])) if rec["counted"]: self.dish_waits.append((env.now - rec["ready"]) / 60) self.robot_trips += 1 self.fragile_robot += rec["fragile"] rec["arrived"].succeed() yield env.timeout(p["robot_handoff_s"]) pos = rec["d"] yield env.timeout(pos / p["robot_speed"]) self.robot_busy_s += env.now - start self.robots.release(rreq) def _staff_carry(self, rec, sreq): env, p = self.env, self.p self.staff_trips += 1 go = rec["d"] / p["staff_speed"] yield env.timeout(go) if rec["counted"]: self.dish_waits.append((env.now - rec["ready"]) / 60) rec["arrived"].succeed() yield env.timeout(p["staff_place_s"] + go) self.staff_walk_m += 2 * rec["d"] self.staff_busy_s += 2 * go + p["staff_place_s"] self.staff.release(sreq) # ---- 巡回回収(食事中にロボットが来て、客が空いた皿を載せる) def collect_visit(self, d, deadline, box): env, p = self.env, self.p req = self.robots.request(priority=2) timer = env.timeout(max(deadline - env.now, 0)) res = yield req | timer if req not in res: req.cancel() return start = env.now t = d / p["robot_speed"] back = (d + p["dish_extra_m"]) / p["robot_speed"] yield env.timeout(t) yield env.process(self.maybe_stop(d)) yield env.timeout(p["guest_load_s"] + back) self.robot_busy_s += env.now - start self.robots.release(req) self.collects += 1 box[0] += 1 # ---- 下げ膳(客が帰ってから卓が使えるようになるまで) def bus(self, d, collected, counted=True): env, p = self.env, self.p start = env.now trips = max(p["bus_trips"] - collected, 0) if p["mode"] == "bus" and self.robots is not None and trips > 0: # v2.4: スタッフの手が空いて片付けに向かうときに、空いているロボットがいれば一緒に卓へ向かわせる with self.staff.request(priority=2) as sreq: yield sreq s0 = env.now rreq = self.robots.request(priority=1) if rreq.triggered: self.robot_bus += 1 arrive, loaded = env.event(), env.event() env.process(self._robot_bus_trip(d, rreq, arrive, loaded)) # スタッフは卓まで行き、皿をまとめ、ロボットが着いたら載せ、卓を拭いて戻る yield env.timeout(d / p["staff_speed"] + p["clear_s"]) yield arrive yield env.timeout(p["load_s"]) loaded.succeed() # 載せ終えた。ロボットは洗い場へ yield env.timeout(p["wipe_s"]) done_at = env.now yield env.timeout(d / p["staff_speed"]) self.staff_walk_m += 2 * d self.staff_busy_s += env.now - s0 if counted: self.turns.append((done_at - start) / 60) return rreq.cancel() # 空いているロボットがいない:そのまま人だけで下げる(皿の量に応じて往復) self.staff_bus += 1 walk = 2 * (d + p["dish_extra_m"]) * trips tt = walk / p["staff_speed"] + p["clear_s"] + p["wipe_s"] self.staff_walk_m += walk self.staff_busy_s += tt yield env.timeout(tt) if counted: self.turns.append((env.now - start) / 60) return # 人だけで下げる(皿の量に応じて往復)。皿がもうなければ拭きに行くだけ self.staff_bus += 1 walk = 2 * (d + p["dish_extra_m"]) * trips if trips > 0 else 2 * d work = (p["clear_s"] if trips > 0 else 0) + p["wipe_s"] yield env.process(self.staff_job(2, walk, work)) if counted: self.turns.append((env.now - start) / 60) def _robot_bus_trip(self, d, rreq, arrive, loaded): env, p = self.env, self.p start = env.now t = d / p["robot_speed"] yield env.timeout(t) yield env.process(self.maybe_stop(d, helper_waiting=True)) arrive.succeed() yield loaded # v2.4: スタッフが載せ終えるまで卓で待つ back = (d + p["dish_extra_m"]) / p["robot_speed"] yield env.timeout(back + 15) # 洗い場で降ろすのは厨房側 self.robot_busy_s += env.now - start self.robots.release(rreq) # ---- 1組の流れ def group(self): env, p, rng = self.env, self.p, self.rng arrive = env.now counted = (self.t0 <= arrive < self.t1) if p["sustain"] else True # 数える時間に来た組だけを記録する if counted: self.counted += 1 # 図面のある店は卓ごとの距離から選ぶ。ほかの店は今までどおり(乱数の使い方も同じ) d = rng.choice(p["dist_list"]) if p.get("dist_list") else rng.uniform(*p["dist_m"]) self._acc() table = self.tables.request() yield table blocked = self.blocked.pop(table, 0.0) if counted: w = (env.now - arrive) / 60 self.seat_waits.append(w) self.seat_wait_by_arrival.append((arrive, w)) # 案内(v2.4): スタッフの手が空いたら卓まで一緒に歩く。卓に着いた時点を「席に着いた」とする。 # 説明(guide_s)と受け渡し口への戻りは、スタッフの手がふさがる時間には入れ、お客さんの注文はそのあとに進む(v2.2 と同じ) g0 = env.now with self.staff.request(priority=1) as greq: yield greq gwait = env.now - g0 yield env.timeout(d / p["staff_speed"]) seated_at = env.now yield env.timeout(p["guide_s"] + d / p["staff_speed"]) self.staff_walk_m += 2 * d self.staff_busy_s += 2 * d / p["staff_speed"] + p["guide_s"] if counted: self.blocked_waits.append(blocked / 60) self.guide_waits.append(gwait / 60) self.entry_waits.append((seated_at - arrive) / 60) firsts = [env.process(self._cook_then_deliver(c, d, False, counted)) for c in p["first_dishes"]] yield simpy.AllOf(env, firsts) eat = tri(rng, p["eat_min"]) * 60 end = env.now + eat # 食事中の追加注文・呼び出し・巡回回収 extras = [] for _ in range(p["extra_orders"]): extras.append(env.process(self._order_later(rng.uniform(0, eat * 0.7), d, p["extra_cook"], False, counted))) n_fragile = int(p["fragile"]) + (rng.random() < p["fragile"] - int(p["fragile"])) for _ in range(n_fragile): extras.append(env.process(self._order_later(rng.uniform(0.3, 0.85) * eat, d, p["fragile_cook"], True, counted))) calls = sum(rng.random() < p["call_rate"] / 2 for _ in range(2)) for _ in range(calls): env.process(self._call_later(rng.uniform(0, eat), d)) collected = [0] if p["mode"] == "patrol" and self.robots is not None: for k in range(p["bus_trips"]): at = eat * (k + 1) / (p["bus_trips"] + 1) env.process(self._collect_later(at, d, end, collected)) yield env.timeout(eat) if extras: yield simpy.AllOf(env, extras) yield env.process(self.staff_job(1, 0, p["pay_s"])) self._acc() self.dirty += 1 # 客は帰った。片付けが済むまで卓は使えない yield env.process(self.bus(d, collected[0], counted)) self._acc() self.dirty -= 1 self.tables.release(table) self.done += 1 if counted: self.counted_done += 1 self._check_finished() def _cook_then_deliver(self, cook, d, fragile=False, counted=True): yield self.env.timeout(tri(self.rng, cook) * 60) yield self.env.process(self.deliver(d, fragile, counted)) def _order_later(self, delay, d, cook, fragile, counted=True): yield self.env.timeout(delay) yield self.env.process(self._cook_then_deliver(cook, d, fragile, counted)) def _call_later(self, delay, d): yield self.env.timeout(delay) yield self.env.process(self.staff_job(1, 2 * d, self.p["call_s"])) def _collect_later(self, delay, d, deadline, box): yield self.env.timeout(delay) yield self.env.process(self.collect_visit(d, deadline, box))