OpenQASM 3 编解码器¶
编解码器把受支持的 OpenQASM 2/3 文本转换为电路级 IR,并把可序列化电路统一 输出为 OpenQASM 3.0。它接受门名展开、寄存器广播和一位经典寄存器的条件; 未支持的构件通过具有明确类型的异常拒绝。文本往返保持解析后的电路, 不保留源码的寄存器划分、门名或排版。
元数据出口¶
需要把电路中的操作对应回源码时,使用 qaiji.codec.parse_qasm3。它只解析一次,
同时返回电路和逐操作的源码来历;接受与拒收的源码和 from_qasm3 完全相同。
origins与circuit.gates逐位一一对应。广播或门名展开产生的多个操作共享 所属语句的 span,用broadcast_index和expansion_index区分;条件操作的 origin 用body按同样规则对应条件体内的门。statement_index是顶层语句序号, 声明等不产生操作的语句也占号,条件体内的门沿用所属if的序号。行号和列号都从 1 起,
line:column到end_line:end_column为闭区间;列按 Unicode code point 计,与错误消息中的at line:column采用同一约定。origin 只是旁表:电路的
==、语义摘要哈希、canonicalize()和to_qasm3都不读取它。ParsedQasm3本身不可哈希,它的==同时比较电路和 origins; 只比较语义时请比较.circuit。circuit仍是可变容器。调用方增删或替换其中的操作后,origins不再对齐, 记录不做运行期检查。declared_version是版本头原文,例如"3.0";缺少版本头时为None, 此时version_header_present为False。
下面的 CY 展开为 3 个门,3 条 origin 都保留源码拼写和同一 span:
>>> from qaiji.codec import parse_qasm3
>>> parsed = parse_qasm3("qubit[2] q; h q[0];\nCY q[0], q[1];")
>>> len(parsed.origins) == len(parsed.circuit.gates)
True
>>> [(o.raw_name, o.line, o.expansion_index) for o in parsed.origins]
[('h', 1, 0), ('CY', 2, 0), ('CY', 2, 1), ('CY', 2, 2)]
>>> parsed.version_header_present
False
- qaiji.codec.parse_qasm3(src)[源代码]¶
一次解析同时得到电路与源码旁表;接受面与拒收面和
from_qasm3完全相同。- 参数:
src (str) -- OpenQASM 2/3 源码,版本头可缺省。
- 返回:
电路、与
circuit.gates逐位对齐的OperationOrigin元组,以及声明的版本。- 抛出:
Qasm3ParseError -- 语法错误、版本不受支持、表达式嵌套过深, 或整数字面量、qubit 总数无法写成十进制。
Qasm3UnsupportedConstructError -- 构造、门或操作数不受支持。
- 返回类型:
- class qaiji.codec.ParsedQasm3(circuit, origins, declared_version)[源代码]¶
一次解析的产物:电路,加上与
circuit.gates逐位对齐的源码旁表。circuit仍是可变容器:调用方一旦增删或替换其中的操作,origins即告失效, 本记录不做运行期防护。记录本身不可哈希,==同时比较电路与旁表, 比较语义时请只比较.circuit。declared_version是版本头原文,缺头为None。示例
>>> parsed = parse_qasm3("OPENQASM 3.0; qubit q; x q;") >>> (parsed.declared_version, len(parsed.origins) == len(parsed.circuit.gates)) ('3.0', True)
- 参数:
circuit (Circuit)
origins (tuple[OperationOrigin, ...])
declared_version (str | None)
- class qaiji.codec.OperationOrigin(statement_index, raw_name, line, column, end_line, end_column, broadcast_index=0, expansion_index=0, body=())[源代码]¶
一个电路操作在源码中的来历;只读元数据,不进入电路的相等、摘要或规范化。
行号从 1 起;列号从 1 起、两端闭区间,按 Unicode code point 计, 与错误消息
at line:column同一约定。statement_index是顶层语句序号,条件体内的门沿用所属if的序号; 同一语句展开或广播出的多个操作共享 span,以两个下标区分。展开为零个门的语句 不留 origin,因此序号可能有空缺。raw_name对门是源码原样拼写,对测量与条件 是构造名"measure"/"if";只有条件 origin 的body非空。示例
>>> origin = parse_qasm3("qubit[2] q; h q;").origins[1] >>> (origin.raw_name, origin.line, origin.column, origin.broadcast_index) ('h', 1, 13, 1)
支持边界¶
注册表支持 id、x、y、z、h、s、t、rx、
ry、rz、u3、cx、cz 和 swap,共 14 个门名。
另有下表的 19 个门名在解析时展开为这些可序列化门;展开不增加
GateType,也不影响 IR 格式版本。门名查找沿用
.lower(),因此这些门名的大写变体也可接受;寄存器名仍区分大小写。
IR 中的 CNOT 在输出时规范化为 cx。
RX90、RX180、ISWAP 与 SQISWAP 属于电路模型,
但没有本编解码器的门映射,to_qasm3 会拒绝包含这些门的电路。需要这些门时可从
Python 构造电路并使用 L2 原生调度接口 中的原生调度生成接口;它输出的是
NativeScheduleIR,不是可交回 to_qasm3 的电路。
版本头、寄存器与错误收敛¶
缺少
OPENQASM版本头的文本可以接受;显式版本的主号只接受 2 或 3, 畸形版本和其他主号拒收。多个量子寄存器按声明顺序连续映射到同一电路的比特编号。
量子寄存器与经典寄存器同名时拒收,无论谁先声明;同类寄存器重名也拒收。
宽度为 1 的经典寄存器上的
if (c[0] == 0)或if (c[0] == 1)降为整个寄存器的Conditional;下标必须为整数字面量且不越界。解析或表达式求值中的递归超限抛出
Qasm3ParseError, detail 为expression nesting exceeds parser limit。空、纯空白或纯注释源码按“没有 qubit 声明”拒收,抛出
Qasm3UnsupportedConstructError,detail 为program has no qubit declaration;解析库调用中的其他AttributeError包装为Qasm3ParseError。超出解释器整数字符串转换上限(默认 4300 位,见
sys.set_int_max_str_digits()) 的十进制整数字面量抛出Qasm3ParseError,消息为source at 1:1:加解释器原生消息,原异常保留为__cause__。门参数中未超出位数上限、但超出浮点范围的整数按无穷大求值,由既有的有限性检查 拒收:抛出
Qasm3UnsupportedConstructError,detail 为Gate parameters must be finite。1/N、pi/N下溢为0.0并被接受;1/(1/N)抛出Qasm3ParseError,detail 为division by zero;N**2与N%2与小操作数时一样报unsupported binary expression。十六进制、二进制、八进制字面量(寄存器宽度、下标、条件值、参数)以及累计的 qubit 总数同样受该上限约束;超限时抛出
Qasm3ParseError,消息为<构件> at 行:列:加解释器原生消息。位置指向该字面量,宽度指向[; 总数越限时指向使总数越限的那条声明。经典位总数不设上限。输出的量子寄存器名依次尝试
q、q0、q1、……,选择首个未被 经典寄存器占用的名字。
这些规则在各版本之间的变化记录在仓库根目录的 CHANGELOG.md。
广播、寄存器与条件¶
门广播要求所有整寄存器操作数长度相同;带下标的单比特操作数在各次广播中复用。
长度为 1 的整寄存器仍参与长度比较,不会当作单比特自动扩展。
例如 cx a[0], b; 可将一个比特与 b 中各比特配对,而宽度为 1 的 a
和宽度为 2 的 b 不能用于 cx a, b;。每组操作数仍须满足门元数、
比特不重复和范围检查。广播展开按下标逐段完成:先生成第 0 组的完整门序列,
再生成第 1 组,依此类推。
测量要求源和目标长度相等,不复用单个源比特或目标位。整寄存器与带下标操作数
混用时,整寄存器一侧必须宽度为 1。例如宽度为 1 的 q 可以用于
c[0] = measure q;,宽度为 1 的 c 可以用于 c = measure q[0];。
两侧都是整寄存器时,按对应下标测量;OpenQASM 2 的箭头形式遵循相同规则。
多量子寄存器在输出时统一为一个量子寄存器。下面的无头输入同时展示广播、 门名展开与命名避让:
>>> from qaiji import from_qasm3, to_qasm3
>>> circuit = from_qasm3("qubit[2] a; qubit b; bit q; sx a; cx a[0], b;")
>>> circuit.num_qubits, len(circuit)
(3, 3)
>>> [gate.qubits for gate in circuit.gates]
[(0,), (1,), (0, 2)]
>>> emitted = to_qasm3(circuit)
>>> "qubit[3] q0;" in emitted
True
>>> from_qasm3(emitted) == circuit
True
支持整寄存器相等条件 if (c == k);多位寄存器的单个位条件
if (c[0] == 1) 会被拒收。条件体只支持门语句,不支持 else。
没有 qubit 声明或 0 qubit 的程序会被拒收。参数表达式中的 ** 和 %
会被拒收,不做求值;循环、reset、barrier、脉冲语句及自定义门定义不受支持。完整错误分类见 异常层级。
广播没有设置数量上限:22 字节的 qubit[1000000] q; h q;
会生成 \(10^6\) 个门。展开会实际分配这些门,
所需时间和内存随展开规模增长;调用方应根据可用资源控制输入中的寄存器长度。
19 个门名的展开等价级别¶
表中 R3 指 OpenQASM 3 stdgates.inc 参考,R2 指官方 OpenQASM 2
qelib1.inc 参考。EXACT 表示算符严格相等;“相位”表示只差一个整体全局相位,
对多比特门是整个酉矩阵共同的相位,不是受控子空间之间的相对相位。
“—”表示不对该参考作声明。u 的 R2 参考取 OpenQASM 2 内置 U;
u、cswap 都不在官方 OQ2 qelib1.inc 中。
名字 |
对 R3 |
对 R2 |
|---|---|---|
|
EXACT |
— |
|
EXACT |
相位 |
|
相位 |
相位 |
|
— |
相位 |
|
EXACT |
相位 |
|
EXACT |
相位 |
|
相位 |
— |
|
相位 |
— |
|
EXACT |
EXACT |
|
EXACT |
相位 |
|
EXACT |
— |
|
EXACT |
— |
|
EXACT |
EXACT |
|
EXACT |
— |
|
— |
相位 |
|
EXACT |
— |
|
— |
EXACT |
|
EXACT |
相位 |
|
EXACT |
— |
cu3 采用官方 OQ2 原文的读法。与 Qiskit 版 qelib1 的读法相比,
控制位为 1 的子空间多一个相对相位 \(e^{-i(\varphi+\lambda)/2}\);
它不能作为整个两比特门的全局相位忽略。迁移 Qiskit 语料时须核对所用定义。
展开后的 IR 不保留原门名;单比特名字展开为 1 个门,多比特名字展开为 3–17 个门,
例如 cy 为 3 个、ccx 为 15 个、cswap 为 17 个。