可视化:线路图、测量统计图与时序图

量子程序本身是"看不见"的:线路是一串指令文本、测量结果是上千个比特串、执行过程更是纳秒级的物理操作。UnifiedQuantum 的可视化工具箱把这三类信息变成看得见的图——线路图回答"我的程序长什么样",测量统计图回答"我测到了什么、离理论有多远",时序图回答"这段程序在硬件上要跑多久、哪里在空转"。

理论背景(测量概率与玻恩规则、叠加态为何测出均匀分布)见量子计算算法教程·量子计算基础。本页示例基于 unified-quantum 0.1.0(含 visualization 扩展),所有输出均为实际运行结果;涉及随机采样的结果每次运行略有不同。

本课知识点

  1. 可视化工具箱与安装——能安装 visualization 扩展,解释三类可视化各自回答的问题并选用合适的 API。

  2. 测量结果直方图——能用 plot_histogram 把采样计数画成直方图,并写出十进制计数键到二进制串键的转换。

  3. 概率分布与均匀参考线——能用 plot_distribution 画概率分布,解释红色参考线 \(1/2^n\) 的含义并估计涨落范围。

  4. 静态 HTML 线路图——能用 circuit_to_html 生成带悬停提示的静态 SVG 线路图文件,并读懂图中各元素。

  5. 门时序调度——能用 schedule_circuit 配合 gate_durations 计算编译后线路的总时长,解释虚拟 Z 门零时长与左对齐调度。

  6. 时间表与时间轴图——能读懂 create_time_line_table 的时间表,用 plot_time_line(_html) 生成时间轴图并指出并行与空闲。

1. 可视化工具箱与安装

可视化依赖 matplotlib 与 pandas,它们是可选依赖,安装时带上 visualization 扩展一次装齐:

pip install "unified-quantum[visualization]"

装好后从 uniqc.visualization 导入本章用到的全部函数并验证版本:

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__)
0.1.0

按"回答的问题"把工具箱分成三格:

回答的问题

API

输出形态

我测到了什么?

plot_histogram / plot_distribution

matplotlib 图

线路长什么样?

circuit_to_htmldraw_html(文本版 draworiginq 扩展)

静态 HTML/SVG

要跑多久?

schedule_circuitcreate_time_line_tableplot_time_line(_html)

调度对象 / 表格 / PDF / HTML

线路构建与模拟的基础(Circuitsimulate_shots)见第 1 章第 2 章

2. 测量结果直方图

用 GHZ 线路做例子:h(0) 制造叠加,两个 CNOT 把纠缠"串"到第三个比特。simulate_shots 返回"十进制整数 → 计数",而 plot_histogram 要的是"二进制串 → 概率",转换只需一行:

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)
{'111': 0.5146484375, '000': 0.4853515625}

(计数来自随机采样,每次运行略有不同。)两根柱子各约 0.5,正是 GHZ 态 \(\frac{1}{\sqrt2}(\lvert000\rangle+\lvert111\rangle)\) 的理论预测。画图并保存:

plot_histogram(probs, title="GHZ state, 1024 shots")
plt.gcf().savefig("ghz-histogram.png", dpi=150, bbox_inches="tight")

三个使用要点:

  • 键必须是等长二进制串(长度等于比特数)。整数键用 format(int(k), f"0{n}b") 转换;注意最右侧字符对应 c[0]——两比特线路 x(0) 后测量,占优的串是 '01'

  • 也可以直接传概率向量列表,如理想 GHZ 的 [0.5, 0, 0, 0, 0, 0, 0, 0.5],横轴标签会自动生成 000111

  • plot_histogram 内部调用 plt.show()(Jupyter 里直接内嵌显示);脚本里用 plt.gcf().savefig(...) 保存图片。

如果走统一执行入口,UnifiedResult.probabilities 的键本来就是二进制串,可以直接喂给 plot_histogram

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")
{'000': 512, '111': 512}

dummy 后端按理论概率确定性折算计数(恰为 512/512),而 simulate_shots 是按玻恩规则随机采样——所以上一张图的柱高有涨落、这里严格对半。真机测量结果的分布可视化见第 5 章

3. 概率分布与均匀参考线

plot_distribution 的输入格式与 plot_histogram 完全相同,区别是会叠加一条均匀分布参考线 \(1/2^n\),一眼看出结果偏离"完全随机"有多远。对三比特均匀叠加态测量 2048 次:

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)
{'000': 0.11865234375, '001': 0.1201171875, '010': 0.12109375, '011': 0.12939453125, '100': 0.13232421875, '101': 0.12890625, '110': 0.12109375, '111': 0.12841796875}

(每次运行略有不同。)画图:

plot_distribution(probs_u, title="Uniform superposition, 2048 shots")
plt.gcf().savefig("uniform-distribution.png", dpi=150, bbox_inches="tight")

