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、单位和模拟选项。中英文报告来自同一批运行。
以下文件由本次文档构建生成:
{
"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 和报告放在一起。用一段话解释近距离和远距离的比较,再完成以下检查:
- 推导独立原子的单激发和双激发曲线,与
scan.json中全部独立原子数据点比较,不只看最高峰。 - 用
--time-steps 64 --output-dir artifacts/rabi-refined重跑,比较相同时长的概率。这个比较能说明什么数值误差问题?还有什么没有检查? - 用
--shots 1024 --output-dir artifacts/rabi-more-shots重跑。哪些图中数值应保持不变,哪些采样频率可能变化? - 在本地副本中将截断半径降到
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 的三条路线研究另一个问题:怎样把同一个优化问题交给不同执行路线,再合理比较结果。