核心线路接口
概述
本章介绍 UnitaryLab 量子线路的核心数据结构与操作方法,适合希望构建和操作量子线路的用户。读完本章,你将能够:
- 使用多种方式创建
Circuit对象 - 添加单量子比特门、受控门和旋转门(包括多控制门)
- 添加测量操作
- 执行模拟并读取结果
- 使用线路变换方法(复制、反转、追加等)
- 导入导出 OpenQASM 与 Python 源码
- 了解 UnitaryLab 的量子比特排序规则
模块索引
| 模块 | 主要类 |
|---|---|
unitarylab.core.circuit | CircuitBase、Circuit |
unitarylab.core.register | Register |
unitarylab.core.classical_register | ClassicalRegister |
unitarylab.backend.gatesequence.gatesequence | GateSequenceBase、GateSequence |
unitarylab.backend.gate.gatebase | QuantumGate |
Circuit、Register、ClassicalRegister 也可直接从 unitarylab 包顶层导入:from unitarylab import Circuit, Register, ClassicalRegister。
1. 创建 Circuit
from unitarylab import Circuit
# 方式一:直接指定量子比特数(最常用)
qc = Circuit(4)
# 方式二:传入初始状态向量(用于从已知量子态开始模拟)
import numpy as np
state = np.array([1, 0, 0, 0], dtype=complex) # 对应 |00⟩
qc = Circuit(state)
# 方式三:传入 Register 和 ClassicalRegister 对象(需要命名寄存器或测量时使用)
from unitarylab import Register, ClassicalRegister
qr = Register('q', 3)
cr = ClassicalRegister('c', 3)
qc = Circuit(qr, cr)
# 可选:为线路命名(默认名称为 "Circuit")
qc = Circuit(4, name="my_circuit")说明:
- 方式一适合快速构建线路,不需要测量时推荐使用。
- 方式二传入的状态向量长度必须是 2 的幂,且必须归一化。
- 方式三在需要进行测量并读取经典比特结果、或需要多个命名寄存器时使用。
Circuit内部会自动维护一个GateSequence,每次调用门方法时会向其中追加一个QuantumGate(QuantumGate是 frozen dataclass,创建后不可变)。
2. 添加单量子比特门
所有单比特门的第一个参数均为目标量子比特索引(从 0 开始)。几乎所有门方法的 target/control 参数都同时支持单个 int 和 list[int]:传入列表时会对列表中每个比特分别施加同一个门,等价于对每个比特单独调用一次。
qc = Circuit(3)
qc.x(0) # Pauli-X(NOT)门
qc.y(1) # Pauli-Y 门
qc.z(2) # Pauli-Z 门
qc.h(0) # Hadamard 门
qc.s(1) # S 门(π/2 相位)
qc.sdag(1) # S† 门
qc.t(2) # T 门(π/4 相位)
qc.tdag(2) # T† 门
qc.sqrtx(0) # √X 门
qc.sqrtxdag(0) # √X† 门
qc.sqrty(1) # √Y 门
qc.sqrtydag(1) # √Y† 门
qc.i(0) # 恒等门(no-op,可用于占位)
# target 支持列表:对多个比特同时施加同一个门
qc2 = Circuit(3)
qc2.h([0, 1, 2]) # 等价于分别调用 qc2.h(0)、qc2.h(1)、qc2.h(2)常用单量子比特门速查表:
| 方法 | 门名称 | 说明 |
|---|---|---|
x(target) | Pauli-X | 量子 NOT 门,将 翻转为 |
y(target) | Pauli-Y | Y 轴旋转 π |
z(target) | Pauli-Z | 将 相位翻转 |
h(target) | Hadamard | 创建叠加态 |
s(target) / sdag(target) | S / S† | 相位门,等价于 及其共轭 |
t(target) / tdag(target) | T / T† | 相位门,等价于 及其共轭 |
sqrtx(target) / sqrtxdag(target) | √X / √X† | 连续两次 sqrtx 等价于 x |
sqrty(target) / sqrtydag(target) | √Y / √Y† | 连续两次 sqrty 等价于 y |
i(target) | Identity | 恒等门(no-op) |
3. 添加旋转门
旋转门需要指定旋转角度(以弧度为单位),同样支持 target 为列表:
import numpy as np
qc = Circuit(2)
# RX(θ):绕 X 轴旋转 θ 角度
qc.rx(np.pi / 2, 0)
# RY(θ):绕 Y 轴旋转
qc.ry(np.pi / 4, 1)
# RZ(θ):绕 Z 轴旋转
qc.rz(np.pi, 0)
# 相位门 P(θ):仅对 |1⟩ 施加 e^{iθ} 相位
qc.p(np.pi / 2, 1)
# U1(λ) ≡ U(0, 0, λ),U2(φ, λ) ≡ U(0, φ, λ),U3 为通用单比特门
qc.u1(np.pi / 4, 0)
qc.u2(0, np.pi, 1)
qc.u3(np.pi / 2, 0, np.pi, 0)
# 全局相位门(不接受 target,作用于整条线路)
qc.gp(np.pi / 8)| 方法 | 参数 | 说明 |
|---|---|---|
rx(angle, target) | 角度(弧度) | 绕 X 轴旋转 |
ry(angle, target) | 角度(弧度) | 绕 Y 轴旋转 |
rz(angle, target) | 角度(弧度) | 绕 Z 轴旋转 |
p(angle, target) | 角度(弧度) | 相位门 |
u1(lmb, target) | λ | U1(λ) ≡ U(0, 0, λ) |
u2(phi, lmb, target) | φ, λ | U2(φ, λ) ≡ U(0, φ, λ) |
u3(theta, phi, lmb, target) | θ, φ, λ | 通用单比特门 U3(θ, φ, λ) |
gp(angle) | 角度(弧度) | 全局相位门,无 target 参数 |
4. 添加受控门
所有受控门方法都接受可选的 control_state 参数:None(默认)表示所有控制比特须为 1(active-high)才触发;也可传入自定义的控制比特模式(如字符串 '01' 表示第一个控制比特为 0、第二个为 1 时触发,具体见 control() 一节中的用法)。
qc = Circuit(3)
# CNOT:控制比特 0,目标比特 1
qc.cx(0, 1)
qc.cnot(0, 1) # cx 的别名
# 多控制 X(Toffoli):控制比特列表,目标比特
qc.mcx([0, 1], 2)
# 受控 Y / Z / H / S
qc.cy(0, 1)
qc.cz(0, 1)
qc.ch(0, 1)
qc.cs(0, 1)
# 对应的多控制版本
qc.mcy([0, 1], 2)
qc.mcz([0, 1], 2)
qc.mch([0, 1], 2)
# 注意:目前没有 mcs()(多控制 S)方法
# 受控相位门 / 多控制相位门
qc.cp(np.pi / 2, 0, 1)
qc.mcp(np.pi / 2, [0, 1], 2)
# 受控旋转门及其多控制版本
qc.crx(np.pi / 2, 0, 1)
qc.cry(np.pi / 2, 0, 1)
qc.crz(np.pi / 2, 0, 1)
qc.mcrx(np.pi / 2, [0, 1], 2)
qc.mcry(np.pi / 2, [0, 1], 2)
qc.mcrz(np.pi / 2, [0, 1], 2)
# SWAP 门
qc.swap(0, 1)
# 任意酉矩阵门(传入 2^k x 2^k 的酉矩阵,target 长度为 k)
import numpy as np
mat = np.array([[0, 1], [1, 0]], dtype=complex) # Pauli-X 矩阵
qc.unitary(mat, 0)
# unitary 也接受 control/control_state
qc.unitary(mat, target=2, control=[0, 1])
# 带控制状态的受控门(control_state=0 表示控制比特为 0 时触发)
qc.cx(0, 1, control_state=0)常用受控门速查表:
| 方法 | 说明 | 多控制版本 |
|---|---|---|
cx(ctrl, tgt) / cnot(ctrl, tgt) | CNOT | mcx(ctrls, tgt) |
cy(ctrl, tgt) | 受控 Y | mcy(ctrls, tgt) |
cz(ctrl, tgt) | 受控 Z | mcz(ctrls, tgt) |
ch(ctrl, tgt) | 受控 H | mch(ctrls, tgt) |
cs(ctrl, tgt) | 受控 S | 无(不存在 mcs) |
cp(angle, ctrl, tgt) | 受控相位 | mcp(angle, ctrls, tgt) |
crx(angle, ctrl, tgt) | 受控 RX | mcrx(angle, ctrls, tgt) |
cry(angle, ctrl, tgt) | 受控 RY | mcry(angle, ctrls, tgt) |
crz(angle, ctrl, tgt) | 受控 RZ | mcrz(angle, ctrls, tgt) |
swap(tgt1, tgt2) | SWAP | — |
unitary(mat, tgt, control, control_state) | 任意酉矩阵,可选受控 | 内建于同一方法 |
注意: unitary() 传入的矩阵采用 little-endian 本地基约定,即 target[0] 对应矩阵中的最低位比特。
5. 添加测量
Circuit(n) 这种整数构造方式会自动创建同样大小的默认经典寄存器(ClassicalRegister('c', n)),因此可以直接调用 measure(),无需先手动创建经典寄存器;只有使用显式 Register 构造 Circuit 且未同时提供 ClassicalRegister 时,才需要先添加经典寄存器(否则该线路没有可写入的经典比特,调用 measure() 会报错):
from unitarylab import Circuit
# 整数构造方式:已自动创建 2 比特经典寄存器 'c',可直接测量
qc = Circuit(2)
qc.h(0)
qc.cx(0, 1)
# 将量子比特 0 测量到经典比特 0,量子比特 1 测量到经典比特 1
qc.measure([0, 1], [0, 1])
# 也可以单个测量
# qc.measure(0, 0)若使用显式 Register 构造且需要自定义命名/多寄存器/自定义比特布局,则需同时传入 ClassicalRegister:
from unitarylab import Circuit, Register, ClassicalRegister
qr = Register('q', 2)
cr = ClassicalRegister('c', 2)
qc = Circuit(qr, cr) # 显式构造时必须同时提供经典寄存器才能测量
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])测量说明:
measure(qubit, clbit)中,qubit和clbit均可以是整数或列表,且数量必须一一对应。- 每个量子比特和经典比特只能被映射一次;重复映射会抛出
ValueError。 - 测量操作在
execute()执行时才真正发生,并将结果写入ExecutionResult.classical_results_map。
6. 执行线路
result = qc.execute()
# 完整参数
result = qc.execute(
initial_state=None, # 初始态,默认从 |0...0⟩ 开始
backend='torch', # 'torch'(默认)/ 'numpy' / 'cpp' / 'tensornet'
device='cpu', # 'cpu' 或 'gpu'
dtype=np.complex128, # 复数精度,默认 complex128
shots=1, # 独立执行次数
seed=42, # 测量随机种子,None 表示不固定
backend_options=None, # tensornet 后端专属:max_bond / cutoff / routing
)执行相关方法与结果字段的完整说明见 线路执行与工具流程。
7. 绘图与分析
# 绘制线路图(弹出 Matplotlib 图窗)
qc.draw()
# 保存为文件,指定标题
qc.draw(filename='circuit.png', title='My Circuit')
# 其他输出格式:'mpl'(默认)、'text'、'latex'
qc.draw(output='text')
# 获取线路分析。show=False 避免随后调用 info.show() 时重复打印。
info = qc.analyze(show=False)
info.show()
# get_matrix() 只适用于不含测量等非酉操作的线路。
unitary_qc = Circuit(2)
unitary_qc.h(0)
unitary_qc.cx(0, 1)
matrix = unitary_qc.get_matrix()
# 只计算酉矩阵左上角的 2**m × 2**m 子块(m <= 比特数),而非"前 m 行"
matrix_top = unitary_qc.get_matrix(m=1)绘图与分析的完整参数、CircuitInfo 的全部方法见 线路执行与工具流程。
8. 线路变换
UnitaryLab 支持多种线路结构变换。其中 copy()、inverse()、dagger()、reverse()、repeat()、decompose() 和 control() 返回新的 Circuit 对象,不修改原线路;append()、prepend() 和 initialize() 则直接修改调用它们的线路,并返回 None。
qc = Circuit(2)
qc.h(0)
qc.cx(0, 1)
# 复制线路
qc2 = qc.copy()
# 反转门顺序(并对每个门取共轭转置),等价于整个线路的 U†
qc_inv = qc.inverse()
qc_dag = qc.dagger() # inverse() 的别名
# 量子比特索引镜像翻转(qubit i ↔ qubit n-1-i),门执行顺序不变
qc_rev = qc.reverse()
# 重复整个线路 3 次
qc_rep = qc.repeat(3)
# 展开块门(n=1 展开一层)
qc_dec = qc.decompose(n=1)
# 添加两个控制比特,使整个线路变为受控线路;control_state='01' 表示
# 新增的两个控制比特分别要求为 0、1 时才触发
qc_ctrl = qc.control(num_control_qubits=2, control_state='01')
# append() 和 prepend() 会原地修改线路。使用副本分别演示,
# 避免两个操作相互影响。
sub = Circuit(2)
sub.z(0)
# 追加子线路到线路末尾(作为一个 block 门整体插入)
qc_appended = qc.copy()
qc_appended.append(sub, target=[0, 1])
# 前置子线路(插入到线路最前面)
qc_prepended = qc.copy()
qc_prepended.prepend(sub, target=[0, 1])
# 将目标比特制备为指定状态向量(内部分解为 RY/CX 序列);
# initialize() 会原地修改线路,并且目标比特此前不能被任何门使用。
# 因此使用一条新线路,并在其他量子门之前调用 initialize()。
import numpy as np
v = np.array([1 / np.sqrt(2), 1 / np.sqrt(2)])
qc_initialized = Circuit(2)
qc_initialized.initialize(v, target=0)
qc_initialized.cx(0, 1)线路变换速查表:
| 方法 | 说明 |
|---|---|
copy() | 返回线路的独立拷贝 |
inverse() / dagger() | 反转顺序并取共轭转置(互为别名) |
reverse() | 量子比特索引镜像翻转 |
repeat(times) | 重复整个线路 times 次 |
decompose(n, name) | 展开块门,n 为展开层数(默认 1) |
control(num_control_qubits, control_state) | 将线路包装为受控线路 |
append(other, target, control, control_state) | 原地追加子线路到线路末尾,返回 None |
prepend(other, target, control, control_state) | 原地将子线路插入到线路最前面,返回 None |
initialize(v, target, control, control_state) | 原地初始化目标比特为指定态,返回 None;要求目标比特未被使用 |
9. OpenQASM 与 Python 源码导入导出
Circuit 直接提供 OpenQASM 2.0 / 3.0 以及 Python 源码的导入导出方法。下面列出公开接口的常用用法;底层 GateSequence 转换、支持门范围和互操作限制见 线路执行与工具流程。
qc = Circuit(2)
qc.h(0)
qc.cx(0, 1)
# 导出字符串(默认 OpenQASM 3.0)
qasm3_str = qc.to_qasm()
qasm2_str = qc.to_qasm2()
# 从字符串恢复,自动识别 2.0 / 3.0
qc2 = Circuit.from_qasm(qasm3_str)
# 文件读写
qc.to_qasm_file('circuit.qasm')
qc3 = Circuit.from_qasm_file('circuit.qasm')导出参数说明(to_qasm() / to_qasm_file()):
decompose:若为True(或正整数),导出前先调用decompose()展开块门。transpile:若为True,导出前先转译为基础门集(用于导出一些 QASM 无法直接表示的门,例如未携带矩阵信息的自定义unitary门)。- 当线路包含不支持直接导出的门时会抛出
NotImplementedError。具体支持范围、unitary扩展及第三方工具兼容性见 OpenQASM 支持范围与限制。
to_python() 的重要限制: 当前的门到 Python 语句转换(unitarylab.codegen.CodeGenerator)只覆盖 rx、ry、rz、p、cx 五种门。若线路包含其余门(如 h),调用 to_python() 会抛出 RuntimeError("cannot reliably export UnitaryLab gate '...' as native code"),而不是退化处理。
默认 transpile() 也不能解决这个限制:默认基础门集本身包含 h,因此 qc.transpile() 会保留 h,随后调用 to_python() 仍会抛出相同异常。对于含有 CodeGenerator 未覆盖门的线路,推荐使用 to_qasm() / to_qasm2();只有线路已经由 rx、ry、rz、p、cx 构成时,才直接使用 to_python()。
# 含 h 门的线路不能直接导出为 Python;默认 transpile() 仍会保留 h:
try:
qc.transpile().to_python()
except RuntimeError as e:
print(e) # cannot reliably export UnitaryLab gate 'h' as native code
# 可导出的线路必须只使用 CodeGenerator 当前支持的门。
# 下面的 P(π) 后接 RY(π/2) 等价于 H,因此该线路与上面的 qc 等价。
import numpy as np
codegen_qc = Circuit(2)
codegen_qc.p(np.pi, 0)
codegen_qc.ry(np.pi / 2, 0)
codegen_qc.cx(0, 1)
python_src = codegen_qc.to_python()
print(python_src)导出导入方法速查表:
| 方法 | 说明 |
|---|---|
to_qasm(qreg_name, creg_name, decompose, transpile, gates_to_unroll, transpile_basis) | 导出 OpenQASM 3.0 字符串 |
to_qasm2(qreg_name, creg_name) | 导出 OpenQASM 2.0 字符串 |
Circuit.from_qasm(qasm_code) | 从 QASM 源码构建 Circuit(类方法,自动识别版本) |
Circuit.from_qasm_file(filepath) | 从 QASM 文件构建 Circuit(类方法) |
to_qasm_file(filepath, ...) | 导出 QASM 3.0 到文件 |
to_python(decompose, transpile, circuit_name, variable_name) | 导出为 Python 源码字符串 |
to_python_file(filepath, ...) | 导出 Python 源码到文件 |
transpile(gates_to_unroll, basis) | 转译为基础门集(默认 basis='default') |
寄存器接口
Register
量子寄存器,支持 Python 风格索引:
from unitarylab import Register
qr = Register('q', 3)
# 单比特访问
print(qr[0])
# 切片访问
print(qr[1:3])
# 列表访问
print(qr[[0, 2]])ClassicalRegister
经典寄存器,接口与 Register 对称,values 属性存储测量结果,-1 表示未测量:
from unitarylab import ClassicalRegister
cr = ClassicalRegister('c', 2)
print(cr.values) # [-1, -1]常见注意事项
- 线路复用:
Circuit对象可以通过append/prepend嵌套,这在构建模块化算法(如 QPE、QFT + 算法主体)时非常有用。 - 不可变门对象:
QuantumGate是 frozen dataclass,一旦创建不可修改。线路变换方法均返回新对象,不修改原线路。 - 块门展开:通过
append/prepend/initialize添加的子线路会以块门(block)形式存储。执行时会自动展开,但绘图和分析时可能需要先调用decompose()展开才能看到细节。 - 门方法命名:Python API 方法名使用
sdag/tdag(如qc.sdag(0)),而导出的 QASM 文本中对应的门名是sdg/tdg(OpenQASM 标准命名)——这是两套不同场景下的命名约定,并非不一致,无需混用。 to_python()门覆盖有限:见上文“OpenQASM 与 Python 源码导入导出”一节;默认transpile()不会展开h等默认基础门,不能保证满足 CodeGenerator 的门集合限制。