跳转至

Rabi 振荡与有限相互作用

扫描恒定驱动的持续时间,比较独立原子与两种距离下的相互作用原子对。完成后,你应得到概率曲线、绘图所用的原始结果,并能解释几何变化怎样影响演化。

先完成全局驱动、物理单位和实验设计。使用安装页的环境,在仓库根目录运行。课堂配置包含三种情况、八个时长,共 24 次双原子运行;每次使用 32 个积分步、256 次采样,总计 6,144 次采样。一次 macOS、Python 3.11 开发环境实测约需 1.5 秒,包含报告生成;这只是该环境下的记录,不是耗时保证。

设置对照

三种情况都从 |00⟩ 开始。全局驱动为 Ω = 2π rad/us,失谐和相位为零,时长从 0.125 到 1 us。独立原子的单原子激发概率为 p(t) = sin²(πt),双激发概率为 p(t)²。当 t = 0.5 us 时,两个原子应同时激发。

另两种情况启用有限相互作用 V = C6/r^6,练习中取 C6 = 312,500 rad*um^6/us,截断半径为 12 um,因此两个距离都在截断范围内。间距 5 um 时,V = 20 rad/us;间距 10 um 时,V = 0.3125 rad/us。距离翻倍,相互作用降为原来的 1/64。这个系数为练习选取,不代表某种原子的实测校准值。

所有情况都显式使用完整状态空间。有限相互作用保留 |11⟩ 并改变它的能量,没有像硬阻塞近似那样将它排除。先预测 5 um 曲线会偏离独立原子曲线多少,再运行核对。

运行并打开报告

python3 examples/learning/projects/rabi_interactions.py

在浏览器中打开 artifacts/rabi-interactions/rabi-zh.html,查看单原子激发和双激发两张概率图。点击图例可隐藏一条曲线。连线只是帮助阅读,不表示额外计算了线上的每一个点。scan.json 保存全部 24 个结果对象,以及计数、概率、seed、单位和模拟选项。中英文报告来自同一批运行。

以下文件由本次文档构建生成:

打开实验报告

下载原始数据(JSON)

{
  "artifacts": {
    "data": "artifacts/rabi-interactions/scan.json",
    "report_en": "artifacts/rabi-interactions/rabi-en.html",
    "report_zh": "artifacts/rabi-interactions/rabi-zh.html"
  },
  "interaction_rad_per_us": {
    "far": 0.3125,
    "independent": 0.0,
    "near": 20.0
  },
  "middle_points": [
    {
      "case": "independent",
      "counts": {
        "11": 256
      },
      "duration_us": 0.5,
      "frequency_11": 1.0,
      "interaction_rad_per_us": 0.0,
      "probability_11": 1.0,
      "probability_q0": 1.0,
      "seed": 4704,
      "spacing_um": 5.0
    },
    {
      "case": "near",
      "counts": {
        "00": 90,
        "01": 81,
        "10": 73,
        "11": 12
      },
      "duration_us": 0.5,
      "frequency_11": 0.046875,
      "interaction_rad_per_us": 20.0,
      "probability_11": 0.04991117028410551,
      "probability_q0": 0.3570432870543567,
      "seed": 4712,
      "spacing_um": 5.0
    },
    {
      "case": "far",
      "counts": {
        "01": 1,
        "11": 255
      },
      "duration_us": 0.5,
      "frequency_11": 0.99609375,
      "interaction_rad_per_us": 0.3125,
      "probability_11": 0.9983828099961084,
      "probability_q0": 0.9990007685341564,
      "seed": 4720,
      "spacing_um": 10.0
    }
  ],
  "points_per_case": 8,
  "sdk_version": "1.0.8a",
  "shots_per_point": 256,
  "time_steps": 32,
  "total_shots": 6144
}

在 0.5 us 时,独立原子对的 P(11) = 1;启用有限相互作用后,5 um 约为 0.04991,10 um 约为 0.99838。这说明在这个时刻,近距离原子对的双激发受到明显抑制。不能由此推断所有可观测量都随相互作用强度或时间单调下降。

