3. 参数化量子电路与符号计算
在固定电路中,每个门的旋转角都是常数,电路一旦构建完成,其行为就完全确定。而参数化量子电路(Parameterized Quantum Circuit, PQC)把部分门的旋转角替换为符号变量,使同一份电路结构可以在不同参数取值下反复求值。这一特性是变分量子算法(VQE、QAOA)、量子机器学习以及量子最优控制的基础。Cirq 通过 sympy 符号系统原生支持这一能力:门的角度可以是 sympy.Symbol,电路可以携带未绑定的参数,只有在调用 cirq.resolve_parameters 或 cirq.Simulator 的 param_resolver 时,符号才会被替换为具体数值。
符号参数的定义与门构造
Cirq 中所有旋转门都接受符号作为角度参数。常用符号来自 sympy,例如 sympy.Symbol('theta')。符号可以参与算术运算,因此 theta/2、2*theta + sympy.pi/4 这类表达式都能直接作为门的角度。下面构造一个含两个符号的单比特电路:
import cirq
import sympy
theta = sympy.Symbol('theta')
phi = sympy.Symbol('phi')
q = cirq.LineQubit(0)
circuit = cirq.Circuit([
cirq.ry(theta)(q),
cirq.rz(phi)(q),
cirq.rx(theta / 2)(q),
])
print(circuit)
print(circuit.is_parameterized()) # True
print(sorted(cirq.parameter_names(circuit))) # ['phi', 'theta']
这里 ry(theta) 表示绕 Y 轴旋转 theta 弧度,rz(phi) 绕 Z 轴旋转 phi,rx(theta/2) 绕 X 轴旋转 theta/2。注意第三个门复用了 theta,说明同一符号可以在电路中出现多次,绑定一次即可同时生效。circuit.is_parameterized() 返回 True,表示电路尚未完全确定;cirq.parameter_names(circuit) 返回电路中所有自由符号的集合,这是检查参数是否遗漏或命名冲突的第一道关卡。
参数绑定与态矢量求值
绑定参数有两种等价方式。第一种是显式调用 cirq.resolve_parameters,得到一个新的、已完全确定的电路;第二种是在仿真时通过 param_resolver 传入字典。前者适合需要复用固定电路、做进一步编译或导出的场景;后者适合在扫描循环中避免反复构造电路对象。
resolver = cirq.ParamResolver({'theta': 0.5, 'phi': 1.2})
resolved = cirq.resolve_parameters(circuit, resolver)
print(resolved)
sim = cirq.Simulator()
result = sim.simulate(circuit, param_resolver=resolver)
print(result.final_state_vector)
态矢量是复数向量,直接打印不易读。可以用 cirq.dirac_notation 转成狄拉克符号,或计算各基态的测量概率:
state = result.final_state_vector
print(cirq.dirac_notation(state))
probs = np.abs(state) ** 2
print(dict(zip(['|0>', '|1>'], probs)))
以 theta=0.5、phi=1.2 为例,电路先绕 Y 轴转 0.5 rad,再绕 Z 轴转 1.2 rad,最后绕 X 轴转 0.25 rad。由于 Z 旋转只改变相对相位、不改变 |0> 与 |1> 的布居,最终测量概率主要由两次 X/Y 方向旋转共同决定。实际运行时,你会看到 |0> 与 |1> 的概率之和为 1,且随 theta 变化而连续变化——这正是参数化电路可调性的直接体现。
参数扫描与态矢量变化观察
参数化电路最典型的用法是扫描:固定电路结构,让一个或多个符号在一段区间内取不同值,观察态矢量或测量概率如何变化。Cirq 提供 cirq.Simulator.simulate_sweep,它接受一个 cirq.Sweep 对象,一次性返回多个参数点上的结果,避免手写循环。
import numpy as np
sweep = cirq.Linspace('theta', start=0, stop=2*np.pi, length=9)
results = sim.simulate_sweep(circuit, params=sweep,
param_resolver=cirq.ParamResolver({'phi': 1.2}))
for r in results:
theta_val = r.params['theta']
p1 = np.abs(r.final_state_vector[1]) ** 2
print(f"theta={theta_val:.3f} P(|1>)={p1:.4f}")
运行后你会得到一组 (theta, P(|1>)) 数据。以本电路为例,由于 rz(phi) 只贡献相对相位,P(|1>) 主要由 ry(theta) 与 rx(theta/2) 的合成旋转决定,整体呈现随 theta 周期性起伏的形态:在 theta 接近 0 和 2π 时回到初态附近,在中间某些角度达到峰值。上图给出了这一趋势的示意曲线,实际数值会因 phi 取值和门顺序略有偏移。判断扫描是否正常,可以检查三点:概率之和是否为 1、曲线是否连续、峰值位置是否与理论预期一致。
常见陷阱与检查清单
- 符号未绑定:直接对含自由符号的电路调用
simulate而不提供 resolver,Cirq 会抛出ValueError。先用circuit.is_parameterized()判断。 - 符号名拼写不一致:
Symbol('theta')与Symbol('Theta')是两个不同符号,resolver 字典的键必须与电路中的符号完全一致。 - 角度单位:Cirq 的旋转门默认使用弧度,不是角度。若习惯用角度,需显式转换
sympy.pi * deg / 180。 - 扫描长度与步长:
Linspace的length是采样点数,不是步长;若需要精确控制步长,可用Points显式列出参数值。 - 态矢量顺序:多比特时
final_state_vector的索引与量子比特顺序相关,默认按LineQubit顺序排列,读取概率前先确认比特映射。
resolve_parameters 能正确替换后再扩展到多符号扫描。这样能把“符号定义错误”和“扫描逻辑错误”分开定位。