Skip to Content

核心线路接口

概述

本章介绍 UnitaryLab 量子线路的核心数据结构与操作方法,适合希望构建和操作量子线路的用户。读完本章,你将能够:

  • 使用多种方式创建 Circuit 对象
  • 添加单量子比特门、受控门和旋转门(包括多控制门)
  • 添加测量操作
  • 执行模拟并读取结果
  • 使用线路变换方法(复制、反转、追加等)
  • 导入导出 OpenQASM 与 Python 源码
  • 了解 UnitaryLab 的量子比特排序规则

模块索引

模块主要类
unitarylab.core.circuitCircuitBaseCircuit
unitarylab.core.registerRegister
unitarylab.core.classical_registerClassicalRegister
unitarylab.backend.gatesequence.gatesequenceGateSequenceBaseGateSequence
unitarylab.backend.gate.gatebaseQuantumGate

CircuitRegisterClassicalRegister 也可直接从 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,每次调用门方法时会向其中追加一个 QuantumGateQuantumGate 是 frozen dataclass,创建后不可变)。

2. 添加单量子比特门

所有单比特门的第一个参数均为目标量子比特索引(从 0 开始)。几乎所有门方法的 target/control 参数都同时支持单个 intlist[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-YY 轴旋转 π
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)CNOTmcx(ctrls, tgt)
cy(ctrl, tgt)受控 Ymcy(ctrls, tgt)
cz(ctrl, tgt)受控 Zmcz(ctrls, tgt)
ch(ctrl, tgt)受控 Hmch(ctrls, tgt)
cs(ctrl, tgt)受控 S无(不存在 mcs
cp(angle, ctrl, tgt)受控相位mcp(angle, ctrls, tgt)
crx(angle, ctrl, tgt)受控 RXmcrx(angle, ctrls, tgt)
cry(angle, ctrl, tgt)受控 RYmcry(angle, ctrls, tgt)
crz(angle, ctrl, tgt)受控 RZmcrz(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) 中,qubitclbit 均可以是整数或列表,且数量必须一一对应。
  • 每个量子比特和经典比特只能被映射一次;重复映射会抛出 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)只覆盖 rxryrzpcx 五种门。若线路包含其余门(如 h),调用 to_python() 会抛出 RuntimeError("cannot reliably export UnitaryLab gate '...' as native code")而不是退化处理

默认 transpile() 也不能解决这个限制:默认基础门集本身包含 h,因此 qc.transpile() 会保留 h,随后调用 to_python() 仍会抛出相同异常。对于含有 CodeGenerator 未覆盖门的线路,推荐使用 to_qasm() / to_qasm2();只有线路已经由 rxryrzpcx 构成时,才直接使用 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 的门集合限制。
最后更新于