跳到主内容
快讯直播
AI智模界
教程

用 Claude 优化量子电路:从 Qiskit 建模到噪声仿真

这篇能做出什么

跟着走完,你会得到一套可复用的工作流:用自然语言把"想要的量子电路"描述清楚,让 Claude 生成 Qiskit 代码和优化方案,再用量子态内积和噪声仿真两条独立的证据链,验证优化真的成立——而不是听起来成立。

具体产物是三个文件和一个对照表:

  • circuits.py:参考电路 + 优化电路两个构造函数
  • check.py:无噪声等价性校验 + 门数/深度统计
  • noise.py:同一份电路在噪声模型下的测量分布对比
  • 一张三列对照表:参考版 / 去 SWAP 版 / transpile 优化版,各自的门数、CX 数、深度、与理想分布的 TVD

主线例子选的是 5 比特量子傅里叶变换(QFT)。理由很实际:它有明确的数学定义可以校验,有 SWAP 这类"结构性浪费"可以压缩,对噪声又足够敏感,能把"门数少一点、噪声下好一截"这件事演示出来。

一个重要的心态校准:Claude 在这里的角色是三个——把文字规格翻译成可运行代码的翻译器、帮你列出优化候选方案的头脑风暴伙伴、审阅你自己代码的评审员。它不负责证明优化正确。正确性由 Qiskit 的态矢仿真和测量统计给出。这个分工贯穿全文。

前置条件清单

环境

  • Python 3.x,具体最低版本以 Qiskit 官方安装文档为准
  • 能联网装包
  • 一个空目录,用来放三个脚本

依赖

```bash

pip install qiskit qiskit-aer

```

包名与安装方式以 Qiskit 官方文档当前版本为准。Qiskit 在 1.0 之后调整过模块结构(例如 Aer 从 qiskit 移到独立的 qiskit-aer,顶层 execute() 被移除),照抄网上两年前的代码经常报 ImportError,遇到就按当前官方文档改。

概念

不需要量子计算背景,但这几个词要能对上号:比特(qubit)、叠加、纠缠、H 门(制造叠加)、CX 门(受控非,两比特门)、CP(θ) 门(受控相位,带角度参数)、电路深度(同一比特上必须串行执行的门层数)。深度比门数更贴近真实噪声表现:在相干时间有限的硬件上,深度直接决定退相干损失。

Claude 入口

网页版或 API 均可。本文的提示词不长,网页版够用。

分步骤

第 1 步:先跑通一个最小电路

装完包不要直接写大电路,先确认工具链正常。

```python

smoke_test.py

from qiskit import QuantumCircuit

qc = QuantumCircuit(2)

qc.h(0)

qc.cx(0, 1)

print(qc.draw())

print("depth:", qc.depth(), "size:", qc.size())

```

```bash

python smoke_test.py

```

能画出一个 H 加一个 CX、打印出深度 2,环境就没问题。很多后续的诡异报错其实在安装这一步就埋下了,先排除掉。

第 2 步:写一张"电路规格卡"交给 Claude

直接说"帮我写个 QFT"会得到一份能跑但难以核对的代码。约束写得越死,后面校验越省力。把下面这段贴给 Claude:

```text

你是一名量子电路工程师。请用 Qiskit 写一个 n 比特量子傅里叶变换(QFT)的参考实现。

约束:

1. 只使用 H、受控相位门 CP(theta)、SWAP 三类门,不要调用

qiskit.circuit.library 里现成的 QFT。

2. 函数签名固定为 build_qft(n: int = 5) -> QuantumCircuit,返回不含测量的电路。

3. 在文件顶部注释里写清楚比特序约定:q0 对应最高位还是最低位。

4. 每个 for 循环上方写一行注释,说明这一层在做什么。

额外输出(单独一节,不要混进代码):

A. 你预计这个电路有多少个 CP 门、多少个 SWAP 门、多少个 CX

(假设一个 SWAP 展开成 3 个 CX)。

B. 列出你认为可以优化掉的门,以及去掉它们为什么不改变计算结果。

C. 列出你这个实现里最可能出错的三处地方。

```

最后那条 "列出最可能出错的三处地方" 值得每次都加。它经常能提前点出比特序、角度公式、全局相位这几类高频错误,省掉一轮调试。

第 3 步:落地参考实现,先把基线量化

把 Claude 给的代码整理成文件。下面是一份符合上述约定的参考实现,可以直接用:

