区分 Hybrid 实验中的三种变化来源¶
先制备一对纠缠原子,施加带局域失谐的 Analog 脉冲,再旋转测量基。比较理想演化、退相干、退相干加读出误差三种情况。本项目要回答的是:计数变化来自量子态改变、测量记录出错,还是有限次采样?
开始前完成共享 Hybrid 状态、物理退相干、局域失谐和实验设计。按安装说明准备环境,在仓库根目录运行。默认执行九次两原子模拟,每次采样 128 次,Analog 积分分为 32 步,总计 1,152 次采样。所有计算均在本地 CPU 完成。
先固定程序,再比较噪声¶
第一个 Digital 块依次施加 H(0)、CX(0, 1),制备 ( |00⟩ + |11⟩ ) / sqrt(2)。Analog 块持续 0.6 us,全局驱动为 Ω = 1.1 rad/us,相位为零。局域波形恒为 0.7 rad/us,站点权重分别为 q0: 1、q1: 0.25,因此两个原子的局域失谐分别为 0.7、0.175 rad/us。程序没有声明相互作用项。最后一个 Digital 块依次施加 CX(0, 1)、H(0)。
令 n = |1⟩⟨1|,Analog Hamiltonian 为 H = Ω(X0 + X1)/2 − 0.7 n0 − 0.175 n1。最后一块还原了制备门,却没有还原中间的 Analog 演化,因此理想实验也不一定回到 00。
三组对照使用完全相同的程序:
| 对照 | 状态演化 | 记录时的位错误 |
|---|---|---|
ideal |
状态向量 | 无 |
dephasing |
密度矩阵,Analog 块内两个原子的退相干率均为 γ = 0.8 /us |
无 |
dephasing_readout |
与上一组相同 | 每一位独立出错:0→1 概率为 0.08,1→0 概率为 0.12 |
这里的退相干约定是 dρ/dt = −i[H,ρ] + (γ/2) Σi (ZiρZi − ρ)。关闭 Hamiltonian 后,单原子密度矩阵的非对角元按 exp(−γt) 衰减。这些参数用于教学,没有拟合实际设备。本模型没有加入 Digital 门误差、制备误差或原子丢失。
运行并阅读对照图¶
python3 examples/learning/projects/noisy_hybrid.py
打开 artifacts/noisy-hybrid/hybrid-zh.html。蓝色方块是加入读出误差前的 00 状态概率,绿色标记是经过经典读出通道后的预期记录概率。三个红点分别对应种子 4881、4882、4883 下的采样频率。红点的横向偏移只是为了避免重叠。
{
"artifacts": {
"data": "artifacts/noisy-hybrid/comparison.json",
"report_en": "artifacts/noisy-hybrid/hybrid-en.html",
"report_zh": "artifacts/noisy-hybrid/hybrid-zh.html"
},
"cases": [
{
"case": "ideal",
"expected_recorded_probabilities": {
"00": 0.5754896316901827,
"01": 0.36395656142510147,
"10": 0.05808150545784879,
"11": 0.002472301426867029
},
"method": "state_vector",
"probabilities_before_readout": {
"00": 0.5754896316901827,
"01": 0.36395656142510147,
"10": 0.05808150545784879,
"11": 0.002472301426867029
}
},
{
"case": "dephasing",
"expected_recorded_probabilities": {
"00": 0.4327894017200914,
"01": 0.23601050347734526,
"10": 0.2900376720088961,
"11": 0.041162422793667296
},
"method": "density_matrix",
"probabilities_before_readout": {
"00": 0.4327894017200914,
"01": 0.23601050347734526,
"10": 0.2900376720088961,
"11": 0.041162422793667296
}
},
{
"case": "dephasing_readout",
"expected_recorded_probabilities": {
"00": 0.42498140707779525,
"01": 0.23005851708015412,
"10": 0.2732802519053948,
"11": 0.07167982393665592
},
"method": "density_matrix",
"probabilities_before_readout": {
"00": 0.4327894017200914,
"01": 0.23601050347734526,
"10": 0.2900376720088961,
"11": 0.041162422793667296
}
}
],
"sdk_version": "1.0.8a",
"shots_per_run": 128,
"total_shots": 1152
}
理想 P(00) 约为 0.57549,退相干后约为 0.43279。增加读出误差不改变这个状态概率,但会让预期记录概率变成约 0.42498。最后这点变化小于 128 次采样下约 0.044 的频率标准差。三个点可以帮助理解采样波动,还不足以可靠估计这样小的实验效应。
设真实结果为 x、记录结果为 y,脚本按 q(y) = Σx P(x) Πi M[yi,xi] 计算预期记录分布,其中 M = [[0.92, 0.12], [0.08, 0.88]],每一列的和为一。q 来自已声明的模型,既不是另一次模拟输出,也没有用计数做拟合。ResultIR.probabilities 和请求的精确可观测量描述读出误差之前的状态;counts 则已经包含读出通道的影响。
检查状态传递和计算成本¶
comparison.json 保存了程序、单位、配置、噪声定义、九份原始结果、种子、计数和可观测量。每次运行的 state_chain 按 Digital → Analog → Digital 记录三个程序块。前一块的输出状态哈希应等于下一块的输入状态哈希。逻辑时间在 Analog 块内从 0 增至 0.6 us;这里的理想 Digital 门不占时长。用这些记录可以检查模拟是否在程序块之间传递了状态。
请求的可观测量是末次旋转之后的 Z(q0)、Z(q1)、Z(q0)*Z(q1)。exact_state 或 exact_density 表示期望值直接从模拟状态计算。标准误差为零,表示该估计没有使用测量采样;它不代表积分误差为零,也没有衡量物理模型参数的不确定性。
两个引擎都使用 complex128。两量子的状态向量有四个复振幅,占 64 字节;密度矩阵有十六个元素,占 256 字节。这只是状态本身的存储量。estimated_peak_bytes 还包含工作区和采样缓冲区,是运行前的估计,不是进程内存实测值。在这样的小规模下,固定开销可能使密度矩阵路径的总估计略小;两种状态的存储规模仍分别按 2^n、4^n 增长。
"""Separate dephasing, readout errors and finite sampling in a two-atom experiment."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
from bokeh.embed import file_html
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.resources import INLINE
import cascaqit
from cascaqit import (
AHSProgram,
AtomRegister,
Circuit,
HybridProgram,
LocalBackend,
ObservableSet,
PauliZ,
PauliZZ,
SimulationOptions,
SitePattern,
Waveform,
)
from cascaqit.simulators import NoiseChannel, NoiseModel
def readout_distribution(
probabilities: dict[str, float], p01: float, p10: float
) -> dict[str, float]:
"""Apply independent classical bit errors to an exact state distribution."""
distribution = {bits: 0.0 for bits in ("00", "01", "10", "11")}
for source, probability in probabilities.items():
for target in distribution:
weight = probability
for before, after in zip(source, target):
error = p01 if before == "0" else p10
weight *= error if before != after else 1 - error
distribution[target] += weight
return distribution
def experiment(
output_dir: Path, *, shots: int = 128, time_steps: int = 32
) -> dict[str, Any]:
"""Save every execution before drawing the two language reports."""
if shots < 1 or time_steps < 1:
raise ValueError("Shots and time steps must be positive.")
output_dir.mkdir(parents=True, exist_ok=True)
duration, omega, detuning, rate = 0.6, 1.1, 0.7, 0.8
p01, p10 = 0.08, 0.12
analog = (
AHSProgram(AtomRegister.line(count=2, spacing=5.0))
.drive(
rabi=Waveform.constant(omega, duration=duration),
detuning=Waveform.constant(0.0, duration=duration),
phase=0.0,
)
.local_detuning(
waveform=Waveform.constant(detuning, duration=duration),
pattern=SitePattern.from_mapping({"q0": 1.0, "q1": 0.25}),
)
)
program = (
HybridProgram("project.noisy.hybrid")
.digital("prepare", Circuit(2).h(0).cx(0, 1))
.analog("evolve", analog)
.digital("readout_rotation", Circuit(2).cx(0, 1).h(0))
.measure_all()
)
cases = (
("ideal", None),
("dephasing", NoiseModel("phase", (NoiseChannel.dephasing(rate),))),
(
"dephasing_readout",
NoiseModel(
"phase.readout",
(NoiseChannel.dephasing(rate), NoiseChannel.readout(p01, p10=p10)),
),
),
)
rows: list[dict[str, Any]] = []
raw: list[dict[str, Any]] = []
for label, noise in cases:
options = SimulationOptions(
method="state_vector" if noise is None else "density_matrix",
dtype="complex128",
integrator="fixed_step_krylov",
max_steps=time_steps,
)
for seed in (4881, 4882, 4883):
result = (
LocalBackend(analog_time_steps=time_steps)
.run(
program,
shots=shots,
seed=seed,
noise=noise,
options=options,
observables=ObservableSet(
(PauliZ("q0"), PauliZ("q1"), PauliZZ("q0", "q1"))
),
)
.result()
)
if result.probabilities is None or result.observable_batch is None:
raise RuntimeError(
"The experiment requires probabilities and observables."
)
expected = readout_distribution(
result.probabilities,
p01 if label == "dephasing_readout" else 0.0,
p10 if label == "dephasing_readout" else 0.0,
)
rows.append(
{
"case": label,
"seed": seed,
"counts": result.counts,
"probabilities_before_readout": result.probabilities,
"expected_recorded_probabilities": expected,
"frequency_00": result.counts.get("00", 0) / shots,
"observables": result.observable_batch.to_dict(),
"state_chain": [
item.to_dict() for item in result.state_transitions()
],
"method": result.metadata["simulation_plan"]["method_selected"],
"resource_estimate": result.metadata[
"simulation_resource_estimate"
],
"options": options.to_dict(),
"noise_model": None if noise is None else noise.to_dict(),
}
)
raw.append(result.to_dict())
artifacts = {"data": str(output_dir / "comparison.json")}
for language in ("en", "zh"):
title = (
"Probability and recorded frequency of 00"
if language == "en"
else "00 的概率与记录频率"
)
names = (
("Ideal", "Dephasing", "Dephasing + readout")
if language == "en"
else ("理想", "退相干", "退相干与读出误差")
)
plot = figure(
title=title, x_range=list(names), y_range=(0, 1), width=880, height=430
)
first = rows[::3]
plot.scatter(
list(names),
[r["probabilities_before_readout"]["00"] for r in first],
size=14,
marker="square",
color="#2563eb",
legend_label="Before readout" if language == "en" else "读出前概率",
)
plot.scatter(
list(names),
[r["expected_recorded_probabilities"]["00"] for r in first],
size=16,
marker="dash",
color="#15803d",
legend_label="Expected recorded" if language == "en" else "预期记录概率",
)
for index in range(3):
plot.scatter(
[(name, (index - 1) * 0.12) for name in names],
[rows[case * 3 + index]["frequency_00"] for case in range(3)],
size=8,
color="#dc2626",
legend_label="Sampled (3 seeds)"
if language == "en"
else "采样频率(三个种子)",
)
plot.legend.location = "bottom_left"
path = output_dir / f"hybrid-{language}.html"
path.write_text(file_html(column(plot), INLINE, title), encoding="utf-8")
artifacts[f"report_{language}"] = str(path)
payload = {
"sdk_version": cascaqit.__version__,
"program": program.to_dict(),
"program_hash": program.stable_hash(),
"duration_us": duration,
"omega_rad_per_us": omega,
"local_detuning_rad_per_us": [detuning, detuning * 0.25],
"dephasing_rate_per_us": rate,
"readout_p01": p01,
"readout_p10": p10,
"shots_per_run": shots,
"total_shots": shots * len(rows),
"time_steps": time_steps,
"rows": rows,
"raw_results": raw,
"artifacts": artifacts,
}
Path(artifacts["data"]).write_text(
json.dumps(payload, indent=2) + "\n", encoding="utf-8"
)
return {
"sdk_version": cascaqit.__version__,
"shots_per_run": shots,
"total_shots": shots * len(rows),
"cases": [
{
key: r[key]
for key in (
"case",
"method",
"probabilities_before_readout",
"expected_recorded_probabilities",
)
}
for r in rows[::3]
],
"artifacts": artifacts,
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--output-dir", type=Path, default=Path("artifacts/noisy-hybrid")
)
parser.add_argument("--shots", type=int, default=128)
parser.add_argument("--time-steps", type=int, default=32)
args = parser.parse_args()
print(
json.dumps(
experiment(args.output_dir, shots=args.shots, time_steps=args.time_steps),
sort_keys=True,
)
)
if __name__ == "__main__":
main()
提交一份实验记录¶
保存脚本提交号、依赖版本、JSON 和报告。先解释哪组比较改变了量子态,再用一张小表回答下面的问题。
- 使用
--shots 2048 --output-dir artifacts/hybrid-more-shots重跑。哪些概率和可观测量应保持不变?采样频率的标准差应怎样变化? - 使用
--time-steps 64 --output-dir artifacts/hybrid-refined重跑。逐项比较与 32 步结果的概率差。为什么使用exact_density仍然需要做这项检查? - 在副本中把两个读出错误概率都设为零。哪两组结果应一致?使用相同种子时,计数是否也应一致?
- 推导
⟨Z0⟩ = P00 + P01 − P10 − P11,与保存的可观测量核对。如果把概率换成采样频率,估计的含义有何变化?
核对思路
增加采样次数不改变状态概率和精确可观测量。从 128 增至 2,048 次,二项采样标准差缩小到原来的四分之一。密度矩阵演化采用有限步长的分裂方法,增加积分步数可以检查数值误差,即使可观测量没有采样也需要检查。读出错误概率为零时,退相干状态和采样位串都不受读出通道改变。可观测量公式采用 q0,q1 位序。改用计数频率后,得到带统计不确定性的采样估计;存在读出错误时,它估计的是记录分布。
研究扩展可以预先选定一组退相干率,增加重复次数,并报告全部运行。在最强退相干率处先检查积分收敛,再解释曲线趋势。两种执行路径复用同一个种子,本身不足以构成受控的配对统计估计。这个小模型只能支持关于所声明模拟通道的结论;与实验室数据比较,还需要实际测得的控制和噪声参数。