Demo 1:BB84 QKD¶
可视化:双子图 —— 1000 轮 QBER 与密钥长度对比
对应教程:第 7 章 量子信息 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
Alice 随机选 (bit, basis) 制备 4 态 |0>,|1>,|+>,|-> Bob 随机选基 (Z/X) 测量 → 保留 bases 一致的回合作为原始 key。 Eve 拦截-重发:随机选基测量后重发 → 错误率 25% (理论)。
注:本 demo 后端 shots=1 永远返回确定结果,所以按 (basis, bit) 聚合 用 shots=2000 给出真实统计分布;Eve 的影响在聚合阶段叠加。
完整源码¶
demos/ch07-quantum-info/01_bb84_qkd.py¶
1"""
2ch07-quantum-info demo 1: BB84 QKD protocol simulation
3
4对应教程:chenzhaoyun.com/quantum-tutorial/chch07-quantum-info/01-bb84-qkd.html
5依赖:pip install unified-quantum[visualization]
6运行:python demos/ch07-quantum-info/01_bb84_qkd.py
7
8Alice 随机选 (bit, basis) 制备 4 态 |0>,|1>,|+>,|->
9Bob 随机选基 (Z/X) 测量 → 保留 bases 一致的回合作为原始 key。
10Eve 拦截-重发:随机选基测量后重发 → 错误率 25% (理论)。
11
12注:本 demo 后端 shots=1 永远返回确定结果,所以按 (basis, bit) 聚合
13 用 shots=2000 给出真实统计分布;Eve 的影响在聚合阶段叠加。
14"""
15
16from __future__ import annotations
17
18from pathlib import Path
19
20import matplotlib
21matplotlib.use("Agg")
22import matplotlib.pyplot as plt
23import numpy as np
24
25from uniqc import Circuit, submit_task, wait_for_result
26
27OUT = Path(__file__).parent
28SHOTS = 2000
29N_ROUNDS = 1000
30
31
32def bb84_round(alice_basis: str, alice_bit: int, bob_basis: str,
33 shots: int = SHOTS) -> dict:
34 """跑 (alice_basis, alice_bit, bob_basis) 一种组合,返回 Bob 的概率分布。
35
36 Alice X|1> 制备为 |-> = H·X|0>(顺序:先 X 再 H)。
37 """
38 c = Circuit()
39 if alice_basis == "X":
40 if alice_bit == 1:
41 c.x(0)
42 c.h(0)
43 elif alice_bit == 1:
44 c.x(0)
45 if bob_basis == "X":
46 c.h(0)
47 c.measure(0)
48 tid = submit_task(circuit=c, backend="dummy:local:simulator", shots=shots)
49 return wait_for_result(tid).probabilities
50
51
52def aggregate_qber(with_eve: bool, n_rounds: int = N_ROUNDS,
53 rng: np.random.Generator | None = None) -> dict:
54 """聚合 BB84 协议统计。"""
55 if rng is None:
56 rng = np.random.default_rng(20250909)
57 alice_bits = rng.integers(0, 2, n_rounds)
58 alice_bases = rng.choice(["Z", "X"], n_rounds)
59 bob_bases = rng.choice(["Z", "X"], n_rounds)
60 if with_eve:
61 eve_bases = rng.choice(["Z", "X"], n_rounds)
62 else:
63 eve_bases = [None] * n_rounds
64
65 cache: dict[tuple[str, str], dict[int, dict[str, float]]] = {}
66 for ab in ["Z", "X"]:
67 for bb in ["Z", "X"]:
68 cache[(ab, bb)] = {abit: bb84_round(ab, abit, bb) for abit in [0, 1]}
69
70 matched = errors = 0
71 for i in range(n_rounds):
72 if alice_bases[i] != bob_bases[i]:
73 continue
74 if with_eve:
75 bob_out = int(alice_bits[i] if eve_bases[i] == alice_bases[i]
76 else rng.integers(0, 2))
77 else:
78 p = cache[(alice_bases[i], bob_bases[i])][int(alice_bits[i])]
79 bob_out = int(rng.choice([0, 1], p=[p.get("0", 0.0),
80 p.get("1", 0.0)]))
81 matched += 1
82 if bob_out != int(alice_bits[i]):
83 errors += 1
84 return {
85 "matched": matched, "qber": errors / max(matched, 1),
86 "key_len": matched - errors, "scenario": "with Eve" if with_eve else "no Eve",
87 }
88
89
90print("ch07-quantum-info demo 1: BB84 QKD simulation")
91res_no_eve = aggregate_qber(with_eve=False, rng=np.random.default_rng(20250909))
92res_eve = aggregate_qber(with_eve=True, rng=np.random.default_rng(20250909))
93sane_check = [(f"{ab}|{abit}>→{bb}",
94 bb84_round(ab, abit, bb).get(str(abit), 0.0))
95 for ab in ["Z", "X"] for abit in [0, 1] for bb in ["Z", "X"]]
96
97# 画 2 子图:QBER + 密钥长度堆叠柱
98fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11.5, 4.6))
99scenarios = ["no Eve", "with Eve"]
100qbers = [res_no_eve["qber"], res_eve["qber"]]
101keys = [res_no_eve["key_len"], res_eve["key_len"]]
102matched = [res_no_eve["matched"], res_eve["matched"]]
103colors = ["#2ca02c", "#d62728"]
104
105bars = ax1.bar(scenarios, qbers, color=colors, alpha=0.85, edgecolor="black")
106for b, q in zip(bars, qbers):
107 ax1.text(b.get_x() + b.get_width() / 2, q + 0.005, f"{q:.3f}",
108 ha="center", va="bottom", fontsize=11, fontweight="bold")
109ax1.axhline(0.11, color="#888", ls="--", lw=1.0,
110 label="Eve-detection threshold (~11%)")
111ax1.set_ylabel("QBER (error rate on matched bases)")
112ax1.set_title("Quantum Bit Error Rate", fontsize=12)
113ax1.set_ylim(0, max(0.30, max(qbers) * 1.15))
114ax1.legend(loc="upper left", fontsize=9)
115ax1.grid(axis="y", alpha=0.3)
116
117x = np.arange(len(scenarios))
118errs = [m - k for m, k in zip(matched, keys)]
119ax2.bar(x, keys, color=colors, alpha=0.85, edgecolor="black", label="final key bits")
120ax2.bar(x, errs, bottom=keys, color="#bbbbbb", alpha=0.7,
121 edgecolor="black", label="discarded (errors)")
122for i, (k, e) in enumerate(zip(keys, errs)):
123 ax2.text(i, k + e + 12, f"{k}", ha="center", va="bottom",
124 fontsize=11, fontweight="bold")
125 ax2.text(i, k / 2, f"key\n{k}", ha="center", va="center",
126 color="white", fontsize=10, fontweight="bold")
127 ax2.text(i, k + e / 2, f"err\n{e}", ha="center", va="center",
128 color="black", fontsize=10)
129ax2.set_xticks(x); ax2.set_xticklabels(scenarios)
130ax2.set_ylabel("Number of bits")
131ax2.set_title(f"Final key length (N={N_ROUNDS} rounds)", fontsize=12)
132ax2.set_ylim(0, max(matched) * 1.15)
133ax2.legend(loc="upper right", fontsize=9)
134ax2.grid(axis="y", alpha=0.3)
135
136plt.suptitle("ch07 BB84 — Quantum Key Distribution: no-Eve vs intercept-resend Eve",
137 fontsize=13, y=1.01)
138plt.tight_layout()
139out = OUT / "01_bb84_qkd.png"
140plt.savefig(out, dpi=120, bbox_inches="tight")
141plt.close()
142
143print(f" no-Eve : QBER = {res_no_eve['qber']:.4f} "
144 f"matched = {res_no_eve['matched']:4d} key = {res_no_eve['key_len']:4d}")
145print(f" with-Eve: QBER = {res_eve['qber']:.4f} "
146 f"matched = {res_eve['matched']:4d} key = {res_eve['key_len']:4d}")
147print(f" theory : QBER_no_eve=0.000 QBER_with_eve=0.250 (intercept-resend)")
148print(f" matched-rate = {res_no_eve['matched']/N_ROUNDS:.3f} "
149 "(expected 0.500, random bases)")
150print(" per-basis detection probabilities (sanity):")
151for label, pc in sane_check:
152 print(f" Alice {label}: P(Bob=Alice) = {pc:.3f}")
153print(f" PNG -> {out}")
运行结果¶

运行方式¶
# 仓库根目录执行;输出 PNG 到 demos/ch07-quantum-info/01_bb84_qkd.png,关键统计到 stdout
.venv/bin/python3 demos/ch07-quantum-info/01_bb84_qkd.py