```python

circuits.py

from math import pi

from qiskit import QuantumCircuit

比特序约定:q0 为最低位。电路末尾的 SWAP 用于把输出反序,

使测量结果的比特串按 q_{n-1}...q0 读取即为频域下标。

def build_qft_reference(n: int = 5) -> QuantumCircuit:

qc = QuantumCircuit(n, name="qft_ref")

for j in range(n):

qc.h(j)

把 j 与所有更高的比特做受控相位,角度随距离折半

for k in range(j + 1, n):

qc.cp(pi / 2 ** (k - j), k, j)

末尾反序

for i in range(n // 2):

qc.swap(i, n - 1 - i)

return qc

def build_qft_no_swap(n: int = 5) -> QuantumCircuit:

"""与参考版表示同一变换,但省掉末尾 SWAP,改由读取时反序比特。"""

qc = QuantumCircuit(n, name="qft_no_swap")

for j in range(n):

qc.h(j)

for k in range(j + 1, n):

qc.cp(pi / 2 ** (k - j), k, j)

return qc

```

接着写指标脚本。关键点是先把电路展开成基础门再统计,否则 depth() 会把 CP、SWAP 当成一个整体跳过去,数字会好看得离谱:

```python

check.py

from qiskit import transpile

from circuits import build_qft_reference, build_qft_no_swap

BASIS = ["h", "p", "cx"] # 接近真实硬件的门集,SWAP 会被展开成 3 个 CX

def report(qc, name, opt=0):

t = transpile(qc, basis_gates=BASIS, optimization_level=opt)

ops = dict(t.count_ops())

return {

"name": name,

"depth": t.depth(),

"size": t.size(),

"cx": int(ops.get("cx", 0)),

"ops": ops,

}

for qc, name in [(build_qft_reference(), "参考版"),

(build_qft_no_swap(), "去SWAP版")]:

print(report(qc, name))

```

示例运行会看到参考版 CX 数量在 26 上下(10 个 CP 门各展开成 2 个 CX,共 20;两个 SWAP 各展开成 3 个 CX,共 6),去 SWAP 版在 20 上下。具体数值随 Qiskit 版本和分解策略略有差异,以你自己跑出来的为准,重点是两者的差值是 6 个 CX——这就是反序的代价。

第 4 步:让 Claude 提优化,要求给出可验证的论证

把上一步的数字贴回给 Claude:

```text

下面是我实测的指标(basis = h/p/cx,optimization_level=0):

参考版:depth=?, size=?, cx=?

去SWAP版:depth=?, size=?, cx=?

目标:在不改变电路所表示变换的前提下,进一步压缩门数和深度。

要求每个方案给出:

1. 具体改哪几行;

2. 预期减少多少 CX、深度大概降多少;

3. 代价是什么(需要经典后处理 / 需要额外比特 / 需要动态电路);

4. 怎么用一句话验证它确实等价。

不要重复"去掉 SWAP"这个已知方案。

```

这一步的产出质量取决于你给的数据质量。只贴"帮我优化"得到的是泛泛之谈,贴了具体数字和约束,Claude 才可能提出诸如"把相邻同比特的相位门合并""调整 CP 门的施加顺序让无依赖的门并行"这类可落地的建议。

拿到建议后,先只实现你能自己复述出等价性理由的那一条。理解不了的优化别急着合进代码,容易从"少几个门"变成"少几个门但算错了"。

Qiskit 自带的两档 transpile 优化也顺手跑一下做对照:

```python

print(report(build_qft_reference(), "参考版-lv3", opt=3))

```

第 5 步:无噪声等价性校验

这一步是整个流程的核心。用一个独立的判据确认两个电路算的是同一件事——态矢内积:

```python

check.py 续

import numpy as np

from qiskit.quantum_info import Statevector

sv_ref = Statevector.from_instruction(build_qft_reference(5))

sv_new = Statevector.from_instruction(build_qft_no_swap(5)).reverse_qargs()

fidelity = abs(sv_ref.inner(sv_new)) ** 2

print("态矢保真度:", fidelity)

```

注意 .reverse_qargs()。去 SWAP 版的输出比特顺序和参考版相反,不翻转就比对,会得到一个接近 0 的保真度,然后你会开始怀疑电路写错了——其实只是没对齐下标。这是这套流程里最容易踩的一脚。

输出接近 1.0(比如 0.9999999)就说明两个电路在无噪声下表示同一个态。内积取模方是为了消掉全局相位,全局相位不影响任何测量结果。

第 6 步:加噪声,看真实收益

无噪声下两者等价,那优化到底赚了什么?靠噪声仿真回答。