红色虚线在 \(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 教程与本站第 7 章实操

4. 静态 HTML 线路图

circuit_to_html 把线路渲染成一个静态 HTML/SVG 文件——不依赖任何可选库、不带 JavaScript,浏览器直接打开:

from uniqc.visualization import circuit_to_html

document = circuit_to_html(circuit, output_path="ghz-circuit.html", title="GHZ circuit")
print(len(document))
4112

返回值是完整 HTML 字符串(4112 字节),output_path 同时把它写入 ghz-circuit.html。渲染效果(本页把其中的 SVG 渲染成 PNG 嵌入):

读图:每条横线是一个量子比特的导线,方块是门——单比特门落在自己的导线上,双比特门用竖线连接两条导线,末端的 M 是测量。把鼠标悬停在任一门上,会弹出该门的比特、参数等原始信息;这是嵌在 SVG 里的原生提示,无需联网、无需插件,适合放进报告或网页。

两个近亲入口:

  • draw_html(circuit.originir, output_path=...):等价写法,第一个参数是线路文本(OriginIR 或 QASM,后者用 language="QASM");

  • draw(circuit.originir):在终端打印纯文本线路图,需要 pip install "unified-quantum[originq]"(提供 pyqpanda3)后可用。

5. 门时序调度:这段程序要跑多久

逻辑线路里没有时间——"跑多久"取决于硬件上每个门实际消耗多少纳秒。schedule_circuit 做两件事:

  1. 编译到基门集(默认 {cz, sx, rz}):H 被分解为 RZ–SX–RZ 三步旋转(角度由编译器决定),CNOT 变成 CZ 加单比特旋转——旋转门的数学见姊妹站参数化门附录

  2. 左对齐调度:每个门在它涉及的所有比特都空闲的最早时刻开工,不同比特上的门并行执行。

门时长由 gate_durations 给出——键可以是精确门名,也可以用 1q / 2q / measure 三个通配键(接真机时也可改由后端元数据提供):

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)
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 门"——只改动后续脉冲的相位参考、不发射脉冲,因此不占时长(上面所有 RZduration 都是 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、也没有后端元数据时,调度器无法确定门时长,直接抛错:

from uniqc.exceptions import TimelineDurationError

try:
    schedule_circuit(circuit)
except TimelineDurationError as e:
    print(str(e).splitlines()[0])
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'.

6. 时间表与时间轴图

format_result 把调度结果整理成"层 → 门列表"的表格数据,create_time_line_table 再变成以时刻为列、比特为行的时间表(装了 pandas 时是 DataFrame,没装也有内置的简化表格):

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)
            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 文件:

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 嵌入):

想要真实时间刻度的矢量图,用 plot_time_line_html:门的宽度正比于时长、顶部标出时间轴、悬停可见每个门的起止时间:

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},
)

悬停第一个 RZ 时看到的提示内容(真实输出):

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 章校准数据(真实门时长)接进来之后时序图的价值。

下一步

练习题

练习 1【可视化工具箱与安装】(→ 第 1 节

  1. 在全新虚拟环境中安装 unified-quantum[visualization],运行本节导入代码,确认打印 0.1.0 且没有 ImportError。

  2. 卸载 pandas(pip uninstall -y pandas)后重跑第 6 节的时间表代码,先预测输出的形态会怎样退化,再验证。

提示:create_time_line_table 内置了不依赖 pandas 的简化表格。

练习 2【测量结果直方图】(→ 第 2 节

  1. 把 GHZ 线路中的 cnot(1, 2) 改成 cnot(0, 2),先笔算测量分布,再画直方图验证。

  2. 分别用 128 次和 8192 次采样画直方图,预测哪一张更接近 50/50,并用二项分布标准差解释。

提示:柱高的典型涨落约为 \(\sqrt{Np(1-p)}/N\)

练习 3【概率分布与均匀参考线】(→ 第 3 节

  1. uni.h(1) 改成 uni.x(1),预测哪几根柱子会消失、剩下的柱子大约多高,再运行验证。

  2. 构造 4 比特均匀叠加态并画 plot_distribution,先算出红色参考线的纵坐标再验证。

提示:\(n\) 比特时参考线在 \(1/2^n\) 处。

练习 4【静态 HTML 线路图】(→ 第 4 节

  1. 为 4 比特 GHZ 线路生成 HTML 线路图,数出图中双比特门的个数,与 OriginIR 中的 CNOT 行数对照。

  2. 对比 circuit_to_html 与第 6 节 plot_time_line_html 的输出,指出两者最显著的视觉差异及各自强调的信息。

提示:一个按"层"等宽排布,一个按"纳秒"等比排布。

练习 5【门时序调度】(→ 第 5 节

  1. measure 时长从 100 改成 40,先笔算新的 total_duration,再运行验证。

  2. 去掉 gate_durations 重跑 schedule_circuit,先预测哪个门最先触发 TimelineDurationError,再验证;解释为什么报错的永远不会是 RZ。

提示:虚拟 Z 门在调度器里的时长恒为 0,不需要查表。

练习 6【时间表与时间轴图】(→ 第 6 节

  1. 对照第 6 节的时间表,写出 qubit 1 的空闲区间,并说明总时长由哪条关键路径决定。

  2. 构造 x(0) 加测量的单比特线路,先预测时间表有几行几列、非 idle 的门各在什么时刻,再生成验证。

提示:X 门同样会被编译分解为 RZ–SX–RZ。