跳转至

区分 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 下的采样频率。红点的横向偏移只是为了避免重叠。

打开实验报告

下载原始数据(JSON)

{
  "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 和报告。先解释哪组比较改变了量子态,再用一张小表回答下面的问题。

  1. 使用 --shots 2048 --output-dir artifacts/hybrid-more-shots 重跑。哪些概率和可观测量应保持不变?采样频率的标准差应怎样变化?
  2. 使用 --time-steps 64 --output-dir artifacts/hybrid-refined 重跑。逐项比较与 32 步结果的概率差。为什么使用 exact_density 仍然需要做这项检查?
  3. 在副本中把两个读出错误概率都设为零。哪两组结果应一致?使用相同种子时,计数是否也应一致?
  4. 推导 ⟨Z0⟩ = P00 + P01 − P10 − P11,与保存的可观测量核对。如果把概率换成采样频率,估计的含义有何变化?
核对思路

增加采样次数不改变状态概率和精确可观测量。从 128 增至 2,048 次,二项采样标准差缩小到原来的四分之一。密度矩阵演化采用有限步长的分裂方法,增加积分步数可以检查数值误差,即使可观测量没有采样也需要检查。读出错误概率为零时,退相干状态和采样位串都不受读出通道改变。可观测量公式采用 q0,q1 位序。改用计数频率后,得到带统计不确定性的采样估计;存在读出错误时,它估计的是记录分布。

研究扩展可以预先选定一组退相干率,增加重复次数,并报告全部运行。在最强退相干率处先检查积分收敛,再解释曲线趋势。两种执行路径复用同一个种子,本身不足以构成受控的配对统计估计。这个小模型只能支持关于所声明模拟通道的结论;与实验室数据比较,还需要实际测得的控制和噪声参数。

English version

SDK 1.0.8a · `6eff6362`