Demo 1:QAOA K4 MaxCut

第 14 章 优化与数值量子算法 · 本章演示 · 代码演示总索引

可视化:双图(热图+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}")

运行结果

Demo 1:QAOA K4 MaxCut

运行方式

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

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