# 线路构建:从门操作到格式互转 第 1 章我们用三行代码搭出了 Bell 态线路。本章把 `Circuit` API 讲完整:全部原生门、命名寄存器、符号参数、控制结构与可复用子程序,以及 Circuit、OriginIR、OpenQASM 2.0 三种表示之间的互转。读完本章,你能用任何顺手的方式把线路"写出来",并把它交给后面章节的模拟器或云平台。 门的矩阵定义与量子线路的理论模型见姊妹站[量子计算算法教程·量子计算基础](https://chenzhaoyun.com/quantum-tutorial/ch01-basics/quantum-computing-basics.html)。本页示例基于 `unified-quantum` **0.1.0**,所有输出均为实际运行结果。 :::{admonition} 本课知识点 :class: tip 1. **[门操作与测量](#uqt-circuit-gates)**——能用 `Circuit` API 写出单比特门、双比特门与三比特门线路,并解释寄存器自动扩展规则。 2. **[命名寄存器](#uqt-circuit-qregs)**——能用 `qregs` 与 `get_qreg` 以命名寄存器组织多比特线路,解释导出时寄存器名被"扫平"的行为。 3. **[参数化线路与绑定](#uqt-circuit-params)**——能构造符号参数线路并计算 `free_parameters`,用 `assign_parameters` 完成整体或部分绑定。 4. **[控制结构与可复用子程序](#uqt-circuit-subroutine)**——能构造 CONTROL 块、DAGGER 块与 `circuit_def` 子程序,比较 Python 块语法与导出的内联形式。 5. **[OriginIR 与 OpenQASM 导入导出](#uqt-circuit-io)**——能在 Circuit、OriginIR、OpenQASM 2.0 之间互转并验证 round-trip,解释扩展门到官方 OriginIR 的自动降级。 6. **[线路统计、重映射与酉矩阵](#uqt-circuit-matrix)**——会读取 `depth`/`qubit_num` 等统计属性、执行量子比特重映射,并用 `get_matrix` 验证线路的 little-endian 酉矩阵约定。 ::: (uqt-circuit-gates)= ## 1. 门操作与测量 `Circuit()` 从空线路开始,不需要预先声明比特数:首次用到某个比特时寄存器自动扩展。门方法与门同名,按作用的比特数与参数个数分类。我们用三类门各一个,搭一个真正有用的线路——**半加器**(输入 $a,b$,输出和 $s=a\oplus b$ 与进位 $c=a\wedge b$): ```python from uniqc import Circuit c = Circuit() # 空线路,用到哪个比特,寄存器就自动扩到多大 c.x(0) # 输入 a = 1 c.x(1) # 输入 b = 1 c.cnot(0, 2) # q2 ^= a c.cnot(1, 2) # q2 ^= b → sum = a ⊕ b c.toffoli(0, 1, 3) # q3 = a ∧ b → carry c.measure(0, 1, 2, 3) # 测量,结果依次写入 c[0..3] print(c.originir) ``` ```text QINIT 4 CREG 4 X q[0] X q[1] CNOT q[0], q[2] CNOT q[1], q[2] TOFFOLI q[0], q[1], q[3] MEASURE q[0], c[0] MEASURE q[1], c[1] MEASURE q[2], c[2] MEASURE q[3], c[3] ``` `measure(0, 1, 2, 3)` 的参数是被测的量子比特编号(不要求连续)。常用门一览(参数均为弧度制数值;符号参数见第 3 节): | 类别 | 门 | |------|-----| | 单比特·无参数 | `h` `x` `y` `z` `sx` `s` `t`(及 `sdg` `tdg` `sxdg`) | | 单比特·带参数 | `rx` `ry` `rz` `p` `u1`(1 个)、`u2`(2 个)、`u3`(3 个) | | 双比特 | `cnot`/`cx` `cz` `swap` `iswap`;带参数 `crx` `cry` `crz` `cp` `cu` `xx` `yy` `zz` | | 三比特 | `toffoli` `cswap` | 快速验证一下半加器确实算对了(模拟细节在第 3 章展开): ```python from uniqc.simulator import Simulator print(Simulator().simulate_shots(c.originir, shots=8)) ``` ```text {11: 8} ``` `11 = 0b1011`,按"`c[0]` 在最右"的约定从右往左读:a=1、b=1、sum=0、carry=1——正是 $1+1=10_2$ 的半加结果,且 8 次采样全部一致(该线路输出是确定性的)。 (uqt-circuit-qregs)= ## 2. 命名寄存器 比特一多,裸整数索引很快变得难读。`Circuit(qregs=...)` 支持命名寄存器:`get_qreg` 取回寄存器引用后,用 `data[0]` 这样的名字访问比特,代码与算法描述一一对应: ```python from uniqc import Circuit c = Circuit(qregs={"data": 3, "anc": 1}) data = c.get_qreg("data") anc = c.get_qreg("anc") c.h(data[0]) c.cnot(data[0], data[1]) c.cnot(data[1], data[2]) # 在 data 上制备 3 比特 GHZ 态 c.x(anc[0]) # ancilla 恒置 1 c.measure(data[0], data[1], data[2], anc[0]) print(c.originir) ``` ```text QINIT 4 CREG 4 H q[0] CNOT q[0], q[1] CNOT q[1], q[2] X q[3] MEASURE q[0], c[0] MEASURE q[1], c[1] MEASURE q[2], c[2] MEASURE q[3], c[3] ``` 注意导出结果:`data` 占据 `q[0..2]`、`anc` 占据 `q[3]`(按声明顺序接续编号),且**寄存器名消失了**——命名只存在于构建期,`originir` 导出时一律"扫平"为物理索引。这是刻意的:所有后端只需要理解扁平的 `q[i]`。 ```python from uniqc.simulator import Simulator print(Simulator().simulate_shots(c.originir, shots=16)) ``` ```text {8: 7, 15: 9} ``` 两个键:`8 = 0b1000`(data 全 0、anc=1)与 `15 = 0b1111`(data 全 1、anc=1)——GHZ 的关联性加上恒为 1 的 ancilla(计数比例每次运行略有不同)。 (uqt-circuit-params)= ## 3. 参数化线路与绑定 变分算法(VQE、QAOA)需要"一个线路模板、反复绑定不同角度"。`Parameter` 是符号标量,`Parameters` 是符号数组,角度槽里还可以写符号表达式(参数化门的理论背景见姊妹站[参数化门附录](https://chenzhaoyun.com/quantum-tutorial/ch02-quantum-nn/parametrized-gates-appendix.html)): ```python from uniqc import Circuit, Parameter, Parameters theta = Parameter("theta") phi = Parameter("phi") w = Parameters("w", size=2) # 参数数组:w[0]、w[1] c = Circuit(2) c.rx(0, theta) c.ry(1, theta * 2 + phi / 3) # 角度槽支持符号表达式 c.rz(0, w[0]) c.measure(0, 1) print(c.originir) print("free_parameters =", c.free_parameters) ``` ```text QINIT 2 CREG 2 PARAM phi PARAM theta PARAM w[2] RX q[0], (theta) RY q[1], (phi/3 + 2*theta) RZ q[0], (w[0]) MEASURE q[0], c[0] MEASURE q[1], c[1] free_parameters = ['phi', 'theta', 'w_0'] ``` 头部多了 `PARAM` 声明,门的角度槽内联参数名或表达式(表达式经符号运算规范化,`theta * 2 + phi / 3` 输出为 `phi/3 + 2*theta`)。两个细节:`free_parameters` 只列出**实际被引用**的符号(`w[1]` 没用到,所以不在列表里);`PARAM` 是 OriginIR-ext 的本地扩展,**提交云端或导出 QASM 之前必须先绑定成数值**。 `assign_parameters` 把符号换成数值,默认返回**新线路**、不动原线路,绑定字典的键可以是名字符串、`Parameter` 对象或整个 `Parameters` 数组: ```python bound = c.assign_parameters({"theta": 0.5, "phi": 1.0, w: [0.3, 0.7]}) print(bound.is_parametric, bound.free_parameters) print(bound.originir) ``` ```text False [] QINIT 2 CREG 2 RX q[0], (0.5) RY q[1], (1.3333333333333333) RZ q[0], (0.3) MEASURE q[0], c[0] MEASURE q[1], c[1] ``` 还支持**部分绑定**——没给的参数保持符号,可以分批赋值。但带着未绑定符号的线路不能模拟,错误信息会把缺失的参数名列出来: ```python partial = c.assign_parameters({"theta": 0.5}) print("partial free:", partial.free_parameters) from uniqc.simulator import Simulator try: Simulator().simulate_shots(partial.originir, shots=8) except ValueError as e: print("ValueError:", e) ``` ```text partial free: ['phi', 'w_0'] ValueError: Cannot simulate a circuit with unbound symbolic parameters: phi, w_0. Bind them to concrete values first, e.g. circuit.assign_parameters({'': , ...}). ``` (uqt-circuit-subroutine)= ## 4. 控制结构与可复用子程序 **CONTROL 块**把一段线路整体变成受控操作:块内所有门共享同一组控制比特。两个控制位控制一个 X,就得到了 TOFFOLI: ```python from uniqc import Circuit from uniqc.simulator import Simulator c = Circuit(3) c.x(0) c.x(1) # 先把两个控制位置 1 with c.control(0, 1): # 块内门受 q0、q1 控制 c.x(2) # 等价于 TOFFOLI(0, 1, 2) c.measure(0, 1, 2) print(c.originir) print(Simulator().simulate_shots(c.originir, shots=8)) ``` ```text QINIT 3 CREG 3 X q[0] X q[1] X q[2] controlled_by (q[0], q[1]) MEASURE q[0], c[0] MEASURE q[1], c[1] MEASURE q[2], c[2] {7: 8} ``` 导出时 CONTROL 块被序列化为内联的 `controlled_by` 后缀(OriginIR-ext 扩展语法);导出官方 OriginIR 时会自动转回 `CONTROL ... ENDCONTROL` 块语法。`{7: 8}` 即 `0b111`:控制位全 1,受控 X 触发。 **DAGGER 块**把块内门取共轭转置。单个旋转门取逆等于角度变号,前后抵消: ```python c2 = Circuit(1) c2.rx(0, 1.23) with c2.dagger(): # 块内门取共轭转置 c2.rx(0, 1.23) # RX(θ)†,与上面的 RX(θ) 抵消 c2.measure(0) print(c2.originir) print(Simulator().simulate_shots(c2.originir, shots=8)) ``` ```text QINIT 1 CREG 1 RX q[0], (1.23) RX q[0], (1.23) dagger MEASURE q[0], c[0] {0: 8} ``` :::{admonition} 注意:多门 DAGGER 块的顺序语义 :class: warning 在 0.1.0 中,Python 的 `with c.dagger():` 对块内**每个门逐个**取逆,并按书写顺序导出为 `G ... dagger` 行序列;而 OriginIR **文本**的 `DAGGER ... ENDDAGGER` 块语义是整段**逆序**取逆($(AB)^\dagger = B^\dagger A^\dagger$)。对不交换的多门序列,两者结果不同(例如"H 后接 CNOT"再取逆)。需要整段取逆时,请在块内按逆序手写各门,并先在小线路上验证;单门或相互交换的门则两种写法完全一致。 ::: **可复用子程序**用 `@circuit_def` 定义:声明形式量子寄存器(还可以声明标量形参),调用时通过 `qreg_mapping` 映射到父线路的实际比特。定义一次,到处复用: ```python from uniqc import Circuit, circuit_def @circuit_def(name="bell_pair", qregs={"q": 2}) def bell_pair(circ, q): circ.h(q[0]) circ.cnot(q[0], q[1]) return circ print(bell_pair.to_originir_def()) c3 = Circuit(qregs={"data": 4}) data = c3.get_qreg("data") bell_pair(c3, qreg_mapping={"q": [data[0], data[1]]}) # 第一对 Bell bell_pair(c3, qreg_mapping={"q": [data[2], data[3]]}) # 第二对 Bell c3.measure(data[0], data[1], data[2], data[3]) print(c3.originir) print(Simulator().simulate_shots(c3.originir, shots=8)) ``` ```text DEF bell_pair(q[2]) H q[0] CNOT q[0], q[1] ENDDEF QINIT 4 CREG 4 H q[0] CNOT q[0], q[1] H q[2] CNOT q[2], q[3] MEASURE q[0], c[0] MEASURE q[1], c[1] MEASURE q[2], c[2] MEASURE q[3], c[3] {0: 2, 15: 1, 3: 2, 12: 3} ``` `to_originir_def()` 单独导出子程序定义为 `DEF` 块(OriginIR-ext 支持 `DEF`/`ENDDEF`);放进父线路后,导出的 `originir` 会把调用**内联展开**——round-trip 是语义等价而非文本等价。计数只出现 `0`(00|00)、`3`(00|11)、`12`(11|00)、`15`(11|11):两对比特各自纠缠成 Bell 对(比例每次运行略有不同)。 (uqt-circuit-io)= ## 5. OriginIR 与 OpenQASM 导入导出 UnifiedQuantum 里线路有三种表示:内存中的 `Circuit`(opcode 序列)、OriginIR-ext 文本(`.originir`,默认本地语言,官方 OriginIR 的超集)、OpenQASM 2.0 文本(`.qasm`,IBM 等平台的标准)。三者可以自由互转: ```python from uniqc import Circuit c = Circuit() c.h(0) c.cnot(0, 1) c.measure(0, 1) print(c.qasm) ``` ```text OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; creg c[2]; h q[0]; cx q[0], q[1]; measure q[0] -> c[0]; measure q[1] -> c[1]; ``` `from_qasm` / `from_originir` 类方法把文本导回 `Circuit`,可以继续编辑或提交。互转是可逆的——round-trip 后文本完全一致: ```python c2 = Circuit.from_qasm(c.qasm) # QASM → Circuit c3 = Circuit.from_originir(c.originir) # OriginIR → Circuit print("QASM round-trip:", c2.originir == c.originir) print("OriginIR round-trip:", c3.qasm == c.qasm) ``` ```text QASM round-trip: True OriginIR round-trip: True ``` 那 OriginIR-ext 比官方 OriginIR"超"在哪里?一部分是扩展门(`ISWAP`、`ECR`、`XX`、`YY`、`ZZ` 等)与 `PARAM`、`DEF`、内联 `dagger`/`controlled_by` 这些语法。`.originir` 原样保留它们;`.originir_official` 则导出官方子集,扩展门被**自动分解**成官方门: ```python c4 = Circuit(2) c4.iswap(0, 1) # ISWAP 是 ext 扩展门 c4.measure(0, 1) print(c4.originir_official) ``` ```text QINIT 2 CREG 2 S q[0] S q[1] H q[0] CNOT q[0], q[1] CNOT q[1], q[0] H q[1] MEASURE q[0], c[0] MEASURE q[1], c[1] ``` 一条 `ISWAP` 被分解为 S–S–H–CNOT–CNOT–H 六个官方门,等价但不冗余——这正是提交真实云平台时后台发生的事(第 5 章)。日常写代码时用哪种格式不用纠结:`Circuit` 对象、OriginIR 文本、QASM 文本都能直接交给模拟与提交入口。 (uqt-circuit-matrix)= ## 6. 线路统计、重映射与酉矩阵 写完线路常要问三个问题:多大规模?多深?怎么搬到别的比特上?前两个看属性(注意 `depth` 等是**属性**不是方法,不要加括号): ```python from uniqc import Circuit c = Circuit() c.h(0) c.cnot(0, 1) c.cz(1, 2) c.measure(0, 1, 2) print("qubit_num =", c.qubit_num, "| cbit_num =", c.cbit_num, "| depth =", c.depth, "| 门操作数 =", len(c.opcode_list)) remapped = c.remapping({0: 10, 1: 11, 2: 12}) # 逻辑比特 → 物理比特 print(remapped.originir) ``` ```text qubit_num = 3 | cbit_num = 3 | depth = 3 | 门操作数 = 3 QINIT 13 CREG 3 H q[10] CNOT q[10], q[11] CZ q[11], q[12] MEASURE q[10], c[0] MEASURE q[11], c[1] MEASURE q[12], c[2] ``` `depth` 是关键路径上的门数(不计 `I` 与 `BARRIER`),直接对应真机上的运行时长量级。`remapping` 返回一条**新线路**,把逻辑比特映射到指定物理索引——对接真机拓扑时(哪些物理比特相连、哪些更干净)就靠它。 最后一个工具把线路当成数学对象:`get_matrix()` 返回线路(不含测量)的完整酉矩阵(幺正演化与酉矩阵的定义见姊妹站[量子力学基础](https://chenzhaoyun.com/quantum-tutorial/ch01-basics/quantum-mechanics-basics.html)): ```python import numpy as np from uniqc import Circuit c = Circuit() c.h(0) c.cnot(0, 1) # 制备 Bell 态的线路 U = c.get_matrix() print(np.round(U, 4)) ``` ```text [[ 0.7071+0.j 0.7071+0.j 0. +0.j 0. +0.j] [ 0. +0.j 0. +0.j 0.7071+0.j -0.7071+0.j] [ 0. +0.j 0. +0.j 0.7071+0.j 0.7071+0.j] [ 0.7071+0.j -0.7071+0.j 0. +0.j 0. +0.j]] ``` 读法:矩阵第 $j$ 列是 $U|j\rangle$,即输入基态 $|j\rangle$ 演化出的态矢。约定是 **little-endian**——`q[0]` 是态矢量索引的最低位。于是第 0 列 $(0.7071, 0, 0, 0.7071)^{\mathsf T} = \frac{1}{\sqrt 2}(|00\rangle + |11\rangle)$,正是 Bell 态 $|\Phi^+\rangle$;第 1 列(输入 $|01\rangle$,即 q0=1)给出 $\frac{1}{\sqrt 2}(|00\rangle - |11\rangle) = |\Phi^-\rangle$——这个矩阵把计算基映成 Bell 基。 `get_matrix` 显式构造 $2^n\times 2^n$ 矩阵,只适合小线路(约 12 比特以内);含测量的线路没有酉矩阵表示,会抛出明确的错误: ```python m = Circuit(1) m.h(0) m.measure(0) try: m.get_matrix() except Exception as e: print(type(e).__name__, ":", e) ``` ```text NotMatrixableError : Measured circuits have no unitary matrix ``` ## 下一步 - 线路构建只是第一步,接下来把它**跑起来**:[第 3 章:本地与含噪模拟](../ch03-simulation/index.md) - 想画线路图、结果统计图与时间线图,去[第 4 章:可视化](../ch04-visualization/index.md) - 参数化线路是变分算法的地基,理论推导见姊妹站 [VQE 教程](https://chenzhaoyun.com/quantum-tutorial/ch08-qml/vqe-tutorial.html) ## 练习题 **练习 1【门操作与测量】**(→ [第 1 节](#uqt-circuit-gates)) 1. 把半加器改成输入 a=1、b=0(删去 `c.x(1)` 那行),先笔算 4 个经典比特的取值与十进制计数键,再运行验证。 2. 把 `c.x(0)` 换成 `c.rx(0, math.pi)`,预测测量计数是否改变、为什么,然后运行验证你的预测。 > 提示:$RX(\pi) = -iX$,与 X 只差全局相位,测量统计完全相同。 **练习 2【命名寄存器】**(→ [第 2 节](#uqt-circuit-qregs)) 1. 构造 `qregs={"a": 2, "b": 2}`:对 `a` 制备 Bell 态、对 `b` 两个比特各加一个 X 门。先手写出你预期的 `originir`(注意谁占哪些物理索引、寄存器名去哪了),再打印对照。 2. 把第 2 节的 data 从 3 比特扩成 4 比特 GHZ(再补一个 CNOT),预测模拟只会出现哪两个计数键,再运行验证。 > 提示:4 比特 GHZ 只测得全 0 与全 1,加上恒为 1 的 ancilla,两个键分别是 16 与 31。 **练习 3【参数化线路与绑定】**(→ [第 3 节](#uqt-circuit-params)) 1. 构造 `rx(0, theta); measure(0)` 的单比特参数化线路,分别绑定 theta = 0、π/2、π 各模拟 100 shots,先预测三组计数的形态(全 0?五五开?全 1?)再验证。 2. 故意只绑定部分参数就去 `simulate_shots`,先预测会发生什么,再运行并确认报错里列出的参数名与 `free_parameters` 一致。 > 提示:$RX(\pi)|0\rangle = -i|1\rangle$(必得 1),$RX(0)$ 是恒等(必得 0),$\pi/2$ 介于两者之间。 **练习 4【控制结构与可复用子程序】**(→ [第 4 节](#uqt-circuit-subroutine)) 1. 把 CONTROL 块的控制位从 `(0, 1)` 改成 `(0,)`(并把 q0 初始化为 1),先预测导出的 OriginIR 与测量结果,再运行验证。 2. 定义三比特 GHZ 子程序 `ghz(q[3])`,在 6 比特寄存器上调用两次,预测计数只可能出现哪些键并模拟验证。 > 提示:两个独立 GHZ 各自全 0 或全 1,拼接后共 4 个等概率键。 **练习 5【OriginIR 与 OpenQASM 导入导出】**(→ [第 5 节](#uqt-circuit-io)) 1. 手写一段两比特 Bell 线路的 OpenQASM 2.0 文本(含测量),用 `Circuit.from_qasm` 导入后打印 `.originir`,逐行对比两种文本里同一个门的写法差异(如 `cx` 与 `CNOT`、测量的写法)。 2. 构造同时含官方门(H、CNOT)与扩展门(ISWAP 或 ECR)的线路,导出 `.originir_official`,数一数扩展门被分解成几个官方门、官方门是否原样保留。 > 提示:只有 ext 专属门才触发分解,官方门逐行照抄。 **练习 6【线路统计、重映射与酉矩阵】**(→ [第 6 节](#uqt-circuit-matrix)) 1. 在 H–CNOT–CZ 线路中插入 `c.barrier(0, 1, 2)` 和 `c.identity(0)`,预测 `depth` 与门操作数各怎么变,再运行验证。 2. 打印 Bell 线路酉矩阵的第 0 列与第 1 列,验证它们分别是 $|\Phi^+\rangle$ 与 $|\Phi^-\rangle$,并解释态矢量索引 1 为什么对应"q0=1、q1=0"。 > 提示:little-endian——q0 是索引的最低位,索引 1 = 二进制 `01` 的最低位是 1。