# 本地模拟与含噪模拟 上一章我们用 `Simulator` 跑过一次无噪声采样;本章把它讲透:`Simulator` 的三种输出各回答什么问题、态矢量与密度矩阵两类后端如何取舍、怎样用错误加载器与读出错误手工构造噪声,以及 dummy 后端家族——从无约束模拟器、虚拟拓扑、真实芯片标定噪声到完全由一份 YAML 声明的含噪虚拟机。 态矢量、测量统计与纠缠的理论背景见[量子计算算法教程·量子计算基础](https://chenzhaoyun.com/quantum-tutorial/ch01-basics/quantum-computing-basics.html)与[量子力学的基本概念](https://chenzhaoyun.com/quantum-tutorial/ch01-basics/quantum-mechanics-basics.html)。本页示例基于 `unified-quantum` **0.1.0**,所有输出均为实际运行结果;凡涉及随机采样的输出**每次运行略有不同**。 :::{admonition} 本课知识点 :class: tip 1. **[三种本地模拟输出](#uqt-sim-outputs)**——能写出 `simulate_pmeasure` / `simulate_statevector` / `simulate_shots` 的调用方式,并区分它们各自回答的问题。 2. **[模拟后端:态矢量与密度矩阵](#uqt-sim-backends)**——能比较 statevector 与 density_matrix 两类后端的能力与开销,并解释噪声模拟为何必须用密度矩阵。 3. **[错误模型与错误加载器](#uqt-sim-error-loader)**——能用三层 `ErrorLoader` 构造从全局到逐门实例的噪声配置,并预测噪声如何让 GHZ 态"泄漏"到其他基矢。 4. **[读出错误](#uqt-sim-readout)**——能写出 `readout_error` 二元组的含义,并手算它对测量概率的作用。 5. **[dummy 后端家族](#uqt-sim-dummy-family)**——能比较 `dummy:local:*` 各成员的拓扑约束与模拟引擎,并解释统一入口如何呈现拓扑不兼容错误。 6. **[声明式含噪虚拟机](#uqt-sim-virtual-machine)**——能用一份 YAML 声明拓扑、门噪声、热弛豫与读出错误,构造 `dummy:virtual:` 并运行任务。 ::: (uqt-sim-outputs)= ## 1. 三种本地模拟输出 以 GHZ 线路为例——`h(0)` 制造叠加,两级 CNOT 把纠缠传播到全部三个比特: ```python from uniqc import Circuit from uniqc.simulator import Simulator c = Circuit() c.h(0) c.cnot(0, 1) c.cnot(1, 2) c.measure(0, 1, 2) sim = Simulator() probs = sim.simulate_pmeasure(c.originir) print("== simulate_pmeasure ==") for state, prob in enumerate(probs): if prob > 1e-9: print(f" |{state:03b}>: {prob:.4f}") sv = sim.simulate_statevector(c.originir) print("== simulate_statevector ==") for state, amp in enumerate(sv): if abs(amp) > 1e-9: print(f" |{state:03b}>: {amp:.4f}") counts = sim.simulate_shots(c.originir, shots=2000) print("== simulate_shots ==") print(counts) ``` ```text == simulate_pmeasure == |000>: 0.5000 |111>: 0.5000 == simulate_statevector == |000>: 0.7071+0.0000j |111>: 0.7071+0.0000j == simulate_shots == {7: 965, 0: 1035} ``` 三个方法回答三个不同的问题: - `simulate_pmeasure`:**理想概率分布**——测量前先算出每个基矢的概率,返回长度 $2^n$ 的列表,下标就是测量结果的十进制值; - `simulate_statevector`:**末态幅值**——拿到态矢量本身(复数振幅),适合检查干涉与相位;GHZ 态幅值 $\tfrac{1}{\sqrt 2}\approx 0.7071$,模平方才是概率 $0.5$; - `simulate_shots`:**采样统计**——按概率分布抽 shots 次样并计数,模拟真实实验"重复测量"的过程,键是十进制整数(`7` 即 `111`)。 概率列表的位序约定:下标 `i` 的二进制表示 `f"{i:03b}"` 中,**qubit 0 在最右**(LSB),与全站测量结果的位串约定一致。`Simulator` 的输入既可以是 `originir` 字符串(如上),也可以直接传 `Circuit` 对象或 OpenQASM 2.0 字符串,格式自动检测。 (uqt-sim-backends)= ## 2. 模拟后端:态矢量与密度矩阵 `Simulator` 底层有两类引擎,由 `backend_type` 选择。同一个无噪声 Bell 线路在两个后端下给出完全一致的概率: ```python from uniqc import Circuit from uniqc.simulator import Simulator bell = Circuit() bell.h(0) bell.cnot(0, 1) bell.measure(0, 1) sv_sim = Simulator(backend_type="statevector") dm_sim = Simulator(backend_type="density_matrix") print("statevector: ", [f"{p:.4f}" for p in sv_sim.simulate_pmeasure(bell.originir)]) print("density_matrix:", [f"{p:.4f}" for p in dm_sim.simulate_pmeasure(bell.originir)]) ``` ```text statevector: ['0.5000', '0.0000', '0.0000', '0.5000'] density_matrix: ['0.5000', '0.0000', '0.0000', '0.5000'] ``` | 后端(`backend_type`) | 噪声支持 | 内存开销 | 适用场景 | |---|---|---|---| | `statevector` | ❌ | $O(2^n)$ | 无噪声快速模拟,默认值 | | `density_matrix` | ✅ | $O(4^n)$ | 含噪声模拟(`NoisySimulator` 的标配) | | `density_operator_qutip` | ✅ | $O(4^n)$ | 复杂噪声模型,需额外安装 `unified-quantum[simulation]` | 噪声为什么必须用密度矩阵?态矢量只能描述**纯态**,而任何噪声通道都会把纯态变成**混合态**——只能用密度矩阵 $\rho$ 编码。`NoisySimulator` 强制检查这一点,在默认 statevector 后端上调用会直接报错: ```python from uniqc.simulator import NoisySimulator from uniqc.simulator.error_model import ErrorLoader_GenericError, Depolarizing noisy_sv = NoisySimulator( backend_type="statevector", # 态矢量后端不支持噪声通道 error_loader=ErrorLoader_GenericError([Depolarizing(0.01)]), ) try: noisy_sv.simulate_pmeasure(bell.originir) except ValueError as e: print("ValueError:", e) ``` ```text ValueError: simulate_pmeasure is only available for density_operator type OpcodeSimulator backend. ``` 两类后端都接受若干别名(如 `density_matrix` / `density_operator` / CLI 的 `density` 是同一个东西),也可以用统一工厂 `create_simulator(backend="density_matrix")` 构造。无噪声时选默认 `statevector` 即可;本章其余含噪示例一律 `density_matrix`。 (uqt-sim-error-loader)= ## 3. 错误模型与错误加载器 `NoisySimulator` 的门噪声由**错误模型**(描述一种物理噪声通道)与**错误加载器**(决定噪声加在哪些门上)组合而成。常用错误模型都在 `uniqc.simulator.error_model` 里: | 错误模型 | 物理含义 | 参数 | |---|---|---| | `BitFlip` / `PhaseFlip` | 位翻转 / 相位翻转 | `p` | | `Depolarizing` | 单比特去极化 | `p` | | `TwoQubitDepolarizing` | 双比特去极化 | `p` | | `AmplitudeDamping` | 振幅阻尼(能量弛豫) | `gamma` | | `PauliError1Q` | 一般单比特泡利噪声 | `p_x, p_y, p_z` | 错误加载器分三层,逐层收窄作用范围: - `ErrorLoader_GenericError([...])`——所有门加同一组噪声; - `ErrorLoader_GateTypeError(generic_error=..., gatetype_error={...})`——按门类型(`'H'`、`'CNOT'`…)额外加噪; - `ErrorLoader_GateSpecificError(..., gate_specific_error={('CNOT', (0, 1)): [...]})`——精确到"某个门 + 某组比特"。 对 GHZ 线路做三组对比——无噪声、全局 2% 去极化、以及"单比特门 0.5% + CNOT 5%"的分层配置: ```python from uniqc import Circuit from uniqc.simulator import Simulator, NoisySimulator from uniqc.simulator.error_model import ( Depolarizing, TwoQubitDepolarizing, ErrorLoader_GenericError, ErrorLoader_GateTypeError, ) ghz = Circuit() ghz.h(0) ghz.cnot(0, 1) ghz.cnot(1, 2) ghz.measure(0, 1, 2) code = ghz.originir def show(title, probs): print(f"== {title} ==") for state, prob in enumerate(probs): if prob > 1e-9: print(f" |{state:03b}>: {prob:.4f}") show("无噪声", Simulator().simulate_pmeasure(code)) generic = NoisySimulator( backend_type="density_matrix", error_loader=ErrorLoader_GenericError([Depolarizing(0.02)]), ) show("所有门 2% 去极化", generic.simulate_pmeasure(code)) gatetype = NoisySimulator( backend_type="density_matrix", error_loader=ErrorLoader_GateTypeError( generic_error=[Depolarizing(0.005)], gatetype_error={"CNOT": [TwoQubitDepolarizing(0.05)]}, ), ) show("1q 0.5% + CNOT 5% 去极化", gatetype.simulate_pmeasure(code)) ``` ```text == 无噪声 == |000>: 0.5000 |111>: 0.5000 == 所有门 2% 去极化 == |000>: 0.4740 |001>: 0.0129 |010>: 0.0066 |011>: 0.0066 |100>: 0.0066 |101>: 0.0066 |110>: 0.0129 |111>: 0.4740 == 1q 0.5% + CNOT 5% 去极化 == |000>: 0.4614 |001>: 0.0222 |010>: 0.0082 |011>: 0.0082 |100>: 0.0082 |101>: 0.0082 |110>: 0.0222 |111>: 0.4614 ``` 噪声的效果是"泄漏":`000`/`111` 的概率跌破 0.5,差额摊到其余六个基矢上。去极化噪声以单比特/双比特泡利错误为主,因此与 `000`、`111` 汉明距离为 1 的态(`001`、`110` 等)泄漏最明显。两组含噪结果都是确定性的(密度矩阵演化不采样),只有 `simulate_shots` 才有随机涨落。 (uqt-sim-readout)= ## 4. 读出错误:测量端的混淆矩阵 门噪声之外,真实设备还有**读出错误**:测 $|0\rangle$ 读成 1、测 $|1\rangle$ 读成 0。`NoisySimulator` 的 `readout_error` 按比特配置一个二元组: ```text readout_error = {q: [p(0→1), p(1→0)]} # p(0→1):真实为 0 被读成 1 的概率;p(1→0):真实为 1 被读成 0 的概率 ``` 只给 qubit 0 配一组非对称读出错误,其余比特保持理想: ```python from uniqc import Circuit from uniqc.simulator import NoisySimulator ghz = Circuit() ghz.h(0) ghz.cnot(0, 1) ghz.cnot(1, 2) ghz.measure(0, 1, 2) sim = NoisySimulator( backend_type="density_matrix", readout_error={0: [0.10, 0.02]}, ) probs = sim.simulate_pmeasure(ghz.originir) for state, prob in enumerate(probs): if prob > 1e-9: print(f" |{state:03b}>: {prob:.4f}") ``` ```text |000>: 0.4500 |001>: 0.0500 |110>: 0.0100 |111>: 0.4900 ``` 这组数字可以完全手算。理想分布是 $P(000)=P(111)=0.5$,读出错误只作用在 $q_0$(位串最右位)上: - $P(000) = 0.5 \times (1-0.10) = 0.45$,$P(001) = 0.5 \times 0.10 = 0.05$(真实 0 被读成 1); - $P(111) = 0.5 \times (1-0.02) = 0.49$,$P(110) = 0.5 \times 0.02 = 0.01$(真实 1 被读成 0)。 实现上就是把每个含错误比特的边缘概率乘上一个 $2\times 2$ 混淆矩阵,因此 `simulate_pmeasure` 的结果依然是确定性的。注意一个已知限制:密度矩阵路径不支持 `simulate_shots`(会抛 `NotImplementedError: Density matrix is not supported for shot simulation.`)——想对含噪线路做端到端采样,用下面两节的 dummy 后端家族。 (uqt-sim-dummy-family)= ## 5. dummy 后端家族:统一入口的本地成员 第 1 章我们用 `submit_task` + `wait_for_result` 走过统一执行管道;dummy 家族就是这条管道的本地成员,backend id 的语法本身描述了机器的约束与噪声: | backend id | 拓扑 | 噪声 | 说明 | |---|---|---|---| | `dummy:local:simulator` | 全连接 | 无 | 无约束虚拟机,调试首选 | | `dummy:local:virtual-line-N` | $N$ 比特链 | 无 | 虚拟链式拓扑 | | `dummy:local:virtual-grid-RxC` | $R\times C$ 网格 | 无 | 虚拟网格拓扑 | | `dummy:local:mps-linear-N:chi=K[:seed=S]` | $N$ 比特链 | 无 | MPS 引擎,大比特数低纠缠可扩展 | | `dummy::` | 真实芯片耦合图 | 芯片标定噪声 | 先按真芯片编译,再本地注入噪声 | | `dummy:virtual:` | YAML 声明 | YAML 声明 | 用户自定义含噪虚拟机(第 6 节) | 用 `resolve_dummy_backend` 可以先"拆开"一个 backend id 看它的规格,再提交 GHZ 线路: ```python from uniqc import Circuit, submit_task, wait_for_result, TaskFailedError from uniqc.backend_adapter.dummy_backend import resolve_dummy_backend ghz = Circuit(3) ghz.h(0) ghz.cnot(0, 1) ghz.cnot(1, 2) ghz.measure(0, 1, 2) for backend in ( "dummy:local:simulator", "dummy:local:virtual-line-3", "dummy:local:mps-linear-3:chi=8:seed=2024", ): spec = resolve_dummy_backend(backend) print(f"== {backend} ==") print(" description: ", spec.description) print(" topology: ", spec.available_topology) print(" simulator_kwargs:", spec.simulator_kwargs) result = wait_for_result(submit_task(ghz, backend=backend, shots=512), timeout=30) print(" counts: ", result.counts) ``` ```text == dummy:local:simulator == description: Unconstrained noiseless local simulator topology: None simulator_kwargs: None counts: {'000': 256, '111': 256} == dummy:local:virtual-line-3 == description: Noiseless virtual 3-qubit line topology topology: [[0, 1], [1, 2]] simulator_kwargs: None counts: {'000': 256, '111': 256} == dummy:local:mps-linear-3:chi=8:seed=2024 == description: Noiseless MPS simulator on a 3-qubit linear chain, chi=8 topology: [[0, 1], [1, 2]] simulator_kwargs: {'chi_max': 8, 'seed': 2024} counts: {'111': 250, '000': 262} ``` 三个成员的差别一目了然:`simulator` 无拓扑(`None` 即全连接);`virtual-line-3` 声明了链 `0-1-2`;MPS 成员额外携带引擎参数 `chi_max` 与 `seed`。`counts` 的键是二进制串(`UnifiedResult` 的统一格式),采样计数每次运行略有不同。 拓扑约束不是摆设。链 `0-1-2` 上没有 `0-2` 边,直接发一个 `cnot(0, 2)` 的线路,任务会在**取结果时**失败——与真实云后端"提交成功、执行报错"的形态完全一致: ```python bad = Circuit(3) bad.h(0) bad.cnot(0, 2) # 0-2 不是链式拓扑 0-1-2 上的相邻边 bad.measure(0, 1, 2) try: wait_for_result(submit_task(bad, backend="dummy:local:virtual-line-3", shots=512), timeout=30) except TaskFailedError as e: print("TaskFailedError:", str(e)[:88], "...") ``` ```text TaskFailedError: Task 'uqt_e412d78f19d741ba9ca4345b639c2cce' failed on backend 'dummy:local:virtual-line- ... ``` (完整错误信息里带有 `Unsupported topology.` 与该线路的 OriginIR;`task_id` 每次运行不同。) 最有意思的成员是 `dummy::`:它按**真实芯片**编译线路,再用本地密度矩阵模拟器注入该芯片**标定数据换算出的噪声**——全程离线,不需要 token。前提是本地已有芯片缓存(由 `uniqc backend update --platform originq` 拉取,那一步需要平台 token,见第 5 章);下面的例子直接使用已缓存的 OriginQ WK_C180: ```python from uniqc import Circuit, submit_task, wait_for_result bell = Circuit() bell.h(0) bell.cnot(0, 9) # WK_C180 耦合图上的一条真实相邻边 bell.measure(0, 9) result = wait_for_result(submit_task(bell, backend="dummy:originq:WK_C180", shots=512), timeout=60) print(result) ``` ```text UnifiedResult(counts={'00': 235, '01': 21, '10': 21, '11': 235}, probabilities={'00': 0.458984375, '01': 0.041015625, '10': 0.041015625, '11': 0.458984375}, shots=512, platform='dummy', task_id='uqt_0f08ed240386468282555e1d1305f78d', backend_name='dummy:originq:WK_C180', execution_time=None, error_message=None) ``` 与理想 Bell 态(只剩 `00`/`11`)相比,真实标定噪声带来了约 4% 的 `01`/`10` 泄漏——在不上真机、不花 token 的情况下,你已经在模拟"这台机器会给出什么样的结果"。 (uqt-sim-virtual-machine)= ## 6. 声明式含噪虚拟机:dummy:virtual 手写 `ErrorLoader` 适合程序化构造;如果只想用一份配置文件描述整台含噪机器(拓扑、分层门噪声、T1/T2 热弛豫、逐比特读出错误),写一个 YAML 到 `~/.uniqc/backend/virtual/.yaml` 即可(`uniqc backend virtual init ` 可生成带完整注释的模板)。虚拟机名就是文件名,之后任何接受 backend 的地方都能用 `dummy:virtual:`: ```yaml # ~/.uniqc/backend/virtual/uqt-tutorial.yaml description: 教学用 3 比特含噪虚拟机 num_qubits: 3 topology: - [0, 1] - [1, 2] gate_times_ns: default_1q: 30 default_2q: 80 noise: depolarizing: 1q: 0.001 2q: 0.02 gate_type: CZ: {depolarizing: 0.03} thermal_relaxation: default: {t1_us: 50, t2_us: 40} qubits: 2: {t1_us: 30, t2_us: 25} readout: default: [0.02, 0.02] qubits: 2: [0.05, 0.08] ``` 配置各节读法:`topology` 省略则全连接;`noise.depolarizing` 按门元数(1q/2q)给均匀去极化,`gate_type` 按门名覆盖;`thermal_relaxation` 按 `gate_times_ns` 的门时长把 T1/T2 换算成振幅阻尼 + 退相位(要求 $T_2 \le 2T_1$);`readout` 逐比特给出 `[p(0→1), p(1→0)]`。各层噪声**叠加生效**。 写好后先校验,再看解析结果: ```console $ uniqc backend virtual validate uqt-tutorial ✓ /home/agony/.uniqc/backend/virtual/uqt-tutorial.yaml: valid Qubits: 3, topology edges: 2 Noise: depol, gate-type(1), T1/T2, readout Use: uniqc submit circuit.qasm --backend dummy:virtual:uqt-tutorial ``` ```console $ uniqc backend virtual show uqt-tutorial ───────────────────────────── virtual:uqt-tutorial ───────────────────────────── ╭────────────────────────────────── Overview ──────────────────────────────────╮ │ File: /home/agony/.uniqc/backend/virtual/uqt-tutorial.yaml │ │ Description: 教学用 3 比特含噪虚拟机 │ │ Qubits: 3 (0, 1, 2) │ │ Topology: 2 edges │ │ Reference: dummy:virtual:uqt-tutorial │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────── Gate Error Model ──────────────────────────────╮ │ Uniform depolarizing: 1q=0.001, 2q=0.02 │ │ Gate type CZ: depolarizing 0.03 │ ╰──────────────────────────────────────────────────────────────────────────────╯ Gate times: default_1q=30.0ns, default_2q=80.0ns Per-Qubit Noise ╭──────────┬────────────┬────────────┬──────────────┬──────────────╮ │ Qubit │ T1 (us) │ T2 (us) │ Readout p01 │ Readout p10 │ ├──────────┼────────────┼────────────┼──────────────┼──────────────┤ │ 0 │ 50.0 │ 40.0 │ 0.0200 │ 0.0200 │ │ 1 │ 50.0 │ 40.0 │ 0.0200 │ 0.0200 │ │ 2 │ 30.0 │ 25.0 │ 0.0500 │ 0.0800 │ ╰──────────────────────────────────────────────────────────────────────────────╯ ``` 最后用统一入口把 GHZ 线路分别跑在无噪声与这台虚拟机上: ```python from uniqc import Circuit, submit_task, wait_for_result ghz = Circuit(3) ghz.h(0) ghz.cnot(0, 1) ghz.cnot(1, 2) ghz.measure(0, 1, 2) for backend in ("dummy:local:simulator", "dummy:virtual:uqt-tutorial"): result = wait_for_result(submit_task(ghz, backend=backend, shots=2048), timeout=60) print(f"== {backend} ==") print(" counts:", result.counts) ``` ```text == dummy:local:simulator == counts: {'000': 1024, '111': 1024} == dummy:virtual:uqt-tutorial == counts: {'000': 910, '001': 37, '010': 27, '011': 84, '100': 54, '101': 26, '110': 35, '111': 875} ``` 同一份代码,只换 backend 字符串,就从理想世界搬进了"有 2% 双比特去极化、qubit 2 更差"的机器。与 `dummy::` 的差别在噪声来源:后者从芯片标定数据换算,前者完全由你的 YAML 声明(且支持真双比特退极化通道与 T1/T2 热弛豫)。虚拟机同样受拓扑约束——YAML 里删掉 `topology` 节即可得到全连接机器。 ## 下一步 - 噪声让 `01`/`10` 出现了——如何**测量并缓解**这些误差,见[第 6 章:芯片校准与误差缓解](../ch06-calibration-qem/index.md) - 把模拟结果画成图(计数直方图、线路图),见[第 4 章:可视化](../ch04-visualization/index.md) - 准备把线路提交到真实云平台,见[第 5 章:云端执行](../ch05-cloud/index.md) - 混合态与密度矩阵的测量理论(量子层析),见姊妹站[量子态层析](https://chenzhaoyun.com/quantum-tutorial/ch07-quantum-info/quantum-state-tomography-tutorial.html) ## 练习题 **练习 1【三种本地模拟输出】**(→ [第 1 节](#uqt-sim-outputs)) 1. 把 GHZ 线路中的 `h(0)` 改成 `x(0)`,先笔算 `simulate_pmeasure` 与 `simulate_shots` 的结果,再运行验证。 2. 解释输出中幅值 `0.7071` 与概率 `0.5000` 的关系:statevector 与 pmeasure 各自存的是什么量? 3. 构造一个线路,使 `|001>` 与 `|110>` 的概率各为 0.5(其余为 0),用 `simulate_statevector` 验证幅值。 > 提示:在 GHZ 线路末尾再加一个 `x(0)`,同时翻转两个分量的最右比特(qubit 0)。 **练习 2【模拟后端:态矢量与密度矩阵】**(→ [第 2 节](#uqt-sim-backends)) 1. 分别用 `statevector` 与 `density_matrix` 后端跑 GHZ 线路的 `simulate_pmeasure`,确认结果一致。 2. 把 `NoisySimulator` 的 `backend_type` 故意设为 `statevector` 并调用 `simulate_pmeasure`,记录报错信息。 3. 解释为什么噪声通道作用后的态无法再用态矢量表示,而密度矩阵始终可以。 > 提示:噪声带来经典不确定性——以概率 $p$ 处于不同纯态的混合,对应 $\rho=\sum_i p_i|\psi_i\rangle\langle\psi_i|$,一般不再是秩 1 的投影算符。 **练习 3【错误模型与错误加载器】**(→ [第 3 节](#uqt-sim-error-loader)) 1. 把分层配置中 CNOT 的噪声从 5% 改成 20%,先预测 `|000>` 概率的移动方向和泄漏态的分布,再运行验证。 2. 用 `AmplitudeDamping(0.1)` 替换 `Depolarizing` 作为通用噪声,观察 GHZ 分布的泄漏模式有何不同(哪个方向偏得多)。 3. 改用 `ErrorLoader_GateSpecificError`,只给第二个 CNOT(比特 1、2)加 10% 去极化,预测哪些错误态概率升高,再验证。 > 提示:与施加噪声的门相邻的比特最容易被翻转,看泄漏态与 `000`/`111` 的汉明距离。 **练习 4【读出错误】**(→ [第 4 节](#uqt-sim-readout)) 1. 对 GHZ 线路把读出错误改为 `{0: [0.0, 0.3]}`,先手算四个非零概率再运行验证。 2. 给三个比特同时配 `{i: [0.1, 0.1]}` 的对称读出错误,预测 `000` 与 `111` 的概率各是多少(其余态不必逐个算)。 3. 试着对 `NoisySimulator` 调用 `simulate_shots`,记录报错,并改用第 6 节的 `dummy:virtual` 虚拟机完成同样的含噪采样。 > 提示:对称错误下每个比特读对的概率是 0.9,三个比特独立读对的概率是 $0.9^3$。 **练习 5【dummy 后端家族】**(→ [第 5 节](#uqt-sim-dummy-family)) 1. 用 `resolve_dummy_backend` 查看 `dummy:local:virtual-grid-2x2` 的拓扑,注意标准 GHZ 线路的 `cnot(1, 2)` 并不是网格上的边;改写线路(仍制备 GHZ 态)后提交并验证计数。 2. 解释 `simulate_shots` 返回 `{7: 965, 0: 1035}` 而 `wait_for_result` 返回 `{'000': ..., '111': ...}`:两套键格式分别是什么,为什么要统一后者? 3. 把 `bad` 线路提交到 `dummy:local:simulator`,验证全连接后端不报拓扑错误;再对比第 5 节的失败信息,总结"拓扑约束由 backend id 声明"这句话的含义。 > 提示:网格上 qubit 0 与 1、2 都相邻,用 `cnot(0, 1)` 与 `cnot(0, 2)` 的"星形"布局同样能制备 GHZ 态。 **练习 6【声明式含噪虚拟机】**(→ [第 6 节](#uqt-sim-virtual-machine)) 1. 用 `uniqc backend virtual init` 生成模板,把 qubit 0 的读出错误改成 `[0.2, 0.2]`,预测 GHZ 泄漏如何加剧,再提交验证。 2. 把 YAML 中 `t2_us` 改成大于 `2 * t1_us` 的值,运行 `uniqc backend virtual validate`,观察并解释报错。 3. 删除 YAML 的 `topology` 节使虚拟机全连接,重新提交第 5 节的 `bad` 线路(含 `cnot(0, 2)`),验证它现在能执行。 > 提示:$T_2 > 2T_1$ 时退相位概率 $p_\varphi = 0.5(1-\mathrm{e}^{-t(1/T_2-1/2T_1)})$ 会变成负数,物理上不允许。