evaluation.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. # evaluation.py
  2. # Run evaluation and plot figures
  3. import math
  4. import os
  5. import pickle
  6. import time
  7. import shutil
  8. import matplotlib.pyplot as plt
  9. import numpy as np
  10. from cycler import cycler
  11. from algorithms import benchmark_using_algorithm # may be used elsewhere
  12. from network import QuantumNetwork
  13. from schedulers import run_scheduler # パッケージ化したものを使う
  14. # ---- Matplotlib style (IEEE-ish) ----
  15. plt.rc("font", family="Times New Roman")
  16. plt.rc("font", size=20)
  17. default_cycler = (
  18. cycler(color=["#4daf4a", "#377eb8", "#e41a1c", "#984ea3", "#ff7f00", "#a65628"])
  19. + cycler(marker=["s", "v", "o", "x", "*", "+"])
  20. + cycler(linestyle=[":", "--", "-", "-.", "--", ":"])
  21. )
  22. plt.rc("axes", prop_cycle=default_cycler)
  23. # =========================
  24. # Fidelity generators
  25. # =========================
  26. def generate_fidelity_list_avg_gap(path_num):
  27. result = []
  28. fidelity_max = 1
  29. fidelity_min = 0.9
  30. gap = (fidelity_max - fidelity_min) / path_num
  31. fidelity = fidelity_max
  32. for _ in range(path_num):
  33. result.append(fidelity)
  34. fidelity -= gap
  35. assert len(result) == path_num
  36. return result
  37. def generate_fidelity_list_fix_gap(path_num, gap, fidelity_max=1):
  38. result = []
  39. fidelity = fidelity_max
  40. for _ in range(path_num):
  41. result.append(fidelity)
  42. fidelity -= gap
  43. assert len(result) == path_num
  44. return result
  45. def generate_fidelity_list_random(path_num, alpha=0.95, beta=0.85, variance=0.1):
  46. """Generate `path_num` links.
  47. u_1 = alpha, u_i = beta for all i = 2, 3, ..., n.
  48. Fidelity_i ~ N(u_i, variance), clipped to [0.8, 1].
  49. Ensure the top-1 gap is large enough (> 0.02) for termination guarantees.
  50. """
  51. while True:
  52. mean = [alpha] + [beta] * (path_num - 1)
  53. result = []
  54. for i in range(path_num):
  55. mu = mean[i]
  56. # Sample a Gaussian random variable and make sure its value is in the valid range
  57. while True:
  58. r = np.random.normal(mu, variance)
  59. # Depolarizing noise and amplitude damping noise models require fidelity >= 0.5
  60. # Be conservative: require >= 0.8
  61. if 0.8 <= r <= 1.0:
  62. break
  63. result.append(r)
  64. assert len(result) == path_num
  65. sorted_res = sorted(result, reverse=True)
  66. # To guarantee the termination of algorithms, we require that the gap is large enough
  67. if sorted_res[0] - sorted_res[1] > 0.02:
  68. return result
  69. # =========================
  70. # Progress helpers (LinkSelfie風)
  71. # =========================
  72. def _start_timer():
  73. return {"t0": time.time(), "last": time.time()}
  74. def _tick(timer):
  75. now = time.time()
  76. dt_total = now - timer["t0"]
  77. dt_step = now - timer["last"]
  78. timer["last"] = now
  79. return dt_total, dt_step
  80. def _log(msg):
  81. print(msg, flush=True)
  82. # =========================
  83. # Plots
  84. # =========================
  85. def plot_accuracy_vs_budget(
  86. budget_list, # e.g., [1000, 2000, 3000, ...] (x-axis)
  87. scheduler_names, # e.g., ["LNaive", "Greedy", ...]
  88. noise_model, # e.g., "Depolar"
  89. node_path_list, # e.g., [5, 5, 5]
  90. importance_list, # e.g., [0.4, 0.7, 1.0] (not used here, but kept for interface)
  91. bounces=(1, 2, 3, 4),
  92. repeat=10,
  93. verbose=True,
  94. print_every=1,
  95. ):
  96. file_name = f"plot_accuracy_vs_budget_{noise_model}"
  97. root_dir = os.path.dirname(os.path.abspath(__file__))
  98. output_dir = os.path.join(root_dir, "outputs")
  99. os.makedirs(output_dir, exist_ok=True)
  100. file_path = os.path.join(output_dir, f"{file_name}.pickle")
  101. if os.path.exists(file_path):
  102. _log("Pickle data exists, skip simulation and plot the data directly.")
  103. _log("To rerun, delete the pickle in `outputs`.")
  104. with open(file_path, "rb") as f:
  105. payload = pickle.load(f)
  106. budget_list = payload["budget_list"]
  107. results = payload["results"]
  108. else:
  109. results = {name: {"accs": [[] for _ in budget_list]} for name in scheduler_names}
  110. for k, C_total in enumerate(budget_list):
  111. timer = _start_timer()
  112. if verbose:
  113. _log(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===")
  114. for r in range(repeat):
  115. if verbose and ((r + 1) % print_every == 0 or r == 0):
  116. _log(f" [repeat {r+1}/{repeat}] generating topology …")
  117. # 1リピート = 1トポロジ(全スケジューラで共有)
  118. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  119. def network_generator(path_num, pair_idx):
  120. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  121. for name in scheduler_names:
  122. if verbose and ((r + 1) % print_every == 0 or r == 0):
  123. _log(f" - {name}: running …")
  124. per_pair_results, _ = run_scheduler(
  125. node_path_list=node_path_list,
  126. importance_list=importance_list,
  127. scheduler_name=name,
  128. bounces=list(bounces),
  129. C_total=int(C_total),
  130. network_generator=network_generator,
  131. )
  132. acc = (
  133. float(np.mean([1.0 if c else 0.0 for (c, _cost, _bf) in per_pair_results]))
  134. if per_pair_results
  135. else 0.0
  136. )
  137. results[name]["accs"][k].append(acc)
  138. if verbose and ((r + 1) % print_every == 0 or r == 0):
  139. _log(f" -> acc={acc:.3f}")
  140. if verbose:
  141. tot, _ = _tick(timer)
  142. _log(f"=== done Budget={C_total} | elapsed {tot:.1f}s ===")
  143. with open(file_path, "wb") as f:
  144. pickle.dump({"budget_list": list(budget_list), "results": results}, f)
  145. # --- Plot ---
  146. plt.rc("axes", prop_cycle=default_cycler)
  147. fig, ax = plt.subplots()
  148. x = list(budget_list)
  149. for name, data in results.items():
  150. avg_accs = [float(np.mean(v)) if v else 0.0 for v in data["accs"]]
  151. label = name.replace("Vanilla NB", "VanillaNB").replace("Succ. Elim. NB", "SuccElimNB")
  152. ax.plot(x, avg_accs, linewidth=2.0, label=label)
  153. ax.set_xlabel("Total Budget (C)")
  154. ax.set_ylabel("Average Correctness")
  155. ax.grid(True)
  156. ax.legend(title="Scheduler", fontsize=14, title_fontsize=18)
  157. plt.tight_layout()
  158. pdf_name = f"{file_name}.pdf"
  159. plt.savefig(pdf_name)
  160. if shutil.which("pdfcrop"):
  161. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  162. _log(f"Saved: {pdf_name}")
  163. def plot_value_vs_used(
  164. budget_list,
  165. scheduler_names,
  166. noise_model,
  167. node_path_list,
  168. importance_list,
  169. bounces=(1, 2, 3, 4),
  170. repeat=10,
  171. verbose=True,
  172. print_every=1,
  173. ):
  174. """x = 実コスト平均(used)で描く版。旧 plot_value_vs_budget と同等の挙動。"""
  175. file_name = f"plot_value_vs_used_{noise_model}"
  176. root_dir = os.path.dirname(os.path.abspath(__file__))
  177. output_dir = os.path.join(root_dir, "outputs")
  178. os.makedirs(output_dir, exist_ok=True)
  179. results = {
  180. name: {"values": [[] for _ in budget_list], "costs": [[] for _ in budget_list]}
  181. for name in scheduler_names
  182. }
  183. for k, C_total in enumerate(budget_list):
  184. timer = _start_timer()
  185. if verbose:
  186. _log(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===")
  187. # 1リピート = 1トポロジ(全スケジューラで共有)
  188. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  189. def network_generator(path_num, pair_idx):
  190. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  191. for r in range(repeat):
  192. if verbose and ((r + 1) % print_every == 0 or r == 0):
  193. _log(f" [repeat {r+1}/{repeat}]")
  194. for name in scheduler_names:
  195. if verbose and ((r + 1) % print_every == 0 or r == 0):
  196. _log(f" - {name}: running …")
  197. per_pair_results, total_cost, per_pair_details = run_scheduler(
  198. node_path_list=node_path_list,
  199. importance_list=importance_list,
  200. scheduler_name=name,
  201. bounces=list(bounces),
  202. C_total=int(C_total),
  203. network_generator=network_generator,
  204. return_details=True,
  205. )
  206. # 価値の合成
  207. value = 0.0
  208. for d, details in enumerate(per_pair_details):
  209. alloc = details.get("alloc_by_path", {})
  210. est = details.get("est_fid_by_path", {})
  211. inner = sum(float(est.get(l, 0.0)) * int(b) for l, b in alloc.items())
  212. value += float(importance_list[d]) * inner
  213. results[name]["values"][k].append(float(value))
  214. results[name]["costs"][k].append(int(total_cost))
  215. if verbose and ((r + 1) % print_every == 0 or r == 0):
  216. _log(f" -> used={total_cost}, value={value:.2f}")
  217. if verbose:
  218. tot, _ = _tick(timer)
  219. _log(f"=== done Budget={C_total} | elapsed {tot:.1f}s ===")
  220. # --- Plot (x = 実コスト平均) ---
  221. plt.rc("axes", prop_cycle=default_cycler)
  222. fig, ax = plt.subplots()
  223. for name, data in results.items():
  224. xs = [float(np.mean(v)) if v else 0.0 for v in data["costs"]]
  225. ys = [float(np.mean(v)) if v else 0.0 for v in data["values"]]
  226. ax.plot(xs, ys, linewidth=2.0, marker="o", label=name)
  227. ax.set_xlabel("Total Measured Cost (used)")
  228. ax.set_ylabel("Total Value (Σ I_d Σ f̂_{d,l}·B_{d,l})")
  229. ax.grid(True)
  230. ax.legend(title="Scheduler")
  231. plt.tight_layout()
  232. pdf_name = f"{file_name}.pdf"
  233. plt.savefig(pdf_name)
  234. if shutil.which("pdfcrop"):
  235. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  236. _log(f"Saved: {pdf_name}")
  237. def plot_value_vs_budget_target(
  238. budget_list,
  239. scheduler_names,
  240. noise_model,
  241. node_path_list,
  242. importance_list,
  243. bounces=(1, 2, 3, 4),
  244. repeat=10,
  245. verbose=True,
  246. print_every=1,
  247. ):
  248. """x = 目標予算(指定した budget_list をそのまま x 軸に)で描く版。"""
  249. file_name = f"plot_value_vs_budget_target_{noise_model}"
  250. root_dir = os.path.dirname(os.path.abspath(__file__))
  251. output_dir = os.path.join(root_dir, "outputs")
  252. os.makedirs(output_dir, exist_ok=True)
  253. results = {
  254. name: {"values": [[] for _ in budget_list], "costs": [[] for _ in budget_list]}
  255. for name in scheduler_names
  256. }
  257. for k, C_total in enumerate(budget_list):
  258. timer = _start_timer()
  259. if verbose:
  260. _log(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===")
  261. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  262. def network_generator(path_num, pair_idx):
  263. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  264. for r in range(repeat):
  265. if verbose and ((r + 1) % print_every == 0 or r == 0):
  266. _log(f" [repeat {r+1}/{repeat}]")
  267. for name in scheduler_names:
  268. if verbose and ((r + 1) % print_every == 0 or r == 0):
  269. _log(f" - {name}: running …")
  270. per_pair_results, total_cost, per_pair_details = run_scheduler(
  271. node_path_list=node_path_list,
  272. importance_list=importance_list,
  273. scheduler_name=name,
  274. bounces=list(bounces),
  275. C_total=int(C_total),
  276. network_generator=network_generator,
  277. return_details=True,
  278. )
  279. value = 0.0
  280. for d, details in enumerate(per_pair_details):
  281. alloc = details.get("alloc_by_path", {})
  282. est = details.get("est_fid_by_path", {})
  283. inner = sum(float(est.get(l, 0.0)) * int(b) for l, b in alloc.items())
  284. value += float(importance_list[d]) * inner
  285. results[name]["values"][k].append(float(value))
  286. results[name]["costs"][k].append(int(total_cost))
  287. if verbose and ((r + 1) % print_every == 0 or r == 0):
  288. _log(f" -> used={total_cost}, value={value:.2f}")
  289. if verbose:
  290. tot, _ = _tick(timer)
  291. _log(f"=== done Budget={C_total} | elapsed {tot:.1f}s ===")
  292. # --- Plot (x = 目標予算) ---
  293. plt.rc("axes", prop_cycle=default_cycler)
  294. fig, ax = plt.subplots()
  295. x = list(budget_list)
  296. for name, data in results.items():
  297. ys = [float(np.mean(v)) if v else 0.0 for v in data["values"]]
  298. ax.plot(x, ys, linewidth=2.0, marker="o", label=name)
  299. ax.set_xlabel("Budget (target)")
  300. ax.set_ylabel("Total Value (Σ I_d Σ f̂_{d,l}·B_{d,l})")
  301. ax.grid(True)
  302. ax.legend(title="Scheduler")
  303. plt.tight_layout()
  304. pdf_name = f"{file_name}.pdf"
  305. plt.savefig(pdf_name)
  306. if shutil.which("pdfcrop"):
  307. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  308. _log(f"Saved: {pdf_name}")
  309. # =========================
  310. # CI width helpers and plots
  311. # =========================
  312. def _ci_radius_hoeffding(n: int, delta: float = 0.1) -> float:
  313. if n <= 0:
  314. return 1.0
  315. return math.sqrt(0.5 * math.log(2.0 / delta) / n)
  316. # =========================
  317. # Width-sum metrics (new)
  318. # =========================
  319. def _sum_widths_all_links(per_pair_details, delta: float = 0.1) -> float:
  320. """
  321. すべてのペア・すべてのリンクについて、(UB - LB) を合計。
  322. est が無いリンクはスキップ(=寄与0)。測定していないリンクは数えません。
  323. """
  324. total = 0.0
  325. for det in per_pair_details:
  326. alloc = det.get("alloc_by_path", {}) # n = 測定回数
  327. est = det.get("est_fid_by_path", {}) # 標本平均
  328. for pid, m in est.items():
  329. n = int(alloc.get(pid, 0))
  330. rad = _ci_radius_hoeffding(n, delta)
  331. lb = max(0.0, float(m) - rad)
  332. ub = min(1.0, float(m) + rad)
  333. total += (ub - lb)
  334. return float(total)
  335. def _sum_min_widths_per_pair(per_pair_details, delta: float = 0.1) -> float:
  336. """
  337. ペアごとにリンクの (UB - LB) を算出し、その「最小値」を取り、全ペアで合計。
  338. est が空のペアは保守的に 1.0 を加算(“全く分からない”幅として扱う)。
  339. """
  340. s = 0.0
  341. for det in per_pair_details:
  342. alloc = det.get("alloc_by_path", {})
  343. est = det.get("est_fid_by_path", {})
  344. if not est:
  345. s += 1.0
  346. continue
  347. widths = []
  348. for pid, m in est.items():
  349. n = int(alloc.get(pid, 0))
  350. rad = _ci_radius_hoeffding(n, delta)
  351. lb = max(0.0, float(m) - rad)
  352. ub = min(1.0, float(m) + rad)
  353. widths.append(ub - lb)
  354. s += (min(widths) if widths else 1.0)
  355. return float(s)
  356. def plot_widthsum_alllinks_vs_budget(
  357. budget_list,
  358. scheduler_names,
  359. noise_model,
  360. node_path_list,
  361. importance_list,
  362. bounces=(1, 2, 3, 4),
  363. repeat=10,
  364. delta=0.1,
  365. verbose=True,
  366. print_every=1,
  367. ):
  368. """
  369. y = 全リンク(UB-LB)総和 の平均 ±95%CI を、x = 目標予算 で描画。
  370. 生データは outputs/plot_widthsum_alllinks_vs_budget_*.pickle に保存。
  371. """
  372. file_name = f"plot_widthsum_alllinks_vs_budget_{noise_model}"
  373. root_dir = os.path.dirname(os.path.abspath(__file__))
  374. output_dir = os.path.join(root_dir, "outputs")
  375. os.makedirs(output_dir, exist_ok=True)
  376. results = {name: {"sums": [[] for _ in budget_list]} for name in scheduler_names}
  377. for k, C_total in enumerate(budget_list):
  378. if verbose:
  379. print(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===", flush=True)
  380. # 1リピート=1トポロジ(全スケジューラ共有)
  381. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  382. def network_generator(path_num, pair_idx):
  383. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  384. for r in range(repeat):
  385. if verbose and ((r + 1) % print_every == 0 or r == 0):
  386. print(f" [repeat {r+1}/{repeat}]", flush=True)
  387. for name in scheduler_names:
  388. per_pair_results, total_cost, per_pair_details = run_scheduler(
  389. node_path_list=node_path_list,
  390. importance_list=importance_list,
  391. scheduler_name=name,
  392. bounces=list(bounces),
  393. C_total=int(C_total),
  394. network_generator=network_generator,
  395. return_details=True,
  396. )
  397. v = _sum_widths_all_links(per_pair_details, delta=delta)
  398. results[name]["sums"][k].append(v)
  399. if verbose and ((r + 1) % print_every == 0 or r == 0):
  400. print(f" - {name}: sum_alllinks={v:.4f} (used={total_cost})", flush=True)
  401. # --- Save raw data (.pickle) ---
  402. file_path = os.path.join(output_dir, f"{file_name}.pickle")
  403. with open(file_path, "wb") as f:
  404. pickle.dump({"budget_list": list(budget_list), "results": results}, f)
  405. print(f"Saved pickle: {file_path}")
  406. # --- Plot mean ± 95% CI across repeats ---
  407. plt.rc("axes", prop_cycle=default_cycler)
  408. fig, ax = plt.subplots()
  409. x = list(budget_list)
  410. for name, data in results.items():
  411. means, halfs = [], []
  412. for vals in data["sums"]:
  413. m, h = mean_ci95(vals)
  414. means.append(m); halfs.append(h)
  415. means = np.asarray(means); halfs = np.asarray(halfs)
  416. ax.plot(x, means, linewidth=2.0, marker="o", label=name)
  417. ax.fill_between(x, means - halfs, means + halfs, alpha=0.25)
  418. ax.set_xlabel("Budget (target)")
  419. ax.set_ylabel("Sum of (UB - LB) over all links")
  420. ax.grid(True)
  421. ax.legend(title="Scheduler")
  422. plt.tight_layout()
  423. pdf_name = f"{file_name}.pdf"
  424. plt.savefig(pdf_name)
  425. if shutil.which("pdfcrop"):
  426. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  427. print(f"Saved: {pdf_name}")
  428. def plot_minwidthsum_perpair_vs_budget(
  429. budget_list,
  430. scheduler_names,
  431. noise_model,
  432. node_path_list,
  433. importance_list,
  434. bounces=(1, 2, 3, 4),
  435. repeat=10,
  436. delta=0.1,
  437. verbose=True,
  438. print_every=1,
  439. ):
  440. """
  441. y = ペアごとの (UB-LB) 最小値の合計 の平均 ±95%CI、x = 目標予算。
  442. 生データは outputs/plot_minwidthsum_perpair_vs_budget_*.pickle に保存。
  443. """
  444. file_name = f"plot_minwidthsum_perpair_vs_budget_{noise_model}"
  445. root_dir = os.path.dirname(os.path.abspath(__file__))
  446. output_dir = os.path.join(root_dir, "outputs")
  447. os.makedirs(output_dir, exist_ok=True)
  448. results = {name: {"sums": [[] for _ in budget_list]} for name in scheduler_names}
  449. for k, C_total in enumerate(budget_list):
  450. if verbose:
  451. print(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===", flush=True)
  452. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  453. def network_generator(path_num, pair_idx):
  454. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  455. for r in range(repeat):
  456. if verbose and ((r + 1) % print_every == 0 or r == 0):
  457. print(f" [repeat {r+1}/{repeat}]", flush=True)
  458. for name in scheduler_names:
  459. per_pair_results, total_cost, per_pair_details = run_scheduler(
  460. node_path_list=node_path_list,
  461. importance_list=importance_list,
  462. scheduler_name=name,
  463. bounces=list(bounces),
  464. C_total=int(C_total),
  465. network_generator=network_generator,
  466. return_details=True,
  467. )
  468. v = _sum_min_widths_per_pair(per_pair_details, delta=delta)
  469. results[name]["sums"][k].append(v)
  470. if verbose and ((r + 1) % print_every == 0 or r == 0):
  471. print(f" - {name}: sum_min_perpair={v:.4f} (used={total_cost})", flush=True)
  472. # --- Save raw data (.pickle) ---
  473. file_path = os.path.join(output_dir, f"{file_name}.pickle")
  474. with open(file_path, "wb") as f:
  475. pickle.dump({"budget_list": list(budget_list), "results": results}, f)
  476. print(f"Saved pickle: {file_path}")
  477. # --- Plot mean ± 95% CI across repeats ---
  478. plt.rc("axes", prop_cycle=default_cycler)
  479. fig, ax = plt.subplots()
  480. x = list(budget_list)
  481. for name, data in results.items():
  482. means, halfs = [], []
  483. for vals in data["sums"]:
  484. m, h = mean_ci95(vals)
  485. means.append(m); halfs.append(h)
  486. means = np.asarray(means); halfs = np.asarray(halfs)
  487. ax.plot(x, means, linewidth=2.0, marker="o", label=name)
  488. ax.fill_between(x, means - halfs, means + halfs, alpha=0.25)
  489. ax.set_xlabel("Budget (target)")
  490. ax.set_ylabel("Sum over pairs of min (UB - LB)")
  491. ax.grid(True)
  492. ax.legend(title="Scheduler")
  493. plt.tight_layout()
  494. pdf_name = f"{file_name}.pdf"
  495. plt.savefig(pdf_name)
  496. if shutil.which("pdfcrop"):
  497. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  498. print(f"Saved: {pdf_name}")
  499. # =========================
  500. # Weighted width-sum metrics (add-on)
  501. # =========================
  502. def _sum_weighted_widths_all_links(per_pair_details, importance_list, delta: float = 0.1) -> float:
  503. """
  504. すべてのペア・すべてのリンクの (UB-LB) に、ペア重要度 I_d を掛けて合計。
  505. importance_list[d] が無ければ I_d=1.0 として扱う。
  506. """
  507. total = 0.0
  508. for d, det in enumerate(per_pair_details):
  509. I = float(importance_list[d]) if d < len(importance_list) else 1.0
  510. alloc = det.get("alloc_by_path", {})
  511. est = det.get("est_fid_by_path", {})
  512. for pid, m in est.items():
  513. n = int(alloc.get(pid, 0))
  514. rad = _ci_radius_hoeffding(n, delta)
  515. lb = max(0.0, float(m) - rad)
  516. ub = min(1.0, float(m) + rad)
  517. total += I * (ub - lb)
  518. return float(total)
  519. def _sum_weighted_min_widths_per_pair(per_pair_details, importance_list, delta: float = 0.1) -> float:
  520. """
  521. ペア d ごとに min_l (UB-LB) を計算し、I_d を掛けて全ペアで合計。
  522. est が空のペアは保守的に幅=1.0 として I_d*1.0 を加算。
  523. """
  524. s = 0.0
  525. for d, det in enumerate(per_pair_details):
  526. I = float(importance_list[d]) if d < len(importance_list) else 1.0
  527. alloc = det.get("alloc_by_path", {})
  528. est = det.get("est_fid_by_path", {})
  529. if not est:
  530. s += I * 1.0
  531. continue
  532. widths = []
  533. for pid, m in est.items():
  534. n = int(alloc.get(pid, 0))
  535. rad = _ci_radius_hoeffding(n, delta)
  536. lb = max(0.0, float(m) - rad)
  537. ub = min(1.0, float(m) + rad)
  538. widths.append(ub - lb)
  539. s += I * (min(widths) if widths else 1.0)
  540. return float(s)
  541. def plot_widthsum_alllinks_weighted_vs_budget(
  542. budget_list,
  543. scheduler_names,
  544. noise_model,
  545. node_path_list,
  546. importance_list,
  547. bounces=(1, 2, 3, 4),
  548. repeat=10,
  549. delta=0.1,
  550. verbose=True,
  551. print_every=1,
  552. ):
  553. """
  554. y = Σ_d Σ_l I_d·(UB-LB) の平均 ±95%CI、x = 目標予算。
  555. 生データは outputs/plot_widthsum_alllinks_weighted_vs_budget_*.pickle に保存。
  556. """
  557. file_name = f"plot_widthsum_alllinks_weighted_vs_budget_{noise_model}"
  558. root_dir = os.path.dirname(os.path.abspath(__file__))
  559. output_dir = os.path.join(root_dir, "outputs")
  560. os.makedirs(output_dir, exist_ok=True)
  561. results = {name: {"sums": [[] for _ in budget_list]} for name in scheduler_names}
  562. for k, C_total in enumerate(budget_list):
  563. if verbose:
  564. print(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===", flush=True)
  565. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  566. def network_generator(path_num, pair_idx):
  567. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  568. for r in range(repeat):
  569. if verbose and ((r + 1) % print_every == 0 or r == 0):
  570. print(f" [repeat {r+1}/{repeat}]", flush=True)
  571. for name in scheduler_names:
  572. per_pair_results, total_cost, per_pair_details = run_scheduler(
  573. node_path_list=node_path_list,
  574. importance_list=importance_list,
  575. scheduler_name=name,
  576. bounces=list(bounces),
  577. C_total=int(C_total),
  578. network_generator=network_generator,
  579. return_details=True,
  580. )
  581. v = _sum_weighted_widths_all_links(per_pair_details, importance_list, delta=delta)
  582. results[name]["sums"][k].append(v)
  583. if verbose and ((r + 1) % print_every == 0 or r == 0):
  584. print(f" - {name}: wsum_alllinks={v:.4f} (used={total_cost})", flush=True)
  585. # --- Save raw data (.pickle) ---
  586. file_path = os.path.join(output_dir, f"{file_name}.pickle")
  587. with open(file_path, "wb") as f:
  588. pickle.dump({"budget_list": list(budget_list), "results": results}, f)
  589. print(f"Saved pickle: {file_path}")
  590. # --- Plot mean ± 95% CI ---
  591. plt.rc("axes", prop_cycle=default_cycler)
  592. fig, ax = plt.subplots()
  593. x = list(budget_list)
  594. for name, data in results.items():
  595. means, halfs = [], []
  596. for vals in data["sums"]:
  597. m, h = mean_ci95(vals)
  598. means.append(m); halfs.append(h)
  599. means = np.asarray(means); halfs = np.asarray(halfs)
  600. ax.plot(x, means, linewidth=2.0, marker="o", label=name)
  601. ax.fill_between(x, means - halfs, means + halfs, alpha=0.25)
  602. ax.set_xlabel("Budget (target)")
  603. ax.set_ylabel("Weighted sum of (UB - LB) over all links (× I_d)")
  604. ax.grid(True); ax.legend(title="Scheduler")
  605. plt.tight_layout()
  606. pdf_name = f"{file_name}.pdf"
  607. plt.savefig(pdf_name)
  608. if shutil.which("pdfcrop"):
  609. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  610. print(f"Saved: {pdf_name}")
  611. def plot_minwidthsum_perpair_weighted_vs_budget(
  612. budget_list,
  613. scheduler_names,
  614. noise_model,
  615. node_path_list,
  616. importance_list,
  617. bounces=(1, 2, 3, 4),
  618. repeat=10,
  619. delta=0.1,
  620. verbose=True,
  621. print_every=1,
  622. ):
  623. """
  624. y = Σ_d I_d·min_l(UB-LB) の平均 ±95%CI、x = 目標予算。
  625. 生データは outputs/plot_minwidthsum_perpair_weighted_vs_budget_*.pickle に保存。
  626. """
  627. file_name = f"plot_minwidthsum_perpair_weighted_vs_budget_{noise_model}"
  628. root_dir = os.path.dirname(os.path.abspath(__file__))
  629. output_dir = os.path.join(root_dir, "outputs")
  630. os.makedirs(output_dir, exist_ok=True)
  631. results = {name: {"sums": [[] for _ in budget_list]} for name in scheduler_names}
  632. for k, C_total in enumerate(budget_list):
  633. if verbose:
  634. print(f"\n=== [{noise_model}] Budget={C_total} ({k+1}/{len(budget_list)}) ===", flush=True)
  635. fidelity_bank = [generate_fidelity_list_random(n) for n in node_path_list]
  636. def network_generator(path_num, pair_idx):
  637. return QuantumNetwork(path_num, fidelity_bank[pair_idx], noise_model)
  638. for r in range(repeat):
  639. if verbose and ((r + 1) % print_every == 0 or r == 0):
  640. print(f" [repeat {r+1}/{repeat}]", flush=True)
  641. for name in scheduler_names:
  642. per_pair_results, total_cost, per_pair_details = run_scheduler(
  643. node_path_list=node_path_list,
  644. importance_list=importance_list,
  645. scheduler_name=name,
  646. bounces=list(bounces),
  647. C_total=int(C_total),
  648. network_generator=network_generator,
  649. return_details=True,
  650. )
  651. v = _sum_weighted_min_widths_per_pair(per_pair_details, importance_list, delta=delta)
  652. results[name]["sums"][k].append(v)
  653. if verbose and ((r + 1) % print_every == 0 or r == 0):
  654. print(f" - {name}: wsum_min_perpair={v:.4f} (used={total_cost})", flush=True)
  655. # --- Save raw data (.pickle) ---
  656. file_path = os.path.join(output_dir, f"{file_name}.pickle")
  657. with open(file_path, "wb") as f:
  658. pickle.dump({"budget_list": list(budget_list), "results": results}, f)
  659. print(f"Saved pickle: {file_path}")
  660. # --- Plot mean ± 95% CI ---
  661. plt.rc("axes", prop_cycle=default_cycler)
  662. fig, ax = plt.subplots()
  663. x = list(budget_list)
  664. for name, data in results.items():
  665. means, halfs = [], []
  666. for vals in data["sums"]:
  667. m, h = mean_ci95(vals)
  668. means.append(m); halfs.append(h)
  669. means = np.asarray(means); halfs = np.asarray(halfs)
  670. ax.plot(x, means, linewidth=2.0, marker="o", label=name)
  671. ax.fill_between(x, means - halfs, means + halfs, alpha=0.25)
  672. ax.set_xlabel("Budget (target)")
  673. ax.set_ylabel("Weighted sum over pairs of min (UB - LB) (× I_d)")
  674. ax.grid(True); ax.legend(title="Scheduler")
  675. plt.tight_layout()
  676. pdf_name = f"{file_name}.pdf"
  677. plt.savefig(pdf_name)
  678. if shutil.which("pdfcrop"):
  679. os.system(f"pdfcrop {pdf_name} {pdf_name}")
  680. print(f"Saved: {pdf_name}")
  681. # =========================
  682. # 95%CI helpers (repeats 可変対応)
  683. # =========================
  684. # 小 n 用の簡易表(両側95%、df = n-1)
  685. _T95 = {
  686. 1: 12.706,
  687. 2: 4.303,
  688. 3: 3.182,
  689. 4: 2.776,
  690. 5: 2.571,
  691. 6: 2.447,
  692. 7: 2.365,
  693. 8: 2.306,
  694. 9: 2.262,
  695. 10: 2.228,
  696. 11: 2.201,
  697. 12: 2.179,
  698. 13: 2.160,
  699. 14: 2.145,
  700. 15: 2.131,
  701. 16: 2.120,
  702. 17: 2.110,
  703. 18: 2.101,
  704. 19: 2.093,
  705. 20: 2.086,
  706. 21: 2.080,
  707. 22: 2.074,
  708. 23: 2.069,
  709. 24: 2.064,
  710. 25: 2.060,
  711. 26: 2.056,
  712. 27: 2.052,
  713. 28: 2.048,
  714. 29: 2.045,
  715. }
  716. def tcrit_95(n: int) -> float:
  717. """repeats=n に対する両側95% t臨界値 (df=n-1)。n<2 は 0 を返す。"""
  718. if n <= 1:
  719. return 0.0
  720. df = n - 1
  721. if df in _T95:
  722. return _T95[df]
  723. if df >= 30:
  724. return 1.96 # 正規近似
  725. return 2.13 # 小 n 保守値
  726. def mean_ci95(vals):
  727. """同一 budget 上の値列 vals(可変 n)に対して (mean, halfwidth) を返す。"""
  728. arr = np.asarray(vals, dtype=float)
  729. n = len(arr)
  730. if n == 0:
  731. return 0.0, 0.0
  732. if n == 1:
  733. return float(arr[0]), 0.0
  734. mean = float(arr.mean())
  735. s = float(arr.std(ddof=1))
  736. half = tcrit_95(n) * (s / math.sqrt(n))
  737. return mean, half
  738. def _plot_with_ci_band(ax, xs, mean, half, *, label, line_kwargs=None, band_kwargs=None):
  739. line_kwargs = {} if line_kwargs is None else dict(line_kwargs)
  740. band_kwargs = {"alpha": 0.25} | ({} if band_kwargs is None else dict(band_kwargs))
  741. ax.plot(xs, mean, label=label, **line_kwargs)
  742. ax.fill_between(xs, mean - half, mean + half, **band_kwargs)