图中画的是模拟概率。frequency_11 来自 256 次采样,可能与概率不同;真实概率约为 0.05 时,二项采样的标准误差约为 sqrt(0.05 × 0.95 / 256) = 0.014。固定 seed 有助于在同一环境复现,但不能把这组采样整数当作通用参考答案。

查看实现

脚本用同一驱动构建程序,改变对照条件和时长,先保存每次结果,再绘图。独立原子的配置采用 enabled=False 关闭有限相互作用,同时保留 blockade_mode="full",避免关闭相互作用后又自动切换为硬阻塞投影。

"""Scan pulse duration for independent atoms and two finite interaction strengths."""

from __future__ import annotations

import argparse
import json
from math import pi
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,
    LocalBackend,
    SimulationOptions,
    VanDerWaalsInteraction,
    Waveform,
)


def experiment(
    output_dir: Path, *, points: int = 8, time_steps: int = 32, shots: int = 256
) -> dict[str, Any]:
    """Retain each result and draw both language reports from the same rows."""
    if not 2 <= points <= 256 or time_steps < 1 or shots < 1:
        raise ValueError("Use 2..256 points, positive time steps and positive shots.")
    output_dir.mkdir(parents=True, exist_ok=True)
    omega, c6, cutoff, seed = 2 * pi, 312_500.0, 12.0, 4701
    configurations = (
        ("independent", 5.0, False),
        ("near", 5.0, True),
        ("far", 10.0, True),
    )
    options = SimulationOptions(
        method="state_vector",
        blockade_mode="full",
        dtype="complex128",
        integrator="fixed_step_krylov",
        max_steps=time_steps,
    )
    rows: list[dict[str, Any]] = []
    raw: list[dict[str, Any]] = []
    for label, spacing, enabled in configurations:
        for index in range(points):
            duration = (index + 1) / points
            program = (
                AHSProgram(
                    AtomRegister.line(count=2, spacing=spacing),
                    interaction=VanDerWaalsInteraction(
                        c6=c6,
                        cutoff_radius=cutoff,
                        enabled=enabled,
                    ),
                )
                .drive(
                    rabi=Waveform.constant(omega, duration=duration),
                    detuning=Waveform.constant(0.0, duration=duration),
                    phase=0.0,
                )
                .measure()
            )
            result = (
                LocalBackend(analog_time_steps=time_steps)
                .run(
                    program,
                    shots=shots,
                    seed=seed + len(rows),
                    options=options,
                )
                .result()
            )
            if result.probabilities is None or sum(result.counts.values()) != shots:
                raise RuntimeError("The scan did not return a complete distribution.")
            probabilities = result.probabilities
            rows.append(
                {
                    "case": label,
                    "spacing_um": spacing,
                    "duration_us": duration,
                    "interaction_rad_per_us": c6 / spacing**6 if enabled else 0.0,
                    "probability_q0": probabilities.get("10", 0)
                    + probabilities.get("11", 0),
                    "probability_11": probabilities.get("11", 0),
                    "frequency_11": result.counts.get("11", 0) / shots,
                    "seed": seed + len(rows),
                    "counts": result.counts,
                }
            )
            raw.append(result.to_dict())
    artifacts = {"data": str(output_dir / "scan.json")}
    labels = {
        "en": (
            "Pulse duration (us)",
            "Excitation probability of atom 0",
            "Double excitation",
            ("Independent", "5 um, finite interaction", "10 um, finite interaction"),
        ),
        "zh": (
            "脉冲时长 (us)",
            "第 0 个原子的激发概率",
            "双激发概率",
            ("独立原子", "5 um,有限相互作用", "10 um,有限相互作用"),
        ),
    }
    for language, (xlabel, first_title, second_title, legends) in labels.items():
        plots = []
        for field, title in (
            ("probability_q0", first_title),
            ("probability_11", second_title),
        ):
            plot = figure(
                title=title,
                x_axis_label=xlabel,
                y_axis_label="P",
                width=880,
                height=330,
                y_range=(-0.03, 1.03),
            )
            for (label, _, _), color, legend in zip(
                configurations, ("#2563eb", "#dc2626", "#15803d"), legends
            ):
                subset = [row for row in rows if row["case"] == label]
                xs, ys = [r["duration_us"] for r in subset], [r[field] for r in subset]
                plot.line(xs, ys, color=color, legend_label=legend, line_width=2)
                plot.scatter(xs, ys, color=color, size=6)
            plot.legend.click_policy = "hide"
            plots.append(plot)
        path = output_dir / f"rabi-{language}.html"
        path.write_text(file_html(column(*plots), INLINE, "Rabi"), encoding="utf-8")
        artifacts[f"report_{language}"] = str(path)
    payload = {
        "sdk_version": cascaqit.__version__,
        "omega_rad_per_us": omega,
        "c6_rad_um6_per_us": c6,
        "cutoff_um": cutoff,
        "points_per_case": points,
        "time_steps": time_steps,
        "shots_per_point": shots,
        "total_shots": len(rows) * shots,
        "simulation_options": options.to_dict(),
        "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__,
        "points_per_case": points,
        "time_steps": time_steps,
        "shots_per_point": shots,
        "total_shots": len(rows) * shots,
        "interaction_rad_per_us": {
            label: c6 / spacing**6 if enabled else 0.0
            for label, spacing, enabled in configurations
        },
        "middle_points": [r for r in rows if r["duration_us"] == 0.5],
        "artifacts": artifacts,
    }


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--output-dir", type=Path, default=Path("artifacts/rabi-interactions")
    )
    parser.add_argument("--points", type=int, default=8)
    parser.add_argument("--time-steps", type=int, default=32)
    parser.add_argument("--shots", type=int, default=256)
    args = parser.parse_args()
    print(
        json.dumps(
            experiment(
                args.output_dir,
                points=args.points,
                time_steps=args.time_steps,
                shots=args.shots,
            ),
            sort_keys=True,
        )
    )


