# c3_judge_lines.py — 基準データ v2(段階B・cs:144 の18回)で、自動の判定の線のたたき台を確かめる # 読むだけ。出力は標準出力(c3_judge_lines_output.txt に保存する) # 使い方: cd /Volumes/KIOXIA/r2r2r-data/service_sim/unity && python3 stageB_cs144/c3_judge_lines.py import csv, glob, json, statistics as st, collections RUNS = sorted(glob.glob('20261025_stageB_cs144_*')) LEDGER = 'unity_ledger_v1.csv' def q(xs, p): if not xs: return None xs = sorted(xs); k = (len(xs) - 1) * p f = int(k); c = min(f + 1, len(xs) - 1) return round(xs[f] + (xs[c] - xs[f]) * (k - f), 1) def rows(r, name): return list(csv.DictReader(open(f'{r}/{name}.csv', encoding='utf-8'))) print(f'回数: {len(RUNS)}') # 1. ロボットの止まり(stops.csv): 1回の止まりの長さ allstop = []; stuck_dur = []; cause_long = collections.Counter() per_run_max = [] for r in RUNS: ds = [] for s in rows(r, 'stops'): d = float(s['duration_s'] or 0); ds.append(d); allstop.append(d) if s['resolved'] == '動けなくなった': stuck_dur.append(d) if d >= 30: cause_long[s['cause']] += 1 per_run_max.append((r.split('cs144_')[1], round(max(ds), 1) if ds else 0, sum(d >= 30 for d in ds), sum(d >= 60 for d in ds))) print('\n## 1. ロボットの止まり(1回の長さ・秒)') print(f'件数 {len(allstop)} p50 {q(allstop,.5)} p90 {q(allstop,.9)} p99 {q(allstop,.99)} 最大 {round(max(allstop),1)}') for th in (10, 20, 30, 45, 60, 120): print(f' {th}秒以上: {sum(d>=th for d in allstop)} 件') print(f'「動けなくなった」と記録された止まり: {len(stuck_dur)} 件 p50 {q(stuck_dur,.5)} 最小 {min(stuck_dur) if stuck_dur else None} 最大 {max(stuck_dur) if stuck_dur else None}') print('30秒以上の止まりの原因:', cause_long.most_common()) print('回ごと(条件 / 最長 / 30秒以上 / 60秒以上):') for x in per_run_max: print(' ', *x) # 2. ロボットどうしの向かい合い(yields.csv の ロボット→ロボット) rr = []; rr_over60 = collections.Counter() for r in RUNS: for y in rows(r, 'yields'): if y['who'].startswith('ロボット') and y['to_whom'].startswith('ロボット'): w = float(y['waited_s'] or 0); rr.append(w) if w >= 60: rr_over60[r.split('cs144_')[1]] += 1 print('\n## 2. ロボットどうしで待った長さ(秒)') print(f'件数 {len(rr)} p50 {q(rr,.5)} p90 {q(rr,.9)} p99 {q(rr,.99)} 最大 {round(max(rr),1) if rr else None}') for th in (30, 60, 120): print(f' {th}秒以上: {sum(w>=th for w in rr)} 件') print('60秒以上がある回:', dict(rr_over60)) # 3. 試算とのずれ(台帳の cs:144 の行) L = [x for x in csv.DictReader(open(LEDGER, encoding='utf-8')) if x['unity_cs'] == 'cs:144' and 'stageB' in x['run_folder']] pairs = [('entry_wait_mean', 'site_entry_wait_mean', '入店待ち'), ('dish_wait_mean', 'site_dish_wait_mean', '料理の待ち'), ('staff_wait_mean', 'site_staff_wait_mean', '席は空いているのに待った'), ('robot_share', 'site_robot_share', 'ロボットが運んだ割合')] print('\n## 3. 試算とのずれ(条件ごとに種3つの平均・Unity ÷ 試算)') by = collections.defaultdict(list) for x in L: by[(x['store'], x['robots'])].append(x) for (store, rb), xs in sorted(by.items()): out = [] for u, s, name in pairs: uv = [float(x[u]) for x in xs if x[u] not in ('', None)] sv = [float(x[s]) for x in xs if x[s] not in ('', None)] if not uv or not sv or st.mean(sv) == 0: out.append(f'{name} -'); continue out.append(f'{name} {st.mean(uv):.2f}/{st.mean(sv):.2f}={st.mean(uv)/st.mean(sv):.2f}倍') print(f' {store} ロボット{rb}台: ' + ' | '.join(out)) # 3b. ロボットの台数で入店待ちが変わる向き(0→1→2台) print('\n## 3b. 入店待ちの向き(0→1→2台・種3つの平均)') for store in sorted({k[0] for k in by}): u = [st.mean(float(x['entry_wait_mean']) for x in by[(store, str(n))]) for n in (0, 1, 2)] s = [st.mean(float(x['site_entry_wait_mean']) for x in by[(store, str(n))]) for n in (0, 1, 2)] print(f' {store}: Unity ' + ' → '.join(f'{v:.2f}' for v in u) + ' 試算 ' + ' → '.join(f'{v:.2f}' for v in s)) # 4. 種によるばらつき print('\n## 4. 同じ条件で種を変えたときのばらつき(入店待ち・分)') for (store, rb), xs in sorted(by.items()): v = sorted(float(x['entry_wait_mean']) for x in xs) print(f' {store} ロボット{rb}台: ' + ', '.join(f'{a:.2f}' for a in v) + f' (最大÷最小 {v[-1]/v[0]:.1f}倍)') # 5. 回の健全さ(壊れた回を見つける材料) print('\n## 5. 回の健全さ') for x in L: print(f" {x['run_folder'].split('cs144_')[1]}: status {x['status']} 全員帰った {x['everyone_left']} 実時間 {float(x['wall_seconds'])/60:.1f}分 " f"立ち往生 {x['stuck_count']} 打ち切り {x['timeouts']} 瞬間移動 {x['warped']} 卓を空けた {x['table_freed']} あきらめた {x['gave_up']} 片付け待ち600秒超え {x['clear_wait_over_count']}") peopleskip = [] for r in RUNS: ri = json.load(open(f'{r}/run_info.json', encoding='utf-8')) peopleskip.append((r.split('cs144_')[1], ri['people_step_m']['max'], ri['frames_over_005m'])) print('人の1コマの移動の最大(m)とロボットの0.05m超えのコマ数:', peopleskip)