```python

noise.py

import numpy as np

from qiskit import transpile

from qiskit.quantum_info import Statevector

from qiskit_aer import AerSimulator

from qiskit_aer.noise import NoiseModel, depolarizing_error

from circuits import build_qft_reference, build_qft_no_swap

BASIS = ["h", "p", "cx"]

def build_noise_model(p1=5e-4, p2=5e-3):

nm = NoiseModel()

门名必须与 transpile 之后的电路一致,否则噪声不会生效

nm.add_all_qubit_quantum_error(depolarizing_error(p1, 1), ["h", "p"])

nm.add_all_qubit_quantum_error(depolarizing_error(p2, 2), ["cx"])

return nm

def sample_probs(qc, nm, n=5, shots=20000, seed=1234):

t = transpile(qc, basis_gates=BASIS, optimization_level=1)

t.measure_all()

sim = AerSimulator(noise_model=nm, seed_simulator=seed)

counts = sim.run(t, shots=shots).result().get_counts()

p = np.zeros(2 ** n)

for key, v in counts.items():

p[int(key.replace(" ", ""), 2)] += v / shots

return p

def ideal_probs(qc, n=5):

return Statevector.from_instruction(qc).probabilities()

def bit_reverse(i, n):

return int(format(i, f"0{n}b")[::-1], 2)

nm = build_noise_model()

n = 5

p_ideal = ideal_probs(build_qft_reference(n), n)

p_ref = sample_probs(build_qft_reference(n), nm, n)

p_new = sample_probs(build_qft_no_swap(n), nm, n)

去 SWAP 版的读出比特序相反,比较前先重排

p_new = p_new[[bit_reverse(i, n) for i in range(2 ** n)]]

tvd = lambda a, b: 0.5 * np.abs(a - b).sum()

print("参考版 TVD :", tvd(p_ideal, p_ref))

print("去SWAP版 TVD:", tvd(p_ideal, p_new))

```

TVD(全变差距离)的含义很直白:两个分布差多少,取值 0 到 1,越小越接近理想。跑下来通常能看到去 SWAP 版的 TVD 更小——少 6 个两比特门,噪声就少 6 份。

如果想更贴近真实硬件,可以把 depolarizing_error 换成 thermal_relaxation_error,用 T1、T2 和门时长建模,参数取自你所用量子后端的校准数据(以后端官方页面公布的当前数值为准)。这一步不改变流程,只改变数字。

第 7 步:把结果回灌,做第三轮

把 TVD 和门数表一起贴回给 Claude,问一个更窄的问题:还有没有不依赖额外比特、不引入动态电路的深度压缩空间?窄问题往往得到能直接落地的答案。

常见坑与排错

统计数字漂亮得不像话。 qc.depth() 和 qc.count_ops() 在复合门未分解时会把它算作一个门。CP、SWAP 都属于这一类。统计前先 transpile(..., basis_gates=[...]),门集按目标硬件选。

逻辑门数和物理门数不是一回事。 少一个 CP 门,在 h/p/cx 门集下等于少两个 CX。写优化报告时说清楚是哪个层面的数字,不然容易高估收益。

噪声模型静默失效。 add_all_qubit_quantum_error 里的门名必须和电路里实际出现的门名一致。transpile 后电路里若还有 u、rz 之类的门,而噪声模型只登记了 h、p、cx,那些门就在无噪声下运行,结果会好得不真实。加一行 print(t.count_ops()) 核对一下门名。

比特序。 Statevector.probabilities() 返回的数组下标以 q0 为最低位,而 get_counts() 返回的字符串最左边是最高位比特。用 int(key, 2) 转成整数后两边正好对齐。涉及 SWAP 省略时,还要按 bit_reverse 重排。这处错了,保真度会接近 0。

用测量分布代替态矢校验。 测量分布只能验证到概率层面,相对相位错了看不出来。要求严格的等价性就用态矢内积。

采样噪声。 shots 太小的时候 TVD 会抖动,两个版本差个 0.005 可能只是随机性。对比时固定 seed_simulator,shots 给到两万以上。

Claude 会写出旧版 API。 典型是顶层 execute()、from qiskit import Aer、qiskit.execute。这些在当前版本里已经不可用,按报错信息对照官方迁移文档替换即可,不是你的环境坏了。

全局相位。 直接比较两个态矢会受全局相位干扰,取内积的模方。相对相位则相反,不能忽略。

下一步建议

  • 把规模加大。 5 比特只是演示。把 n 提到 8 以上,再看"丢弃小角度受控相位门"的近似 QFT:低于阈值的 CP 直接删掉,门数显著下降,代价是引入了可控的近似误差。用同一套校验流程量化这个误差,就能画出"门数—保真度"的权衡曲线。
  • 试半经典 QFT。 思路是把多比特的受控相位换成"测一个比特、按结果决定是否施加相位、再 reset"的动态电路,把量子资源换成经典反馈。Qiskit 支持 if_test 和 reset,实现路径清晰但细节不少,正好适合当作下一轮与 Claude 协作的题目。
  • 把校验脚本接进 CI。 态矢保真度大于阈值、TVD 不劣化,这两条作为断言写进测试。以后每次让 Claude 改电路,跑一遍就知道有没有退化。
  • 换一个电路家族练手。 硬件高效拟设(hardware-efficient ansatz)的纠缠层同样是优化的重点:全连接纠缠换线性链、层数压缩、参数合并。方法论和本文完全一样,只是把 QFT 换成变分电路。
  • 用真实后端校准数据建模。 depolarizing 是简化模型,换成带 T1/T2 的热弛豫模型,结论会更贴近你真想跑的那台机器。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。