if __name__ == "__main__":
    main()

下载完整脚本

提交一份实验记录

把脚本版本、依赖版本、JSON 和报告放在一起。用一段话解释近距离和远距离的比较,再完成以下检查:

  1. 推导独立原子的单激发和双激发曲线,与 scan.json 中全部独立原子数据点比较,不只看最高峰。
  2. 用 --time-steps 64 --output-dir artifacts/rabi-refined 重跑,比较相同时长的概率。这个比较能说明什么数值误差问题?还有什么没有检查?
  3. 用 --shots 1024 --output-dir artifacts/rabi-more-shots 重跑。哪些图中数值应保持不变,哪些采样频率可能变化?
  4. 在本地副本中将截断半径降到 7 um。哪个原子对的有限相互作用项会被去掉?
核对思路

单原子的传播算符为 exp(−i Ωt X/2),给出 sin²(Ωt/2),独立原子的联合概率相乘。这里的 Hamiltonian 不随时间变化,因此时间步加密前后可能已经在浮点精度附近一致。这是一项一致性检查,不能证明任意时变控制用 32 步都足够。增加 shots 改变采样频率,不改变理想概率。7 um 截断会排除 10 um 原子对,保留 5 um 原子对;两者仍使用完整状态空间。

研究扩展可以从 --points 32 --time-steps 128 开始,在副本中加入失谐扫描,再用几组独立记录的 seed 重复实验。加入时变控制后重新检查收敛性;采样量应报告误差范围,未选中的运行也要保留。本项目没有加入退相干或设备误差,讨论实验室表现前需另行指定噪声模型。

同一 MIS 的三条路线研究另一个问题:怎样把同一个优化问题交给不同执行路线,再合理比较结果。

English

SDK 1.0.8a · `6eff6362`