Demo 2:Welded Tree

第 12 章 量子图算法 · 本章演示 · 代码演示总索引

可视化:2 子图 —— 14 顶点焊缝树 + 经典 vs 量子 P(EXIT)

对应教程:第 12 章 量子图算法 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"

实现要点

核心:取 n=2 的焊接树 (2n+2=6 列、14 顶点)。左侧三列大小 1,2,4;右侧三列 大小 4,2,1;焊接在第 2 与第 3 列之间。整图邻接矩阵 A 在列子空间上压缩成 一条带权路径(边权树内 √2、焊缝 2)。连续时间量子游走 e^{-iAt}|0⟩_col 在 这个 6 维子空间上演化,振幅沿链传播并在 O(n) 时间内到达 EXIT 列;经典 随机游走 P_t = T^t |ENTRANCE⟩ 受生日悖论下界 2^{Ω(n)} 限制。这里画 P(EXIT) vs t 的对比曲线 + 列链上的概率剖面演化。

完整源码

demos/ch12-quantum-graphs/02_welded_tree.py
  1"""
  2ch12-quantum-graphs demo 2: Welded Tree — classical vs continuous-time quantum walk
  3
  4对应教程:chenzhaoyun.com/quantum-tutorial/ch12-quantum-graphs/welded-tree.html
  5核心:取 n=2 的焊接树 (2n+2=6 列、14 顶点)。左侧三列大小 1,2,4;右侧三列
  6     大小 4,2,1;焊接在第 2 与第 3 列之间。整图邻接矩阵 A 在列子空间上压缩成
  7     一条带权路径(边权树内 √2、焊缝 2)。连续时间量子游走 e^{-iAt}|0⟩_col 在
  8     这个 6 维子空间上演化,振幅沿链传播并在 O(n) 时间内到达 EXIT 列;经典
  9     随机游走 P_t = T^t |ENTRANCE⟩ 受生日悖论下界 2^{Ω(n)} 限制。这里画
 10     P(EXIT) vs t 的对比曲线 + 列链上的概率剖面演化。
 11
 12依赖:pip install unified-quantum[visualization]
 13运行:python demos/ch12-quantum-graphs/02_welded_tree.py
 14"""
 15from __future__ import annotations
 16
 17from pathlib import Path
 18
 19import matplotlib
 20matplotlib.use("Agg")
 21import matplotlib.pyplot as plt
 22import numpy as np
 23from scipy.linalg import expm
 24
 25OUT = Path(__file__).parent
 26
 27# === 1. Build welded tree graph (n = 2, N = 14) ===
 28N_DEPTH = 2
 29N = 2 ** (N_DEPTH + 2) - 2          # 14
 30N_COLS = 2 * N_DEPTH + 2            # 6
 31COL_SIZES = [2 ** j if j <= N_DEPTH else 2 ** (2 * N_DEPTH + 1 - j)
 32             for j in range(N_COLS)]
 33COL_OFFSET = np.concatenate([[0], np.cumsum(COL_SIZES)])
 34
 35A = np.zeros((N, N), dtype=int)
 36# Left tree (cols 0..N_DEPTH-1): each vertex in col j has 2 children in col j+1
 37for col in range(N_DEPTH):
 38    for v_local in range(COL_SIZES[col]):
 39        v = COL_OFFSET[col] + v_local
 40        for c in range(2):
 41            A[v, COL_OFFSET[col + 1] + 2 * v_local + c] = 1
 42# Right tree (cols N_DEPTH+1..N_COLS-2): each vertex in col j has 1 parent in col j+1
 43for col in range(N_DEPTH + 1, N_COLS - 1):
 44    for v_local in range(COL_SIZES[col]):
 45        v = COL_OFFSET[col] + v_local
 46        A[v, COL_OFFSET[col + 1] + v_local // 2] = 1
 47# Weld: each left leaf connects to 2 right leaves (cyclic pairing)
 48left_n = COL_SIZES[N_DEPTH]
 49right_n = COL_SIZES[N_DEPTH + 1]
 50for v_local in range(left_n):
 51    v = COL_OFFSET[N_DEPTH] + v_local
 52    for r in [v_local % right_n, (v_local + 1) % right_n]:
 53        A[v, COL_OFFSET[N_DEPTH + 1] + r] = 1
 54A = np.maximum(A, A.T)               # symmetrize
 55
 56deg = A.sum(axis=1)
 57max_deg = int(deg.max())
 58assert max_deg == 3, f"welded tree should have max degree 3, got {max_deg}"
 59
 60# === 2. Column-subspace 6×6 tridiagonal matrix (weights √2 except weld=2) ===
 61A_col = np.zeros((N_COLS, N_COLS))
 62for j in range(N_COLS - 1):
 63    w = 2.0 if j == N_DEPTH else np.sqrt(2.0)
 64    A_col[j, j + 1] = A_col[j + 1, j] = w
 65
 66# === 3. Classical random walk on full graph (start at ENTRANCE = vertex 0) ===
 67T = A / deg[None, :]                  # transition matrix
 68T_MAX = 80
 69exit_probs_class = np.zeros(T_MAX)
 70p = np.zeros(N); p[0] = 1.0
 71EXIT_VERTEX = N - 1
 72for t in range(T_MAX):
 73    exit_probs_class[t] = p[EXIT_VERTEX]
 74    p = T @ p
 75
 76# === 4. Continuous-time quantum walk |psi(t)> = e^{-i A_col t} |0>_col ===
 77T_QUANT_MAX = 20.0
 78N_QUANT_STEPS = 200
 79t_quant = np.linspace(0, T_QUANT_MAX, N_QUANT_STEPS)
 80exit_probs_quant = np.zeros(N_QUANT_STEPS)
 81profile_probs = np.zeros((N_QUANT_STEPS, N_COLS))
 82v0 = np.zeros(N_COLS); v0[0] = 1.0
 83for k, t in enumerate(t_quant):
 84    probs = np.abs(expm(-1j * A_col * t) @ v0) ** 2
 85    exit_probs_quant[k] = probs[-1]
 86    profile_probs[k] = probs
 87
 88# === 5. uniqc sanity: ENTRANCE = |0000⟩ should be deterministic ===
 89print("ch12-quantum-graphs-demo-2: Welded Tree (n=2) classical vs quantum walk")
 90print(f"  N = {N} vertices, columns = {N_COLS}, column sizes = {COL_SIZES}")
 91print(f"  max degree = {max_deg} (welded-tree invariant)")
 92try:
 93    from uniqc import Circuit, submit_task, wait_for_result
 94    c = Circuit()
 95    for q in range(4):
 96        c.measure(q)
 97    r = wait_for_result(
 98        submit_task(circuit=c, backend="dummy:local:simulator", shots=100)
 99    )
100    counts = r.counts
101    top = max(counts, key=counts.get)
102    print(f"  uniqc sanity: |ENTRANCE>=|0000> with p={counts[top]/sum(counts.values()):.2f}")
103except Exception as e:
104    print(f"  uniqc sanity skipped: {e}")
105
106
107# === 6. matplotlib 可视化 ===
108fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13.0, 5.2))
109
110# Left: P(EXIT) vs time, classical (steps) vs quantum (continuous)
111ax1.semilogy(np.arange(T_MAX), np.maximum(exit_probs_class, 1e-4),
112             "-", color="#ff7f0e", lw=2.0, label=f"classical ({T_MAX} steps)")
113ax1.semilogy(t_quant, np.maximum(exit_probs_quant, 1e-4),
114             "-", color="#1f77b4", lw=1.6,
115             label=r"continuous-time $e^{-iA_S t}$")
116ax1.axvline(N_DEPTH, color="gray", ls=":", lw=1, alpha=0.6,
117            label=f"n={N_DEPTH} depth")
118ax1.set_ylim(1e-4, 1.0)
119ax1.set_xlabel(r"time $t$ (steps for classical, $\hbar=1$ for quantum)")
120ax1.set_ylabel(r"$P(\mathrm{EXIT})$")
121ax1.set_title("welded tree n=2: probability of reaching EXIT")
122ax1.grid(alpha=0.3, which="both")
123ax1.legend(loc="lower right", fontsize=9, framealpha=0.9)
124
125# Right: column-probability profiles at selected times (wave packet propagation)
126profile_times = [0.0, 2.5, 5.0, 7.5, 10.0]
127colors_prof = ["#888888", "#1f77b4", "#2ca02c", "#d62728", "#9467bd"]
128for tk, col_t in zip(profile_times, colors_prof):
129    probs = np.abs(expm(-1j * A_col * tk) @ v0) ** 2
130    ax2.plot(np.arange(N_COLS), probs, "-o", color=col_t, lw=1.6, ms=7,
131             label=f"t={tk:.1f}")
132ax2.set_xticks(np.arange(N_COLS))
133ax2.set_xticklabels([f"C{j}\n(|C|={COL_SIZES[j]})" for j in range(N_COLS)],
134                    fontsize=8)
135ax2.set_xlabel("column index j (size of C_j)")
136ax2.set_ylabel(r"$|\langle j | e^{-iA_S t} | 0\rangle_\mathrm{col}|^2$")
137ax2.set_title("wave-packet propagating across the 6-column chain")
138ax2.set_ylim(-0.02, 1.0)
139ax2.grid(alpha=0.3)
140ax2.legend(loc="upper left", fontsize=8, framealpha=0.9)
141
142# Inset: welded tree sketch
143inset = fig.add_axes([0.04, 0.55, 0.18, 0.36])
144inset.set_xlim(-0.5, N_COLS - 0.5)
145inset.set_ylim(-2.0, 2.0)
146inset.axis("off")
147node_pos = {}
148for col, size in enumerate(COL_SIZES):
149    ys = np.linspace(-1.6, 1.6, size) if size > 1 else [0.0]
150    for i, y_ in enumerate(ys):
151        node_pos[COL_OFFSET[col] + i] = (col, y_)
152for i in range(N):
153    for j in range(i + 1, N):
154        if A[i, j]:
155            x0, y0 = node_pos[i]; x1, y1 = node_pos[j]
156            is_weld = (node_pos[i][0] == N_DEPTH and node_pos[j][0] == N_DEPTH + 1)
157            color, lw, alpha = ("#d62728", 1.4, 0.55) if is_weld else ("#444", 0.7, 0.45)
158            inset.plot([x0, x1], [y0, y1], "-", color=color, lw=lw, alpha=alpha)
159inset.scatter(*node_pos[0], s=160, c="#2ca02c", ec="black", zorder=5)
160inset.text(-0.3, 0, "ENTRANCE", ha="right", va="center",
161           fontsize=8, fontweight="bold")
162inset.scatter(*node_pos[N - 1], s=160, c="#1f77b4", ec="black", zorder=5)
163inset.text(N_COLS - 0.3, 0, "EXIT", ha="left", va="center",
164           fontsize=8, fontweight="bold")
165inset.set_title("welded tree n=2 (red = weld)", fontsize=9)
166
167plt.suptitle("ch12-quantum-graphs demo 2: Welded Tree classical vs quantum walk",
168             y=1.02)
169plt.tight_layout()
170out_path = OUT / "02_welded_tree.png"
171plt.savefig(out_path, dpi=120, bbox_inches="tight")
172plt.close()
173
174# === 7. stdout 关键统计 ===
175peak_idx = int(np.argmax(exit_probs_quant))
176print(f"\n  classical: max P(EXIT) over {T_MAX} steps = "
177      f"{exit_probs_class.max():.4e}")
178print(f"  classical: P(EXIT) at step {N_DEPTH} = "
179      f"{exit_probs_class[N_DEPTH]:.4e}")
180print(f"  quantum  : peak P(EXIT)={exit_probs_quant[peak_idx]:.3f} at "
181      f"t={t_quant[peak_idx]:.2f}")
182print(f"  quantum  : P(EXIT) at t=π={np.pi:.2f} = "
183      f"{exit_probs_quant[np.argmin(np.abs(t_quant - np.pi))]:.3f}")
184print(f"  A_col eigvals (rad/s): "
185      f"{np.sort(np.linalg.eigvalsh(A_col)).round(3).tolist()}")
186print(f"  theoretical weld weight: 2.0, tree weight: sqrt(2)={np.sqrt(2):.4f}")
187print(f"  PNG -> {out_path}")

运行结果

Demo 2:Welded Tree

运行方式

# 仓库根目录执行;输出 PNG 到 demos/ch12-quantum-graphs/02_welded_tree.png,关键统计到 stdout
.venv/bin/python3 demos/ch12-quantum-graphs/02_welded_tree.py

← 本章演示 · ↑ 代码演示总索引 · ⌂ 教程门户