# 可视化:线路图、测量统计图与时序图 量子程序本身是"看不见"的:线路是一串指令文本、测量结果是上千个比特串、执行过程更是纳秒级的物理操作。UnifiedQuantum 的可视化工具箱把这三类信息变成看得见的图——**线路图**回答"我的程序长什么样",**测量统计图**回答"我测到了什么、离理论有多远",**时序图**回答"这段程序在硬件上要跑多久、哪里在空转"。 理论背景(测量概率与玻恩规则、叠加态为何测出均匀分布)见[量子计算算法教程·量子计算基础](https://chenzhaoyun.com/quantum-tutorial/ch01-basics/quantum-computing-basics.html)。本页示例基于 `unified-quantum` **0.1.0**(含 `visualization` 扩展),所有输出均为实际运行结果;涉及随机采样的结果每次运行略有不同。 :::{admonition} 本课知识点 :class: tip 1. **[可视化工具箱与安装](#uqt-vis-toolbox)**——能安装 `visualization` 扩展,解释三类可视化各自回答的问题并选用合适的 API。 2. **[测量结果直方图](#uqt-vis-histogram)**——能用 `plot_histogram` 把采样计数画成直方图,并写出十进制计数键到二进制串键的转换。 3. **[概率分布与均匀参考线](#uqt-vis-distribution)**——能用 `plot_distribution` 画概率分布,解释红色参考线 $1/2^n$ 的含义并估计涨落范围。 4. **[静态 HTML 线路图](#uqt-vis-circuit-html)**——能用 `circuit_to_html` 生成带悬停提示的静态 SVG 线路图文件,并读懂图中各元素。 5. **[门时序调度](#uqt-vis-schedule)**——能用 `schedule_circuit` 配合 `gate_durations` 计算编译后线路的总时长,解释虚拟 Z 门零时长与左对齐调度。 6. **[时间表与时间轴图](#uqt-vis-timeline)**——能读懂 `create_time_line_table` 的时间表,用 `plot_time_line(_html)` 生成时间轴图并指出并行与空闲。 ::: (uqt-vis-toolbox)= ## 1. 可视化工具箱与安装 可视化依赖 matplotlib 与 pandas,它们是可选依赖,安装时带上 `visualization` 扩展一次装齐: ```bash pip install "unified-quantum[visualization]" ``` 装好后从 `uniqc.visualization` 导入本章用到的全部函数并验证版本: ```python import uniqc from uniqc.visualization import ( circuit_to_html, create_time_line_table, format_result, plot_distribution, plot_histogram, plot_time_line, plot_time_line_html, schedule_circuit, ) print(uniqc.__version__) ``` ```text 0.1.0 ``` 按"回答的问题"把工具箱分成三格: | 回答的问题 | API | 输出形态 | |---|---|---| | 我测到了什么? | `plot_histogram` / `plot_distribution` | matplotlib 图 | | 线路长什么样? | `circuit_to_html`、`draw_html`(文本版 `draw` 需 `originq` 扩展) | 静态 HTML/SVG | | 要跑多久? | `schedule_circuit`、`create_time_line_table`、`plot_time_line(_html)` | 调度对象 / 表格 / PDF / HTML | 线路构建与模拟的基础(`Circuit`、`simulate_shots`)见[第 1 章](../ch01-getting-started/index.md)与[第 2 章](../ch02-circuits/index.md)。 (uqt-vis-histogram)= ## 2. 测量结果直方图 用 GHZ 线路做例子:`h(0)` 制造叠加,两个 CNOT 把纠缠"串"到第三个比特。`simulate_shots` 返回"十进制整数 → 计数",而 `plot_histogram` 要的是"二进制串 → 概率",转换只需一行: ```python import matplotlib matplotlib.use("Agg") # 纯脚本环境用 Agg;Jupyter 里可省略 import matplotlib.pyplot as plt from uniqc import Circuit from uniqc.simulator import Simulator from uniqc.visualization import plot_histogram circuit = Circuit() circuit.h(0) circuit.cnot(0, 1) circuit.cnot(1, 2) circuit.measure(0, 1, 2) counts = Simulator().simulate_shots(circuit.originir, shots=1024) n = circuit.qubit_num probs = {format(int(k), f"0{n}b"): v / 1024 for k, v in counts.items()} print(probs) ``` ```text {'111': 0.5146484375, '000': 0.4853515625} ``` (计数来自随机采样,每次运行略有不同。)两根柱子各约 0.5,正是 GHZ 态 $\frac{1}{\sqrt2}(\lvert000\rangle+\lvert111\rangle)$ 的理论预测。画图并保存: ```python plot_histogram(probs, title="GHZ state, 1024 shots") plt.gcf().savefig("ghz-histogram.png", dpi=150, bbox_inches="tight") ``` ![](ghz-histogram.png) 三个使用要点: - **键必须是等长二进制串**(长度等于比特数)。整数键用 `format(int(k), f"0{n}b")` 转换;注意最右侧字符对应 `c[0]`——两比特线路 `x(0)` 后测量,占优的串是 `'01'`。 - 也可以直接传**概率向量**列表,如理想 GHZ 的 `[0.5, 0, 0, 0, 0, 0, 0, 0.5]`,横轴标签会自动生成 `000`…`111`。 - `plot_histogram` 内部调用 `plt.show()`(Jupyter 里直接内嵌显示);脚本里用 `plt.gcf().savefig(...)` 保存图片。 如果走统一执行入口,`UnifiedResult.probabilities` 的键本来就是二进制串,可以直接喂给 `plot_histogram`: ```python from uniqc import submit_task, wait_for_result result = wait_for_result(submit_task(circuit, backend="dummy:local:simulator", shots=1024)) print(result.counts) plot_histogram(result.probabilities, title="GHZ via dummy backend") ``` ```text {'000': 512, '111': 512} ``` dummy 后端按理论概率**确定性**折算计数(恰为 512/512),而 `simulate_shots` 是按玻恩规则**随机采样**——所以上一张图的柱高有涨落、这里严格对半。真机测量结果的分布可视化见[第 5 章](../ch05-cloud/index.md)。 (uqt-vis-distribution)= ## 3. 概率分布与均匀参考线 `plot_distribution` 的输入格式与 `plot_histogram` 完全相同,区别是会叠加一条**均匀分布参考线** $1/2^n$,一眼看出结果偏离"完全随机"有多远。对三比特均匀叠加态测量 2048 次: ```python from uniqc.visualization import plot_distribution uni = Circuit() uni.h(0) uni.h(1) uni.h(2) uni.measure(0, 1, 2) counts_u = Simulator().simulate_shots(uni.originir, shots=2048) probs_u = {format(int(k), "03b"): v / 2048 for k, v in sorted(counts_u.items())} print(probs_u) ``` ```text {'000': 0.11865234375, '001': 0.1201171875, '010': 0.12109375, '011': 0.12939453125, '100': 0.13232421875, '101': 0.12890625, '110': 0.12109375, '111': 0.12841796875} ``` (每次运行略有不同。)画图: ```python plot_distribution(probs_u, title="Uniform superposition, 2048 shots") plt.gcf().savefig("uniform-distribution.png", dpi=150, bbox_inches="tight") ``` ![](uniform-distribution.png) 红色虚线在 $1/8 = 0.125$ 处,八根柱子都落在它附近——均匀叠加态 $\lvert+\rangle^{\otimes 3}$ 的八种测量结果应当等概率。定量地说:每根柱子的期望计数是 $2048\times\frac18 = 256$,标准差 $\sqrt{2048\cdot\frac18\cdot\frac78}\approx 15$(约 0.007 的概率幅度),上面观察到的波动都在一个标准差上下。 参考线的用途是**快速判断"是否均匀"**:量子随机数要求分布贴着红线;Grover 这类搜索算法的答案峰则应当显著冒出红线;变分算法(如 VQE)靠对比不同迭代轮次的分布图判断收敛,见姊妹站 [VQE 教程](https://chenzhaoyun.com/quantum-tutorial/ch08-qml/vqe-tutorial.html)与本站[第 7 章实操](../ch07-labs/index.md)。 (uqt-vis-circuit-html)= ## 4. 静态 HTML 线路图 `circuit_to_html` 把线路渲染成一个**静态 HTML/SVG 文件**——不依赖任何可选库、不带 JavaScript,浏览器直接打开: ```python from uniqc.visualization import circuit_to_html document = circuit_to_html(circuit, output_path="ghz-circuit.html", title="GHZ circuit") print(len(document)) ``` ```text 4112 ``` 返回值是完整 HTML 字符串(4112 字节),`output_path` 同时把它写入 `ghz-circuit.html`。渲染效果(本页把其中的 SVG 渲染成 PNG 嵌入): ![](circuit-diagram.png) 读图:每条横线是一个量子比特的导线,方块是门——单比特门落在自己的导线上,双比特门用竖线连接两条导线,末端的 M 是测量。把鼠标悬停在任一门上,会弹出该门的比特、参数等原始信息;这是嵌在 SVG 里的原生提示,无需联网、无需插件,适合放进报告或网页。 两个近亲入口: - `draw_html(circuit.originir, output_path=...)`:等价写法,第一个参数是线路文本(OriginIR 或 QASM,后者用 `language="QASM"`); - `draw(circuit.originir)`:在终端打印**纯文本**线路图,需要 `pip install "unified-quantum[originq]"`(提供 pyqpanda3)后可用。 (uqt-vis-schedule)= ## 5. 门时序调度:这段程序要跑多久 逻辑线路里没有时间——"跑多久"取决于硬件上每个门实际消耗多少纳秒。`schedule_circuit` 做两件事: 1. **编译到基门集**(默认 `{cz, sx, rz}`):H 被分解为 RZ–SX–RZ 三步旋转(角度由编译器决定),CNOT 变成 CZ 加单比特旋转——旋转门的数学见姊妹站[参数化门附录](https://chenzhaoyun.com/quantum-tutorial/ch02-quantum-nn/parametrized-gates-appendix.html); 2. **左对齐调度**:每个门在它涉及的所有比特都空闲的最早时刻开工,不同比特上的门并行执行。 门时长由 `gate_durations` 给出——键可以是精确门名,也可以用 `1q` / `2q` / `measure` 三个通配键(接真机时也可改由后端元数据提供): ```python from uniqc.visualization import schedule_circuit schedule = schedule_circuit(circuit, gate_durations={"1q": 20, "2q": 40, "measure": 100}) for g in schedule.gates: print(f"{g.name:8s} q={list(g.qubits)} start={g.start:6.1f} duration={g.duration:5.1f} layer={g.layer}") print("total_duration:", schedule.total_duration, schedule.unit) ``` ```text RZ q=[0] start= 0.0 duration= 0.0 layer=0 SX q=[0] start= 0.0 duration= 20.0 layer=0 RZ q=[0] start= 20.0 duration= 0.0 layer=1 RZ q=[1] start= 0.0 duration= 0.0 layer=0 SX q=[1] start= 0.0 duration= 20.0 layer=0 RZ q=[1] start= 20.0 duration= 0.0 layer=1 CZ q=[1, 0] start= 20.0 duration= 40.0 layer=1 SX q=[0] start= 60.0 duration= 20.0 layer=2 RZ q=[0] start= 80.0 duration= 0.0 layer=3 RZ q=[2] start= 0.0 duration= 0.0 layer=0 SX q=[2] start= 0.0 duration= 20.0 layer=0 RZ q=[2] start= 20.0 duration= 0.0 layer=1 CZ q=[0, 2] start= 80.0 duration= 40.0 layer=3 SX q=[2] start= 120.0 duration= 20.0 layer=4 RZ q=[2] start= 140.0 duration= 0.0 layer=5 MEASURE q=[1] start= 60.0 duration=100.0 layer=2 MEASURE q=[0] start= 120.0 duration=100.0 layer=4 MEASURE q=[2] start= 140.0 duration=100.0 layer=5 total_duration: 240.0 ns ``` 每个 `TimelineGate` 都带 `qubits` / `params` / `start` / `duration` / `end` / `layer` 字段,可以直接做程序化分析。输出里有四个值得注意的细节: - **RZ 时长为 0**:RZ 是"虚拟 Z 门"——只改动后续脉冲的相位参考、不发射脉冲,因此不占时长(上面所有 `RZ` 的 `duration` 都是 0.0)。 - **并行度**:t=0 时刻三个比特的 `SX` 同时开工;`q[1]` 的 `CZ` 一结束(t=60)它的 `MEASURE` 立刻开始,与 `q[0]`、`q[2]` 上还在跑的门重叠。18 个门时长串行相加是 480 ns,调度后总时长只要 240 ns。 - **编译会重排比特**:第二个逻辑 CNOT 作用在 (1,2) 上,时间轴里却是 `CZ q=[0, 2]`——编译器在全连接虚拟拓扑上等价地置换了逻辑比特(对应地 `MEASURE q[1], c[0]` 等测量映射也被重排)。对编译后的线路再做模拟,测量分布仍是 GHZ(000/111 各半),结果不变。 - **时长数据必须有来源**:既不给 `gate_durations`、也没有后端元数据时,调度器无法确定门时长,直接抛错: ```python from uniqc.exceptions import TimelineDurationError try: schedule_circuit(circuit) except TimelineDurationError as e: print(str(e).splitlines()[0]) ``` ```text No duration is available for gate 'SX'. Provide backend_info.extra timing data, chip_characterization.global_info gate times, or gate_durations with either the exact gate name or generic key '1q'. ``` (uqt-vis-timeline)= ## 6. 时间表与时间轴图 `format_result` 把调度结果整理成"层 → 门列表"的表格数据,`create_time_line_table` 再变成**以时刻为列、比特为行**的时间表(装了 pandas 时是 DataFrame,没装也有内置的简化表格): ```python from uniqc.visualization import format_result, create_time_line_table layers, qubits, time_points = format_result( circuit, gate_durations={"1q": 20, "2q": 40, "measure": 100} ) table = create_time_line_table(layers, qubits, time_points) print(table) ``` ```text 0 20 60 80 120 140 qubit 0 SX 0.0 CZ 0.0 SX 0.0 CZ 0.0 MEASURE idle qubit 1 SX 0.0 CZ 0.0 MEASURE idle idle idle qubit 2 SX 0.0 RZ 3.142 idle CZ 0.0 SX 0.0 MEASURE ``` 读表:单元格标签是"门名 + 首参数"(圆周率显示成 3.142);列标题是各门的起始时刻;没有门的格子标 `idle`。同一比特同一时刻叠了多个门时,后写入的标签覆盖先写入的——例如 `qubit 0` 在 20 ns 列同时有一个零时长 `RZ` 和一个 `CZ`,表格里显示 `CZ 0.0`。可以看出 `qubit 1` 在 160 ns 之后就全部空闲,整条线路的总时长由 `qubit 2` 的关键路径决定:CZ(80–120) → SX(120–140) → MEASURE(140–240)。 `plot_time_line` 把这张表画成分色表格图(超过 20 列自动分页),输出为 PDF 文件: ```python from uniqc.visualization import plot_time_line plot_time_line( circuit, figure_save_path="timeline_out", gate_durations={"1q": 20, "2q": 40, "measure": 100}, ) ``` 生成 `timeline_out/timeline_1.pdf`,效果(本页转成 PNG 嵌入): ![](timeline-table.png) 想要**真实时间刻度**的矢量图,用 `plot_time_line_html`:门的宽度正比于时长、顶部标出时间轴、悬停可见每个门的起止时间: ```python from uniqc.visualization import plot_time_line_html plot_time_line_html( circuit, output_path="ghz-timeline.html", gate_durations={"1q": 20, "2q": 40, "measure": 100}, ) ``` ![](timeline-html.png) 悬停第一个 `RZ` 时看到的提示内容(真实输出): ```text gate: RZ qubits: [0] start: 0 ns duration: 0 ns end: 0 ns params: [1.5707963267948966] raw: ('RZ', 0, None, 1.5707963267948966, False, None) ``` 从时间轴图能直观看到改线路的方向:把读出(`MEASURE`,往往是最慢的操作)从关键路径上挪开、或缩短两比特门,总时长就会跟着降——这正是第 6 章校准数据(真实门时长)接进来之后时序图的价值。 ## 下一步 - 把测量统计图用进真实算法:[第 7 章 VQE 实操](../ch07-labs/index.md),理论推导见姊妹站 [VQE 教程](https://chenzhaoyun.com/quantum-tutorial/ch08-qml/vqe-tutorial.html) - 云端/真机任务的结果长什么样、回来后怎么后处理:[第 5 章 云端执行](../ch05-cloud/index.md) - 时序图的输入是"编译后"线路,编译与线路构建细节见[第 2 章](../ch02-circuits/index.md);含噪声模拟见[第 3 章](../ch03-simulation/index.md) ## 练习题 **练习 1【可视化工具箱与安装】**(→ [第 1 节](#uqt-vis-toolbox)) 1. 在全新虚拟环境中安装 `unified-quantum[visualization]`,运行本节导入代码,确认打印 `0.1.0` 且没有 ImportError。 2. 卸载 pandas(`pip uninstall -y pandas`)后重跑第 6 节的时间表代码,先预测输出的形态会怎样退化,再验证。 > 提示:`create_time_line_table` 内置了不依赖 pandas 的简化表格。 **练习 2【测量结果直方图】**(→ [第 2 节](#uqt-vis-histogram)) 1. 把 GHZ 线路中的 `cnot(1, 2)` 改成 `cnot(0, 2)`,先笔算测量分布,再画直方图验证。 2. 分别用 128 次和 8192 次采样画直方图,预测哪一张更接近 50/50,并用二项分布标准差解释。 > 提示:柱高的典型涨落约为 $\sqrt{Np(1-p)}/N$。 **练习 3【概率分布与均匀参考线】**(→ [第 3 节](#uqt-vis-distribution)) 1. 把 `uni.h(1)` 改成 `uni.x(1)`,预测哪几根柱子会消失、剩下的柱子大约多高,再运行验证。 2. 构造 4 比特均匀叠加态并画 `plot_distribution`,先算出红色参考线的纵坐标再验证。 > 提示:$n$ 比特时参考线在 $1/2^n$ 处。 **练习 4【静态 HTML 线路图】**(→ [第 4 节](#uqt-vis-circuit-html)) 1. 为 4 比特 GHZ 线路生成 HTML 线路图,数出图中双比特门的个数,与 OriginIR 中的 CNOT 行数对照。 2. 对比 `circuit_to_html` 与第 6 节 `plot_time_line_html` 的输出,指出两者最显著的视觉差异及各自强调的信息。 > 提示:一个按"层"等宽排布,一个按"纳秒"等比排布。 **练习 5【门时序调度】**(→ [第 5 节](#uqt-vis-schedule)) 1. 把 `measure` 时长从 100 改成 40,先笔算新的 `total_duration`,再运行验证。 2. 去掉 `gate_durations` 重跑 `schedule_circuit`,先预测哪个门最先触发 `TimelineDurationError`,再验证;解释为什么报错的永远不会是 RZ。 > 提示:虚拟 Z 门在调度器里的时长恒为 0,不需要查表。 **练习 6【时间表与时间轴图】**(→ [第 6 节](#uqt-vis-timeline)) 1. 对照第 6 节的时间表,写出 `qubit 1` 的空闲区间,并说明总时长由哪条关键路径决定。 2. 构造 `x(0)` 加测量的单比特线路,先预测时间表有几行几列、非 idle 的门各在什么时刻,再生成验证。 > 提示:X 门同样会被编译分解为 RZ–SX–RZ。