Demo 1:QAOA K4 MaxCut¶
可视化:双图(热图+top cuts) —— 11×11 网格 + 爬山
对应教程:第 14 章 优化与数值量子算法 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"
实现要点¶
核心:K4 = 4-node complete graph, 6 edges. We scan the (γ, β) landscape for p=1 QAOA, plot ⟨H_C⟩ as a contour/heatmap, overlay a classical hill-climb trajectory that pushes past the grid optimum, then histogram the dominant bitstrings at the final (γ*, β*). For K4 the optimal cut = 4 and QAOA p=1 typically attains ≈ 0.92 approximation ratio (Farhi et al. 2014).
完整源码¶
demos/ch14-optimization-numerics/01_qaoa_maxcut_4node.py¶
1"""
2ch14-optimization-numerics demo 1: QAOA p=1 on the 4-node MaxCut (K4)
3
4对应教程:chenzhaoyun.com/quantum-tutorial/ch14-optimization-numerics/constraint-satisfaction.html
5核心:K4 = 4-node complete graph, 6 edges. We scan the (γ, β) landscape for
6 p=1 QAOA, plot ⟨H_C⟩ as a contour/heatmap, overlay a classical hill-climb
7 trajectory that pushes past the grid optimum, then histogram the dominant
8 bitstrings at the final (γ*, β*). For K4 the optimal cut = 4 and QAOA p=1
9 typically attains ≈ 0.92 approximation ratio (Farhi et al. 2014).
10
11依赖:pip install unified-quantum[visualization]
12运行:python demos/ch14-optimization-numerics/01_qaoa_maxcut_4node.py
13"""
14from __future__ import annotations
15
16from pathlib import Path
17
18import matplotlib
19matplotlib.use("Agg")
20import matplotlib.pyplot as plt
21import numpy as np
22
23from uniqc import qaoa_ansatz, submit_task, wait_for_result
24
25OUT = Path(__file__).parent
26
27# === 1. K4 graph and problem Hamiltonian ===
28EDGES = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
29N_QUBITS = 4
30OPT_CUT = 4
31
32# Cost H_C = sum_edges (1 - Z_i Z_j)/2 → drop the +1/2 constant, encode
33# the Z_i Z_j part so that the sampler emits high-energy bitstrings when
34# edges disagree.
35H_C = [(f"Z{i}Z{j}", -0.5) for i, j in EDGES]
36
37SHOTS = 600
38
39
40def cut_value(bs: str) -> int:
41 bs = bs.zfill(N_QUBITS)
42 return sum(
43 1 for i, j in EDGES
44 if int(bs[N_QUBITS - 1 - i]) != int(bs[N_QUBITS - 1 - j])
45 )
46
47
48def evaluate_qaoa(gamma: float, beta: float, shots: int = SHOTS) -> float:
49 c = qaoa_ansatz(H_C, p=1, gammas=np.array([gamma]), betas=np.array([beta]))
50 for q in range(N_QUBITS):
51 c.measure(q)
52 r = wait_for_result(
53 submit_task(circuit=c, backend="dummy:local:simulator", shots=shots)
54 )
55 counts = r.counts
56 total = sum(counts.values())
57 if total <= 0:
58 return 0.0
59 return sum(cut_value(bs) * cnt for bs, cnt in counts.items()) / total
60
61
62def best_counts(gamma: float, beta: float, shots: int) -> dict:
63 c = qaoa_ansatz(H_C, p=1, gammas=np.array([gamma]), betas=np.array([beta]))
64 for q in range(N_QUBITS):
65 c.measure(q)
66 r = wait_for_result(
67 submit_task(circuit=c, backend="dummy:local:simulator", shots=shots)
68 )
69 return r.counts
70
71
72# === 2. γ × β landscape scan ===
73print("ch14-demo-1: QAOA p=1 on K4 MaxCut")
74print(f" nodes={N_QUBITS}, edges={len(EDGES)}, optimal cut={OPT_CUT}")
75print(f" shots per QAOA run: {SHOTS}")
76
77G = np.linspace(0.10, 1.30, 11)
78B = np.linspace(0.10, 0.90, 11)
79landscape = np.zeros((len(G), len(B)))
80for gi, g in enumerate(G):
81 for bi, b in enumerate(B):
82 landscape[gi, bi] = evaluate_qaoa(g, b)
83print(f" grid scan: {len(G)} x {len(B)} = {len(G) * len(B)} points")
84
85# === 3. classical hill-climb refinement ===
86gi0, bi0 = np.unravel_index(int(np.argmax(landscape)), landscape.shape)
87g_star, b_star = float(G[gi0]), float(B[bi0])
88c_star = float(landscape[gi0, bi0])
89trajectory = [(g_star, b_star, c_star)]
90
91rng = np.random.default_rng(0)
92for step in range(15):
93 sigma = max(0.04, 0.18 * (0.55 ** step))
94 g_try = float(np.clip(g_star + rng.normal(0, sigma), 0.05, np.pi))
95 b_try = float(np.clip(b_star + rng.normal(0, sigma), 0.05, np.pi))
96 c_try = evaluate_qaoa(g_try, b_try)
97 if c_try > c_star:
98 g_star, b_star, c_star = g_try, b_try, c_try
99 trajectory.append((g_star, b_star, c_star))
100
101# Big shots at optimum for the histogram
102counts = best_counts(g_star, b_star, shots=4000)
103hist = {bs.zfill(N_QUBITS): cnt for bs, cnt in counts.items()}
104total_best = sum(hist.values())
105hist_sorted = sorted(hist.items(), key=lambda kv: -kv[1])
106top_states = hist_sorted[:8]
107
108# === 4. visualisation ===
109fig = plt.figure(figsize=(12.5, 5.4))
110gs = fig.add_gridspec(1, 2, width_ratios=[1.05, 1.0])
111
112# Left: landscape with hill-climb trajectory
113ax1 = fig.add_subplot(gs[0, 0])
114im = ax1.imshow(
115 landscape, origin="lower", aspect="auto", cmap="viridis",
116 extent=[B[0] - (B[1] - B[0]) / 2, B[-1] + (B[1] - B[0]) / 2,
117 G[0] - (G[1] - G[0]) / 2, G[-1] + (G[1] - G[0]) / 2],
118)
119cs = ax1.contour(B, G, landscape, levels=[1.5, 2.5, 3.0, 3.3],
120 colors="white", linewidths=0.7, alpha=0.55)
121ax1.clabel(cs, inline=True, fontsize=8, fmt="%.1f")
122ax1.plot([b_star], [g_star], "*", color="#ffd700", ms=18, mec="black", mew=0.9,
123 label=rf"opt $\gamma^*={g_star:.3f},\beta^*={b_star:.3f}$")
124traj_g = [t[0] for t in trajectory]
125traj_b = [t[1] for t in trajectory]
126ax1.plot(traj_b, traj_g, "-o", color="#ff6f61", ms=3, lw=1.0, alpha=0.8,
127 label="classical hill-climb trajectory")
128ax1.set_xlabel(r"mixer angle $\beta$")
129ax1.set_ylabel(r"cost angle $\gamma$")
130ax1.set_title(r"p=1 QAOA $\langle H_C\rangle$ landscape on K4 (11×11 scan)")
131cbar = plt.colorbar(im, ax=ax1)
132cbar.set_label(r"$\langle H_C\rangle$ (mean cut value)")
133ax1.legend(loc="lower left", fontsize=8)
134
135# Right: top bitstrings histogram
136ax2 = fig.add_subplot(gs[0, 1])
137labels = [bs for bs, _ in top_states]
138probs = [cnt / total_best for _, cnt in top_states]
139cuts = [cut_value(bs) for bs, _ in top_states]
140colors = ["#2ca02c" if c == OPT_CUT else
141 "#1f77b4" if c == OPT_CUT - 1 else "#d62728" for c in cuts]
142bars = ax2.bar(range(len(labels)), probs, color=colors, ec="black", lw=0.5)
143for k, (lab, p, c) in enumerate(zip(labels, probs, cuts)):
144 ax2.text(k, p + 0.012, f"{lab}\ncut={c}", ha="center", va="bottom",
145 fontsize=7.5)
146ax2.set_xticks(range(len(labels)))
147ax2.set_xticklabels(labels, rotation=0, fontsize=8)
148ax2.set_ylabel("sampling probability")
149ax2.set_title(rf"top bitstrings at $(\gamma^*,\beta^*)$ — $\langle H_C\rangle$={c_star:.3f}")
150ax2.set_ylim(0, max(probs) * 1.45)
151ax2.grid(alpha=0.3, axis="y")
152
153plt.suptitle("ch14 demo 1: QAOA p=1 on K4 MaxCut", y=1.02)
154plt.tight_layout()
155out_path = OUT / "01_qaoa_maxcut_4node.png"
156plt.savefig(out_path, dpi=120, bbox_inches="tight")
157plt.close()
158
159# === 5. stdout 关键统计 ===
160total_evals = len(G) * len(B) + len(trajectory) - 1 + 1
161print(f" total QAOA circuit evaluations: {total_evals}")
162print(f" grid best : cuts={float(landscape.max()):.3f}/{OPT_CUT} "
163 f"at γ={G[gi0]:.3f}, β={B[bi0]:.3f}")
164print(f" after hill-climb: γ*={g_star:.4f}, β*={b_star:.4f}")
165print(f" mean cut value : {c_star:.4f} / {OPT_CUT}")
166print(f" approximation r : {c_star / OPT_CUT:.4f} (3-regular worst-case guarantee = 0.6924)")
167print(f" top 4 bitstrings:")
168for bs, cnt in hist_sorted[:4]:
169 print(f" |{bs}> cut={cut_value(bs)} p={cnt/total_best:.4f}")
170print(f" PNG -> {out_path}")
运行结果¶

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