From e7e468598fa370ec7a93548668e037554f1d2044 Mon Sep 17 00:00:00 2001 From: Suxeca <127194151+Suxeca@users.noreply.github.com> Date: Thu, 7 May 2026 14:30:31 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E3=80=90=E5=88=9B=E6=96=B0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E3=80=91=E6=96=B0=E5=A2=9E=20SU(2)=20color-chain=20qu?= =?UTF-8?q?antum=20simulation=20tutorial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增自包含 SU(2) color-chain 教程,展示 sector diagnostics、局域 quench、OpenQASM 与 pyQPanda CPUQVM 示例。 Co-Authored-By: Claude Opus 4.7 --- SU2ColorChainTutorial/.gitignore | 7 + SU2ColorChainTutorial/README.md | 210 ++++++++++++++++++ SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md | 50 +++++ .../SU2ColorChainTutorial/.gitignore | 4 + .../CONTRIBUTION_NOTE.md | 82 +++++++ .../SU2ColorChainTutorial/DERIVATION.md | 157 +++++++++++++ .../SU2ColorChainTutorial/PR_DESCRIPTION.md | 62 ++++++ .../Tutorials/SU2ColorChainTutorial/README.md | 173 +++++++++++++++ .../make_tutorial_plots.py | 18 ++ .../pyqpanda_cpuqvm_example.py | 16 ++ .../SU2ColorChainTutorial/run_tutorial.py | 17 ++ SU2ColorChainTutorial/contest_pitch.txt | 24 ++ SU2ColorChainTutorial/make_plots.py | 83 +++++++ SU2ColorChainTutorial/pyqpanda_demo.py | 101 +++++++++ SU2ColorChainTutorial/requirements.txt | 3 + SU2ColorChainTutorial/run_demo.py | 61 +++++ SU2ColorChainTutorial/src/su2_toy/__init__.py | 11 + SU2ColorChainTutorial/src/su2_toy/io_utils.py | 45 ++++ SU2ColorChainTutorial/src/su2_toy/model.py | 149 +++++++++++++ .../src/su2_toy/operators.py | 46 ++++ .../src/su2_toy/qasm_export.py | 38 ++++ SU2ColorChainTutorial/src/su2_toy/scan.py | 108 +++++++++ SU2ColorChainTutorial/tests/test_su2_toy.py | 50 +++++ 23 files changed, 1515 insertions(+) create mode 100644 SU2ColorChainTutorial/.gitignore create mode 100644 SU2ColorChainTutorial/README.md create mode 100644 SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/.gitignore create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/DERIVATION.md create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/PR_DESCRIPTION.md create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/README.md create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py create mode 100644 SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py create mode 100644 SU2ColorChainTutorial/contest_pitch.txt create mode 100644 SU2ColorChainTutorial/make_plots.py create mode 100644 SU2ColorChainTutorial/pyqpanda_demo.py create mode 100644 SU2ColorChainTutorial/requirements.txt create mode 100644 SU2ColorChainTutorial/run_demo.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/__init__.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/io_utils.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/model.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/operators.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/qasm_export.py create mode 100644 SU2ColorChainTutorial/src/su2_toy/scan.py create mode 100644 SU2ColorChainTutorial/tests/test_su2_toy.py diff --git a/SU2ColorChainTutorial/.gitignore b/SU2ColorChainTutorial/.gitignore new file mode 100644 index 0000000..ece9189 --- /dev/null +++ b/SU2ColorChainTutorial/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +.pytest_cache/ +.venv/ +*.pyc +*.pyo +outputs*/ +Tutorials/SU2ColorChainTutorial/outputs*/ diff --git a/SU2ColorChainTutorial/README.md b/SU2ColorChainTutorial/README.md new file mode 100644 index 0000000..525d96b --- /dev/null +++ b/SU2ColorChainTutorial/README.md @@ -0,0 +1,210 @@ +# SU(2) Color-Chain Quantum Simulation Tutorial + +## 项目简介 + +本项目是一个面向量子算法创新应用的 SU(2) 对称量子多体模拟示例。项目使用小规模 color-chain toy Hamiltonian,展示 SU(2) singlet 态制备、singlet/triplet sector 能谱分析、局域 color-singlet quench 以及左右探测点的 connected color correlation。 + +该示例默认使用 4 到 8 个 qubit,可在本地完成精确对角化扫描、图表生成和基础线路导出;同时提供 pyQPanda CPUQVM 示例,便于学习和迁移到 pyQPanda 生态。 + +本项目定位为一个自包含教程型贡献:NumPy 精确对角化部分提供可复现实验参考量,pyQPanda CPUQVM 脚本展示对应的 singlet 态制备与局域 probe 线路构造方式。 + +## 任务类型 + +```text +【创新应用】SU(2) color-chain quantum simulation tutorial +``` + +## 主要功能 + +- 构造 SU(2)-scalar color interaction:$S_i \cdot S_j$。 +- 扫描 dimerization 参数下的低能谱结构。 +- 计算 total spin sector,用于区分 singlet 与 triplet 能级。 +- 对中心 bond 施加 color-singlet local quench。 +- 计算 quench energy injection 与 low-energy spectral weights。 +- 计算左右 probe sites 的 connected color correlation。 +- 导出 singlet-pair state preparation 与 color-quench probe 的 OpenQASM 原型。 +- 提供 pyQPanda CPUQVM 示例脚本。 + +## 目录结构 + +```text +SU2ColorChainTutorial/ +├── README.md +├── SUBMISSION_CHECKLIST.md +├── contest_pitch.txt +├── requirements.txt +├── run_demo.py +├── make_plots.py +├── pyqpanda_demo.py +├── src/su2_toy/ +├── tests/test_su2_toy.py +└── Tutorials/SU2ColorChainTutorial/ + ├── README.md + ├── DERIVATION.md + ├── CONTRIBUTION_NOTE.md + ├── PR_DESCRIPTION.md + ├── run_tutorial.py + ├── make_tutorial_plots.py + └── pyqpanda_cpuqvm_example.py +``` + +## 安装依赖 + +```bash +pip install -r requirements.txt +``` + +依赖包括: + +```text +numpy +matplotlib +pyqpanda +``` + +其中 `pyqpanda` 用于 CPUQVM 示例;如果只运行数值扫描和画图,`numpy` 与 `matplotlib` 即可。 + +## 赛事指定工具使用说明 + +本项目使用 pyQPanda CPUQVM 构造并运行 singlet-pair preparation 与 central color-quench probe 线路。NumPy 精确对角化部分用于生成参考观测量,帮助解释和验证量子线路示例的物理含义;pyQPanda CPUQVM 部分用于展示可执行的量子编程入口;OpenQASM 导出用于连接线路原型与后续模拟器或硬件迁移。 + +因此,本项目的设计关系为: + +```text +NumPy exact diagonalization -> reference observables +OpenQASM export -> portable circuit prototype +pyQPanda CPUQVM -> executable circuit realization +``` + +## 运行示例 + +### 1. 运行 SU(2) 扫描 + +```bash +python run_demo.py --num-qubits 6 --points 17 --out outputs_su2_n6 +``` + +示例终端输出如下: + +```text +SU(2) color-chain quantum simulation 完成 +输出目录:outputs_su2_n6 +singlet-triplet gap 最小窗口:delta = -0.750000 +correlation slope 诊断窗口:delta = -0.562500 +fidelity dip 诊断窗口:delta = -0.187500 +probe-site color correlation = -0.073882 +``` + +### 2. 生成图表 + +```bash +python make_plots.py --input outputs_su2_n6/summary.json --out outputs_su2_n6/figures +``` + +### 3. 运行 tutorial 入口 + +```bash +python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 +python Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py +``` + +### 4. 运行 pyQPanda CPUQVM 示例 + +```bash +python pyqpanda_demo.py --num-qubits 6 --shots 1000 +``` + +或: + +```bash +python Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py --num-qubits 6 --shots 1000 +``` + +运行后会输出 bitstring counts,并保存为 JSON 文件。示例终端输出如下: + +```text +pyQPanda CPUQVM SU(2) singlet-pair demo 完成 +输出文件:outputs_pyqpanda/counts.json +非零 bitstring 数:... +``` + +## 输出文件 + +运行扫描后会生成: + +```text +su2_spectrum_scan.csv +summary.json +singlet_triplet_gap_chart.txt +correlation_slope_chart.txt +fidelity_loss_chart.txt +state_prep_singlet_pairs.qasm +color_quench_probe.qasm +``` + +运行画图脚本后会生成: + +```text +singlet_triplet_gap.png +probe_color_correlation.png +fidelity_loss.png +quench_delta_energy.png +summary_panels.png +``` + +图表含义: + +- `singlet_triplet_gap.png`:展示最低 singlet 与 triplet sector 的能量差随参数变化的趋势。 +- `probe_color_correlation.png`:展示左右 probe sites 的 connected color correlation。 +- `fidelity_loss.png`:展示相邻参数点之间 ground-state overlap 的变化。 +- `quench_delta_energy.png`:展示 central color-singlet quench 后的能量注入。 +- `summary_panels.png`:汇总主要诊断量,便于快速检查教程输出。 + +## 数值诊断量与线路对应关系 + +数值扫描用于生成参考观测量,pyQPanda / OpenQASM 部分用于展示可迁移的线路构造入口。 + +| 数值或线路对象 | 物理含义 | 主要输出文件 | pyQPanda / OpenQASM 对应关系 | +|---|---|---|---| +| singlet-pair state preparation | SU(2) singlet 初态构造 | `state_prep_singlet_pairs.qasm` | pyQPanda 示例中的 pair preparation 线路 | +| singlet/triplet sector energies | sector-resolved spectroscopy | `su2_spectrum_scan.csv`, `singlet_triplet_gap_chart.txt` | 作为后续 VQE 或谱测量线路的 reference observable | +| central color-singlet quench energy injection | 中心 bond 上的 SU(2)-scalar 局域扰动响应 | `summary.json`, `quench_delta_energy.png` | `color_quench_probe.qasm` 与 CPUQVM local probe circuit | +| connected color correlation between two probe sites | 左右 probe sites 的 connected color correlation | `correlation_slope_chart.txt`, `probe_color_correlation.png` | 给出后续 measurement circuit 的目标观测量 | +| ground-state fidelity loss | 参数扫描中基态结构变化的参考诊断 | `fidelity_loss_chart.txt`, `fidelity_loss.png` | 可用于验证参数化线路扫描的一致性 | + +## 数学模型概述 + +每个 qubit 表示一个 fundamental SU(2) color spin。两体相互作用取 SU(2)-scalar 形式: + +$$ +S_i \cdot S_j = \frac{X_i X_j + Y_i Y_j + Z_i Z_j}{4} +$$ + +模型 Hamiltonian 为: + +$$ +\begin{aligned} +H(\delta) +&= \sum_i J\left[1 + \delta(-1)^i\right] S_i \cdot S_{i+1} \\ +&\quad + \kappa \sum_{i OpenQASM circuit prototype -> pyQPanda CPUQVM execution +``` + +## 任务类型 + +```text +【创新应用】SU(2) color-chain quantum simulation tutorial +``` + +## 流程概览 + +```text +SU(2)-symmetric color-chain Hamiltonian +-> singlet ground state +-> singlet/triplet sector spectrum +-> central color-singlet quench +-> spectral weight redistribution +-> connected color correlation between two probe sites +-> OpenQASM / pyQPanda CPUQVM prototype +``` + +## 文件说明 + +```text +Tutorials/SU2ColorChainTutorial/ +├── README.md +├── DERIVATION.md +├── CONTRIBUTION_NOTE.md +├── PR_DESCRIPTION.md +├── run_tutorial.py +├── make_tutorial_plots.py +└── pyqpanda_cpuqvm_example.py +``` + +可复用代码位于: + +```text +src/su2_toy/ +``` + +## 运行教程扫描 + +在项目根目录执行: + +```bash +python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 +``` + +默认输出到: + +```text +Tutorials/SU2ColorChainTutorial/outputs/ +``` + +示例终端输出: + +```text +SU(2) color-chain quantum simulation 完成 +输出目录:Tutorials/SU2ColorChainTutorial/outputs +singlet-triplet gap 最小窗口:delta = -0.750000 +correlation slope 诊断窗口:delta = -0.562500 +fidelity dip 诊断窗口:delta = -0.187500 +probe-site color correlation = -0.073882 +``` + +主要输出: + +```text +su2_spectrum_scan.csv +summary.json +singlet_triplet_gap_chart.txt +correlation_slope_chart.txt +fidelity_loss_chart.txt +state_prep_singlet_pairs.qasm +color_quench_probe.qasm +``` + +## 生成图表 + +```bash +python Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py +``` + +图表输出到: + +```text +Tutorials/SU2ColorChainTutorial/outputs/figures/ +``` + +包括: + +```text +singlet_triplet_gap.png +probe_color_correlation.png +fidelity_loss.png +quench_delta_energy.png +summary_panels.png +``` + +这些图分别对应 sector gap、connected probe-site correlation、fidelity loss、quench energy injection 和汇总诊断面板。 + +## 数值诊断量与线路对应关系 + +| 数值或线路对象 | 教程作用 | 主要输出文件 | pyQPanda / OpenQASM 对应关系 | +|---|---|---|---| +| singlet-pair state preparation | 构造 SU(2) singlet 初态 | `state_prep_singlet_pairs.qasm` | pyQPanda CPUQVM 示例中的 pair preparation 线路 | +| singlet/triplet sector energies | 展示 sector-resolved spectroscopy | `su2_spectrum_scan.csv`, `singlet_triplet_gap_chart.txt` | 为后续 VQE 或谱测量线路提供参考量 | +| central color-singlet quench energy injection | 展示中心 bond 局域扰动后的能量注入 | `summary.json`, `quench_delta_energy.png` | `color_quench_probe.qasm` 与 CPUQVM local probe circuit | +| connected color correlation between two probe sites | 展示左右 probe sites 的 connected response | `correlation_slope_chart.txt`, `probe_color_correlation.png` | 给出后续 measurement circuit 的目标观测量 | +| ground-state fidelity loss | 展示参数扫描下基态结构变化 | `fidelity_loss_chart.txt`, `fidelity_loss.png` | 可用于验证参数化线路扫描的一致性 | + +## pyQPanda CPUQVM 示例 + +```bash +python Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py --num-qubits 6 --shots 1000 +``` + +该脚本展示 singlet-pair preparation 与 central color-quench probe 的 pyQPanda 线路构造方式。运行后会输出 CPUQVM bitstring counts,并保存到: + +```text +Tutorials/SU2ColorChainTutorial/outputs/pyqpanda_counts.json +``` + +示例终端输出: + +```text +pyQPanda CPUQVM SU(2) singlet-pair demo 完成 +输出文件:Tutorials/SU2ColorChainTutorial/outputs/pyqpanda_counts.json +非零 bitstring 数:... +``` + +## 理论说明 + +见: + +```text +DERIVATION.md +``` + +该文档说明: + +- SU(2)-scalar interaction 的构造。 +- singlet/triplet sector 的识别方式。 +- color-singlet quench 的线路含义。 +- connected color correlation between two probe sites 的计算方式。 +- 小规模量子模拟示例中的建模与测量难点。 + +## 示例输出摘要 + +默认 6-qubit 示例会输出: + +```text +minimum singlet-triplet gap position +crossover window by probe-correlation slope +crossover window by fidelity loss +quench energy injection +connected color correlation between two probe sites +``` + +这些量用于展示有限尺寸 SU(2) color-chain 中的谱结构与关联结构变化。 diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py new file mode 100644 index 0000000..1280558 --- /dev/null +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from make_plots import main as make_plots_main + + +if __name__ == "__main__": + here = Path(__file__).resolve().parent + if "--input" not in sys.argv: + sys.argv.extend(["--input", str(here / "outputs" / "summary.json")]) + if "--out" not in sys.argv: + sys.argv.extend(["--out", str(here / "outputs" / "figures")]) + make_plots_main() diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py new file mode 100644 index 0000000..6ebc29b --- /dev/null +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from pyqpanda_demo import main as pyqpanda_demo_main + + +if __name__ == "__main__": + default_out = str(Path(__file__).resolve().parent / "outputs" / "pyqpanda_counts.json") + if "--out" not in sys.argv: + sys.argv.extend(["--out", default_out]) + pyqpanda_demo_main() diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py new file mode 100644 index 0000000..c6bed22 --- /dev/null +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from run_demo import main as run_demo_main + + +if __name__ == "__main__": + default_out = str(Path(__file__).resolve().parent / "outputs") + if "--out" not in sys.argv: + sys.argv.extend(["--out", default_out]) + run_demo_main() diff --git a/SU2ColorChainTutorial/contest_pitch.txt b/SU2ColorChainTutorial/contest_pitch.txt new file mode 100644 index 0000000..4d282fb --- /dev/null +++ b/SU2ColorChainTutorial/contest_pitch.txt @@ -0,0 +1,24 @@ +项目名:SU(2) Color-Chain Quantum Simulation Tutorial + +任务类型:创新应用 + +项目简介: +本项目实现了一个小规模 SU(2)-对称量子多体模拟示例,用于展示 singlet 态制备、singlet/triplet sector 能谱分析、局域 color-singlet quench 和 connected color correlation between two probe sites。项目默认使用 4 到 8 个 qubit,可在本地快速复现,并提供 OpenQASM 与 pyQPanda CPUQVM 示例入口。 + +核心展示: +1. 使用 SU(2)-scalar interaction $S_i \cdot S_j$ 构造 color-chain toy Hamiltonian。 +2. 扫描 dimerization 参数 $\delta$,展示低能谱结构变化。 +3. 使用 total spin $\mathbf{S}_{\mathrm{tot}}^2$ 识别 singlet / triplet sector。 +4. 在中心 bond 施加 color-singlet quench,观察能量注入与低能级权重重排。 +5. 计算左右 probe sites 的 connected color correlation。 +6. 导出 singlet-pair preparation 和 color-quench probe 的 OpenQASM 原型。 +7. 提供 pyQPanda CPUQVM 示例,便于量子线路学习与迁移。 + +适用场景: +- 量子多体模拟教学示例。 +- SU(2) 对称性与 sector spectroscopy 演示。 +- connected correlation 与 color-chain 类观测量演示。 +- pyQPanda / OpenQASM 线路构造示例。 + +推荐 PR 标题: +【创新应用】SU(2) color-chain quantum simulation tutorial diff --git a/SU2ColorChainTutorial/make_plots.py b/SU2ColorChainTutorial/make_plots.py new file mode 100644 index 0000000..aabd527 --- /dev/null +++ b/SU2ColorChainTutorial/make_plots.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + + +def load_rows(path: Path) -> list[dict[str, Any]]: + payload = json.loads(path.read_text(encoding="utf-8")) + rows = payload.get("rows", []) + if not rows: + raise SystemExit(f"没有在 {path} 中找到 rows 数据。") + return rows + + +def values(rows: list[dict[str, Any]], key: str) -> list[float]: + return [float(row[key]) for row in rows] + + +def plot_line(rows: list[dict[str, Any]], y_key: str, ylabel: str, title: str, out_path: Path) -> None: + x = values(rows, "delta") + y = values(rows, y_key) + fig, ax = plt.subplots(figsize=(7.0, 4.4), dpi=150) + ax.plot(x, y, marker="o", linewidth=1.8, markersize=4) + ax.set_xlabel("dimerization delta") + ax.set_ylabel(ylabel) + ax.set_title(title) + ax.grid(True, alpha=0.28) + fig.tight_layout() + fig.savefig(out_path) + plt.close(fig) + + +def plot_summary_panels(rows: list[dict[str, Any]], out_path: Path) -> None: + x = values(rows, "delta") + panels = [ + ("singlet_triplet_gap", "singlet-triplet gap"), + ("probe_color_conn", "probe-site color correlation"), + ("fidelity_loss_to_previous", "ground-state fidelity loss"), + ("quench_delta_e", "quench energy injection"), + ] + fig, axes = plt.subplots(2, 2, figsize=(10.0, 7.2), dpi=150, sharex=True) + for ax, (key, title) in zip(axes.flatten(), panels): + ax.plot(x, values(rows, key), marker="o", linewidth=1.6, markersize=3.5) + ax.set_title(title) + ax.set_xlabel("delta") + ax.grid(True, alpha=0.28) + fig.suptitle("SU(2) color-chain quantum diagnostics", y=0.995) + fig.tight_layout() + fig.savefig(out_path) + plt.close(fig) + + +def main() -> None: + parser = argparse.ArgumentParser() + root = Path(__file__).resolve().parent + parser.add_argument("--input", default=str(root / "outputs" / "summary.json")) + parser.add_argument("--out", default=str(root / "outputs" / "figures")) + args = parser.parse_args() + + input_path = Path(args.input) + out_dir = Path(args.out) + out_dir.mkdir(parents=True, exist_ok=True) + rows = load_rows(input_path) + + plot_line(rows, "singlet_triplet_gap", "E_triplet - E_singlet", "SU(2) singlet-triplet gap", out_dir / "singlet_triplet_gap.png") + plot_line(rows, "probe_color_conn", "connected color correlation", "left-right probe-site color correlation", out_dir / "probe_color_correlation.png") + plot_line(rows, "fidelity_loss_to_previous", "1 - fidelity", "ground-state fidelity loss", out_dir / "fidelity_loss.png") + plot_line(rows, "quench_delta_e", "Delta E after quench", "central color-singlet quench energy injection", out_dir / "quench_delta_energy.png") + plot_summary_panels(rows, out_dir / "summary_panels.png") + + print("SU(2) demo 图表已生成") + print(f"输出目录:{out_dir}") + + +if __name__ == "__main__": + main() diff --git a/SU2ColorChainTutorial/pyqpanda_demo.py b/SU2ColorChainTutorial/pyqpanda_demo.py new file mode 100644 index 0000000..eb4e8b4 --- /dev/null +++ b/SU2ColorChainTutorial/pyqpanda_demo.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +def load_pyqpanda() -> Any: + try: + import pyqpanda as pq + except Exception as exc: + raise SystemExit("未安装 pyqpanda。请先运行:py -3 -m pip install pyqpanda") from exc + return pq + + +def insert_gate(prog: Any, gate: Any) -> Any: + method = getattr(prog, "insert", None) + if callable(method): + return method(gate) + return prog << gate + + +def build_singlet_pair_program(pq: Any, qubits: list[Any], cbits: list[Any], theta: float, with_quench: bool) -> Any: + prog = pq.QProg() + for left in range(0, len(qubits), 2): + right = left + 1 + insert_gate(prog, pq.X(qubits[right])) + insert_gate(prog, pq.H(qubits[left])) + insert_gate(prog, pq.CNOT(qubits[left], qubits[right])) + insert_gate(prog, pq.Z(qubits[left])) + + if with_quench: + center_left = len(qubits) // 2 - 1 + center_right = len(qubits) // 2 + insert_gate(prog, pq.CNOT(qubits[center_left], qubits[center_right])) + insert_gate(prog, pq.RZ(qubits[center_right], theta)) + insert_gate(prog, pq.CNOT(qubits[center_left], qubits[center_right])) + + measure = getattr(pq, "Measure", None) + if callable(measure): + for qubit, cbit in zip(qubits, cbits): + insert_gate(prog, measure(qubit, cbit)) + return prog + + measure_all = getattr(pq, "measure_all", None) + if callable(measure_all): + insert_gate(prog, measure_all(qubits, cbits)) + return prog + + raise RuntimeError("pyqpanda 未找到 Measure 或 measure_all") + + +def run_cpuqvm(num_qubits: int, shots: int, theta: float, with_quench: bool) -> dict[str, Any]: + pq = load_pyqpanda() + qvm = pq.CPUQVM() + qvm.init_qvm() + try: + qubits = qvm.qAlloc_many(num_qubits) + cbits = qvm.cAlloc_many(num_qubits) + prog = build_singlet_pair_program(pq, qubits, cbits, theta, with_quench) + result = qvm.run_with_configuration(prog, cbits, shots) + return { + "num_qubits": num_qubits, + "shots": shots, + "theta": theta, + "with_quench": with_quench, + "counts": dict(result), + } + finally: + finalize = getattr(qvm, "finalize", None) + if callable(finalize): + finalize() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--num-qubits", type=int, default=6) + parser.add_argument("--shots", type=int, default=1000) + parser.add_argument("--theta", type=float, default=0.35) + parser.add_argument("--no-quench", action="store_true") + parser.add_argument("--out", default=str(Path(__file__).resolve().parent / "outputs_pyqpanda" / "counts.json")) + args = parser.parse_args() + + if args.num_qubits < 2 or args.num_qubits % 2 != 0: + raise SystemExit("num-qubits 必须是大于等于 2 的偶数。") + if args.shots <= 0: + raise SystemExit("shots 必须为正整数。") + + payload = run_cpuqvm(args.num_qubits, args.shots, args.theta, with_quench=not args.no_quench) + path = Path(args.out) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + + print("pyQPanda CPUQVM SU(2) singlet-pair demo 完成") + print(f"输出文件:{path}") + print(f"非零 bitstring 数:{len(payload['counts'])}") + + +if __name__ == "__main__": + main() diff --git a/SU2ColorChainTutorial/requirements.txt b/SU2ColorChainTutorial/requirements.txt new file mode 100644 index 0000000..36f597a --- /dev/null +++ b/SU2ColorChainTutorial/requirements.txt @@ -0,0 +1,3 @@ +numpy +matplotlib +pyqpanda diff --git a/SU2ColorChainTutorial/run_demo.py b/SU2ColorChainTutorial/run_demo.py new file mode 100644 index 0000000..3d1fb7d --- /dev/null +++ b/SU2ColorChainTutorial/run_demo.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import numpy as np + +ROOT = Path(__file__).resolve().parent +sys.path.insert(0, str(ROOT / "src")) + +from su2_toy.io_utils import ascii_gap_chart, ascii_metric_chart, write_csv, write_json, write_text +from su2_toy.model import ToyConfig +from su2_toy.qasm_export import singlet_pair_state_prep_qasm, su2_color_quench_probe_qasm +from su2_toy.scan import scan_parameter_grid, summarize_scan + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--num-qubits", type=int, default=6) + parser.add_argument("--delta-min", type=float, default=-0.75) + parser.add_argument("--delta-max", type=float, default=0.75) + parser.add_argument("--points", type=int, default=25) + parser.add_argument("--theta", type=float, default=0.35) + parser.add_argument("--electric-kappa", type=float, default=0.12) + parser.add_argument("--axial-decay", type=float, default=2.5) + parser.add_argument("--periodic", action="store_true") + parser.add_argument("--out", default=str(ROOT / "outputs")) + args = parser.parse_args() + + if args.num_qubits < 2 or args.num_qubits > 10 or args.num_qubits % 2 != 0: + raise SystemExit("建议 num-qubits 取 2、4、6、8、10;更大系统请先改用稀疏矩阵。") + if args.points < 3: + raise SystemExit("points 至少为 3。") + if abs(args.delta_min) >= 1.0 or abs(args.delta_max) >= 1.0: + raise SystemExit("为保持反铁磁 SU(2) bond,建议 delta 范围在 (-1, 1) 内。") + + out_dir = Path(args.out) + config = ToyConfig(num_qubits=args.num_qubits, electric_kappa=args.electric_kappa, axial_decay=args.axial_decay, periodic=args.periodic) + delta_values = np.linspace(args.delta_min, args.delta_max, args.points) + rows = scan_parameter_grid(config, delta_values, quench_theta=args.theta) + summary = summarize_scan(config, rows) + + write_csv(out_dir / "su2_spectrum_scan.csv", rows) + write_json(out_dir / "summary.json", {"summary": summary, "rows": rows}) + write_text(out_dir / "singlet_triplet_gap_chart.txt", ascii_gap_chart(rows)) + write_text(out_dir / "correlation_slope_chart.txt", ascii_metric_chart(rows, "probe_corr_abs_slope", "SU(2) probe-site color-correlation slope")) + write_text(out_dir / "fidelity_loss_chart.txt", ascii_metric_chart(rows, "fidelity_loss_to_previous", "SU(2) ground-state fidelity loss between adjacent delta values")) + write_text(out_dir / "state_prep_singlet_pairs.qasm", singlet_pair_state_prep_qasm(args.num_qubits)) + write_text(out_dir / "color_quench_probe.qasm", su2_color_quench_probe_qasm(args.num_qubits)) + + print("SU(2) color-chain quantum simulation 完成") + print(f"输出目录:{out_dir}") + print(f"singlet-triplet gap 最小窗口:delta = {float(summary['minimum_singlet_triplet_gap']['delta']):.6f}") + print(f"correlation slope 诊断窗口:delta = {float(summary['crossover_by_correlation_slope']['delta']):.6f}") + print(f"fidelity dip 诊断窗口:delta = {float(summary['crossover_by_fidelity_dip']['delta']):.6f}") + print(f"probe-site color correlation = {float(summary['crossover_by_correlation_slope']['probe_color_conn']):.6f}") + + +if __name__ == "__main__": + main() diff --git a/SU2ColorChainTutorial/src/su2_toy/__init__.py b/SU2ColorChainTutorial/src/su2_toy/__init__.py new file mode 100644 index 0000000..25bcf9f --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/__init__.py @@ -0,0 +1,11 @@ +from .model import ToyConfig, central_quench, connected_probe_correlation, ground_state, hamiltonian +from .scan import scan_parameter_grid + +__all__ = [ + "ToyConfig", + "central_quench", + "connected_probe_correlation", + "ground_state", + "hamiltonian", + "scan_parameter_grid", +] diff --git a/SU2ColorChainTutorial/src/su2_toy/io_utils.py b/SU2ColorChainTutorial/src/su2_toy/io_utils.py new file mode 100644 index 0000000..9e3170f --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/io_utils.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import csv +import json +from pathlib import Path +from typing import Any + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + + +def write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fieldnames = list(rows[0].keys()) + with path.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=fieldnames) + writer.writeheader() + for row in rows: + out = dict(row) + for key, value in out.items(): + if isinstance(value, (list, dict)): + out[key] = json.dumps(value) + writer.writerow(out) + + +def ascii_metric_chart(rows: list[dict[str, Any]], metric: str, title: str, x_key: str = "delta", width: int = 42) -> str: + values = [abs(float(row[metric])) for row in rows] + max_value = max(values) if values else 1.0 + lines = [title, f"{x_key:<9} {metric:<24} chart"] + for row, value in zip(rows, values): + raw = float(row[metric]) + bar_len = int(width * value / max_value) if max_value > 0 else 0 + lines.append(f"{float(row[x_key]):9.4f} {raw:24.9f} " + "█" * max(1, bar_len)) + return "\n".join(lines) + "\n" + + +def ascii_gap_chart(rows: list[dict[str, Any]], width: int = 42) -> str: + return ascii_metric_chart(rows, "singlet_triplet_gap", "SU(2) singlet-triplet spectral gap", width=width) + + +def write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") diff --git a/SU2ColorChainTutorial/src/su2_toy/model.py b/SU2ColorChainTutorial/src/su2_toy/model.py new file mode 100644 index 0000000..ffb30f5 --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/model.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from .operators import X, Y, Z, expectation, normalize, pair_operator, site_operator + + +@dataclass(frozen=True) +class ToyConfig: + num_qubits: int = 6 + exchange_j: float = 1.0 + electric_kappa: float = 0.12 + axial_decay: float = 2.5 + periodic: bool = False + + +def spin_component(num_qubits: int, site: int, pauli: np.ndarray) -> np.ndarray: + return 0.5 * site_operator(num_qubits, site, pauli) + + +def color_dot_operator(num_qubits: int, left: int, right: int) -> np.ndarray: + return 0.25 * ( + pair_operator(num_qubits, left, X, right, X) + + pair_operator(num_qubits, left, Y, right, Y) + + pair_operator(num_qubits, left, Z, right, Z) + ) + + +def total_spin_squared_operator(config: ToyConfig) -> np.ndarray: + n = config.num_qubits + s2 = 0.75 * n * np.eye(2**n, dtype=complex) + for i in range(n): + for j in range(i + 1, n): + s2 += 2.0 * color_dot_operator(n, i, j) + return s2 + + +def hamiltonian(config: ToyConfig, dimer_delta: float) -> np.ndarray: + n = config.num_qubits + dim = 2**n + h = np.zeros((dim, dim), dtype=complex) + for i in range(n - 1): + bond = config.exchange_j * (1.0 + dimer_delta * ((-1.0) ** i)) + h += bond * color_dot_operator(n, i, i + 1) + if config.periodic and n > 2: + bond = config.exchange_j * (1.0 + dimer_delta * ((-1.0) ** (n - 1))) + h += bond * color_dot_operator(n, n - 1, 0) + if config.electric_kappa != 0.0: + for i in range(n): + for j in range(i + 1, n): + distance = j - i + kernel = np.exp(-distance / max(config.axial_decay, 1e-9)) + h += config.electric_kappa * kernel * color_dot_operator(n, i, j) + return h + + +def ground_state(h: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + values, vectors = np.linalg.eigh(h) + order = np.argsort(values.real) + return values[order].real, vectors[:, order[0]] + + +def apply_singlet_quench(state: np.ndarray, config: ToyConfig, theta: float) -> np.ndarray: + n = config.num_qubits + left = max(0, n // 2 - 1) + right = min(n - 1, n // 2) + generator = color_dot_operator(n, left, right) + values, vectors = np.linalg.eigh(generator) + phases = np.exp(-1j * theta * values) + return normalize(vectors @ (phases * (vectors.conjugate().T @ state))) + + +def central_quench(state: np.ndarray, config: ToyConfig, theta: float) -> np.ndarray: + return apply_singlet_quench(state, config, theta) + + +def probe_sites(config: ToyConfig) -> tuple[int, int]: + n = config.num_qubits + if n < 4: + return 0, n - 1 + return max(0, n // 2 - 2), min(n - 1, n // 2 + 1) + + +def connected_probe_correlation(state: np.ndarray, config: ToyConfig) -> float: + n = config.num_qubits + left, right = probe_sites(config) + conn = 0.0 + for pauli in (X, Y, Z): + sl = spin_component(n, left, pauli) + sr = spin_component(n, right, pauli) + conn += (expectation(state, sl @ sr) - expectation(state, sl) * expectation(state, sr)).real + return float(conn) + + +def total_spin_expectation(state: np.ndarray, config: ToyConfig) -> float: + return float(expectation(state, total_spin_squared_operator(config)).real) + + +def spin_from_s2(s2_value: float) -> float: + return float(max(0.0, (-1.0 + np.sqrt(max(0.0, 1.0 + 4.0 * s2_value))) / 2.0)) + + +def energy(state: np.ndarray, h: np.ndarray) -> float: + return float(expectation(state, h).real) + + +def spectral_weights(state: np.ndarray, h: np.ndarray, levels: int) -> list[float]: + values, vectors = np.linalg.eigh(h) + order = np.argsort(values.real) + vectors = vectors[:, order[:levels]] + return [float(abs(np.vdot(vectors[:, i], state)) ** 2) for i in range(vectors.shape[1])] + + +def spectrum_spin_table(h: np.ndarray, config: ToyConfig, levels: int) -> list[dict[str, float]]: + values, vectors = np.linalg.eigh(h) + order = np.argsort(values.real) + values = values[order].real + vectors = vectors[:, order] + s2_op = total_spin_squared_operator(config) + table = [] + for i in range(min(levels, len(values))): + s2_value = float(expectation(vectors[:, i], s2_op).real) + table.append({"level": i, "energy": float(values[i]), "s2": s2_value, "spin": spin_from_s2(s2_value)}) + return table + + +def lowest_sector_energies(h: np.ndarray, config: ToyConfig) -> dict[str, float]: + values, vectors = np.linalg.eigh(h) + order = np.argsort(values.real) + values = values[order].real + vectors = vectors[:, order] + s2_op = total_spin_squared_operator(config) + lowest_singlet = float("nan") + lowest_triplet = float("nan") + for i, value in enumerate(values): + spin = spin_from_s2(float(expectation(vectors[:, i], s2_op).real)) + if np.isnan(lowest_singlet) and abs(spin - 0.0) < 0.35: + lowest_singlet = float(value) + if np.isnan(lowest_triplet) and abs(spin - 1.0) < 0.35: + lowest_triplet = float(value) + if not np.isnan(lowest_singlet) and not np.isnan(lowest_triplet): + break + return { + "lowest_singlet_e": lowest_singlet, + "lowest_triplet_e": lowest_triplet, + "singlet_triplet_gap": lowest_triplet - lowest_singlet, + } diff --git a/SU2ColorChainTutorial/src/su2_toy/operators.py b/SU2ColorChainTutorial/src/su2_toy/operators.py new file mode 100644 index 0000000..a2e3605 --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/operators.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from functools import reduce +from typing import Iterable + +import numpy as np + +I2 = np.eye(2, dtype=complex) +X = np.array([[0, 1], [1, 0]], dtype=complex) +Y = np.array([[0, -1j], [1j, 0]], dtype=complex) +Z = np.array([[1, 0], [0, -1]], dtype=complex) + +PAULI = {"I": I2, "X": X, "Y": Y, "Z": Z} + + +def kron_all(mats: Iterable[np.ndarray]) -> np.ndarray: + return reduce(np.kron, mats) + + +def site_operator(num_qubits: int, site: int, op: np.ndarray) -> np.ndarray: + mats = [I2] * num_qubits + mats[site] = op + return kron_all(mats) + + +def pair_operator(num_qubits: int, left: int, left_op: np.ndarray, right: int, right_op: np.ndarray) -> np.ndarray: + mats = [I2] * num_qubits + mats[left] = left_op + mats[right] = right_op + return kron_all(mats) + + +def expectation(state: np.ndarray, operator: np.ndarray) -> complex: + return np.vdot(state, operator @ state) + + +def normalize(state: np.ndarray) -> np.ndarray: + norm = np.linalg.norm(state) + if norm == 0: + raise ValueError("不能归一化零态") + return state / norm + + +def pauli_rotation_state(state: np.ndarray, pauli_matrix: np.ndarray, theta: float) -> np.ndarray: + dim = pauli_matrix.shape[0] + return np.cos(theta) * state - 1j * np.sin(theta) * (pauli_matrix @ state) diff --git a/SU2ColorChainTutorial/src/su2_toy/qasm_export.py b/SU2ColorChainTutorial/src/su2_toy/qasm_export.py new file mode 100644 index 0000000..e7b7532 --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/qasm_export.py @@ -0,0 +1,38 @@ +from __future__ import annotations + + +def singlet_pair_state_prep_qasm(num_qubits: int, measure: bool = True) -> str: + if num_qubits % 2 != 0: + raise ValueError("SU(2) singlet-pair ansatz 需要偶数 qubit") + + lines = [ + "OPENQASM 2.0;", + 'include "qelib1.inc";', + f"qreg q[{num_qubits}];", + f"creg c[{num_qubits}];", + ] + for left in range(0, num_qubits, 2): + right = left + 1 + lines.append(f"x q[{right}];") + lines.append(f"h q[{left}];") + lines.append(f"cx q[{left}],q[{right}];") + lines.append(f"z q[{left}];") + if measure: + for q in range(num_qubits): + lines.append(f"measure q[{q}] -> c[{q}];") + return "\n".join(lines) + "\n" + + +def su2_color_quench_probe_qasm(num_qubits: int) -> str: + if num_qubits % 2 != 0: + raise ValueError("SU(2) quench probe ansatz 需要偶数 qubit") + + lines = singlet_pair_state_prep_qasm(num_qubits, measure=False).splitlines() + center_left = num_qubits // 2 - 1 + center_right = num_qubits // 2 + lines.append(f"cx q[{center_left}],q[{center_right}];") + lines.append(f"rz(0.35) q[{center_right}];") + lines.append(f"cx q[{center_left}],q[{center_right}];") + for q in range(num_qubits): + lines.append(f"measure q[{q}] -> c[{q}];") + return "\n".join(lines) + "\n" diff --git a/SU2ColorChainTutorial/src/su2_toy/scan.py b/SU2ColorChainTutorial/src/su2_toy/scan.py new file mode 100644 index 0000000..73484ef --- /dev/null +++ b/SU2ColorChainTutorial/src/su2_toy/scan.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +from dataclasses import asdict +from typing import Iterable + +from .model import ( + ToyConfig, + central_quench, + connected_probe_correlation, + energy, + ground_state, + hamiltonian, + lowest_sector_energies, + spectral_weights, + spectrum_spin_table, + spin_from_s2, + total_spin_expectation, +) + + +def scan_parameter_grid(config: ToyConfig, delta_values: Iterable[float], quench_theta: float = 0.35, levels: int = 8) -> list[dict[str, float | int | list[float]]]: + rows = [] + states = [] + for delta in delta_values: + h = hamiltonian(config, float(delta)) + spectrum, psi0 = ground_state(h) + psiq = central_quench(psi0, config, quench_theta) + kept = min(levels, len(spectrum)) + spin_table = spectrum_spin_table(h, config, kept) + sector = lowest_sector_energies(h, config) + s2_ground = total_spin_expectation(psi0, config) + states.append(psi0) + rows.append( + { + "num_qubits": config.num_qubits, + "delta": float(delta), + "e0": float(spectrum[0]), + "e1": float(spectrum[1]), + "e2": float(spectrum[2]) if kept > 2 else float("nan"), + "e3": float(spectrum[3]) if kept > 3 else float("nan"), + "ground_s2": s2_ground, + "ground_spin": spin_from_s2(s2_ground), + "level_spins": [item["spin"] for item in spin_table], + "level_s2": [item["s2"] for item in spin_table], + "lowest_singlet_e": sector["lowest_singlet_e"], + "lowest_triplet_e": sector["lowest_triplet_e"], + "singlet_triplet_gap": sector["singlet_triplet_gap"], + "probe_color_conn": connected_probe_correlation(psi0, config), + "quench_delta_e": energy(psiq, h) - float(spectrum[0]), + "quench_low_level_weight": spectral_weights(psiq, h, kept), + "fidelity_to_previous": 1.0, + "fidelity_loss_to_previous": 0.0, + "probe_corr_abs_slope": 0.0, + } + ) + + for i in range(1, len(rows)): + fidelity = float(abs(states[i - 1].conjugate() @ states[i]) ** 2) + rows[i]["fidelity_to_previous"] = fidelity + rows[i]["fidelity_loss_to_previous"] = 1.0 - fidelity + _fill_correlation_slopes(rows) + return rows + + +def _fill_correlation_slopes(rows: list[dict[str, float | int | list[float]]]) -> None: + if len(rows) < 3: + return + for i in range(1, len(rows) - 1): + ddelta = float(rows[i + 1]["delta"]) - float(rows[i - 1]["delta"]) + if ddelta == 0: + continue + rows[i]["probe_corr_abs_slope"] = abs( + (float(rows[i + 1]["probe_color_conn"]) - float(rows[i - 1]["probe_color_conn"])) / ddelta + ) + + +def summarize_scan(config: ToyConfig, rows: list[dict[str, float | int | list[float]]]) -> dict[str, object]: + min_sector_gap_row = min(rows, key=lambda item: float(item["singlet_triplet_gap"])) + correlation_slope_row = max(rows, key=lambda item: float(item["probe_corr_abs_slope"])) + fidelity_dip_row = max(rows[1:] or rows, key=lambda item: float(item["fidelity_loss_to_previous"])) + max_quench_row = max(rows, key=lambda item: float(item["quench_delta_e"])) + return { + "config": asdict(config), + "minimum_singlet_triplet_gap": { + "delta": min_sector_gap_row["delta"], + "singlet_triplet_gap": min_sector_gap_row["singlet_triplet_gap"], + "lowest_singlet_e": min_sector_gap_row["lowest_singlet_e"], + "lowest_triplet_e": min_sector_gap_row["lowest_triplet_e"], + }, + "crossover_by_correlation_slope": { + "delta": correlation_slope_row["delta"], + "probe_color_conn": correlation_slope_row["probe_color_conn"], + "probe_corr_abs_slope": correlation_slope_row["probe_corr_abs_slope"], + "singlet_triplet_gap": correlation_slope_row["singlet_triplet_gap"], + }, + "crossover_by_fidelity_dip": { + "delta": fidelity_dip_row["delta"], + "fidelity_to_previous": fidelity_dip_row["fidelity_to_previous"], + "fidelity_loss_to_previous": fidelity_dip_row["fidelity_loss_to_previous"], + "singlet_triplet_gap": fidelity_dip_row["singlet_triplet_gap"], + }, + "largest_singlet_quench_response": { + "delta": max_quench_row["delta"], + "quench_delta_e": max_quench_row["quench_delta_e"], + "probe_color_conn": max_quench_row["probe_color_conn"], + }, + "interpretation": "SU(2)-symmetric finite-chain demo: singlet/triplet sector energies, probe-site color correlation, fidelity loss, and a central color-singlet quench show a small-system precursor of finite-size spectral change without claiming a thermodynamic phase transition", + } diff --git a/SU2ColorChainTutorial/tests/test_su2_toy.py b/SU2ColorChainTutorial/tests/test_su2_toy.py new file mode 100644 index 0000000..6b84795 --- /dev/null +++ b/SU2ColorChainTutorial/tests/test_su2_toy.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +import numpy as np + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from su2_toy.model import ToyConfig, central_quench, ground_state, hamiltonian, total_spin_expectation +from su2_toy.qasm_export import singlet_pair_state_prep_qasm +from su2_toy.scan import scan_parameter_grid, summarize_scan + + +def test_su2_hamiltonian_is_hermitian() -> None: + config = ToyConfig(num_qubits=6) + h = hamiltonian(config, dimer_delta=0.2) + assert np.allclose(h, h.conjugate().T) + + +def test_ground_state_is_normalized_and_near_singlet() -> None: + config = ToyConfig(num_qubits=6) + values, psi0 = ground_state(hamiltonian(config, dimer_delta=0.2)) + assert values[1] >= values[0] + assert np.isclose(np.linalg.norm(psi0), 1.0) + assert total_spin_expectation(psi0, config) < 1e-8 + + +def test_color_singlet_quench_preserves_norm() -> None: + config = ToyConfig(num_qubits=6) + _, psi0 = ground_state(hamiltonian(config, dimer_delta=0.2)) + psiq = central_quench(psi0, config, theta=0.35) + assert np.isclose(np.linalg.norm(psiq), 1.0) + + +def test_scan_summary_has_su2_sector_diagnostics() -> None: + config = ToyConfig(num_qubits=6) + rows = scan_parameter_grid(config, [-0.4, 0.0, 0.4], quench_theta=0.2) + summary = summarize_scan(config, rows) + assert "minimum_singlet_triplet_gap" in summary + assert "crossover_by_correlation_slope" in summary + assert summary["minimum_singlet_triplet_gap"]["singlet_triplet_gap"] >= 0.0 + + +def test_singlet_pair_qasm_shape() -> None: + qasm = singlet_pair_state_prep_qasm(4) + assert "OPENQASM 2.0" in qasm + assert "cx q[0],q[1];" in qasm + assert "cx q[2],q[3];" in qasm From 6eca98fae531b14e4cc41aba0b8fa582c8cb234d Mon Sep 17 00:00:00 2001 From: Suxeca <127194151+Suxeca@users.noreply.github.com> Date: Thu, 7 May 2026 14:33:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E3=80=90=E5=86=85=E5=AE=B9=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91=E4=BC=98=E5=8C=96=20SU(2)=20color-chain=20?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一 quench theta 在数值扫描、OpenQASM 与 CPUQVM 示例中的使用,并拆分 pyQPanda 可选依赖。 Co-Authored-By: Claude Opus 4.7 --- SU2ColorChainTutorial/README.md | 14 +++-- SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md | 3 +- .../make_tutorial_plots.py | 8 ++- .../pyqpanda_cpuqvm_example.py | 6 +- .../SU2ColorChainTutorial/run_tutorial.py | 7 ++- SU2ColorChainTutorial/pyqpanda_demo.py | 34 +++++++++-- .../requirements-pyqpanda.txt | 2 + SU2ColorChainTutorial/requirements.txt | 1 - SU2ColorChainTutorial/run_demo.py | 2 +- SU2ColorChainTutorial/src/su2_toy/io_utils.py | 4 +- .../src/su2_toy/qasm_export.py | 37 ++++++++++-- SU2ColorChainTutorial/src/su2_toy/scan.py | 58 +++++++++++++++---- SU2ColorChainTutorial/tests/test_su2_toy.py | 9 ++- 13 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 SU2ColorChainTutorial/requirements-pyqpanda.txt diff --git a/SU2ColorChainTutorial/README.md b/SU2ColorChainTutorial/README.md index 525d96b..baf0a26 100644 --- a/SU2ColorChainTutorial/README.md +++ b/SU2ColorChainTutorial/README.md @@ -2,7 +2,7 @@ ## 项目简介 -本项目是一个面向量子算法创新应用的 SU(2) 对称量子多体模拟示例。项目使用小规模 color-chain toy Hamiltonian,展示 SU(2) singlet 态制备、singlet/triplet sector 能谱分析、局域 color-singlet quench 以及左右探测点的 connected color correlation。 +本项目是一个面向量子算法创新应用的 SU(2) 对称量子多体模拟示例。项目使用小规模 color-chain toy Hamiltonian,展示 SU(2) singlet 态制备、singlet/triplet sector 能谱分析、局域 color-singlet quench 以及左右 probe sites 的 connected color correlation。 该示例默认使用 4 到 8 个 qubit,可在本地完成精确对角化扫描、图表生成和基础线路导出;同时提供 pyQPanda CPUQVM 示例,便于学习和迁移到 pyQPanda 生态。 @@ -33,6 +33,7 @@ SU2ColorChainTutorial/ ├── SUBMISSION_CHECKLIST.md ├── contest_pitch.txt ├── requirements.txt +├── requirements-pyqpanda.txt ├── run_demo.py ├── make_plots.py ├── pyqpanda_demo.py @@ -50,19 +51,22 @@ SU2ColorChainTutorial/ ## 安装依赖 +基础数值教程依赖: + ```bash pip install -r requirements.txt ``` -依赖包括: - ```text numpy matplotlib -pyqpanda ``` -其中 `pyqpanda` 用于 CPUQVM 示例;如果只运行数值扫描和画图,`numpy` 与 `matplotlib` 即可。 +如需运行 pyQPanda CPUQVM 示例,再安装可选依赖: + +```bash +pip install -r requirements-pyqpanda.txt +``` ## 赛事指定工具使用说明 diff --git a/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md b/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md index ca3f46b..73ff61a 100644 --- a/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md +++ b/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md @@ -10,7 +10,8 @@ - [x] `README.md`:项目总说明 - [x] `contest_pitch.txt`:项目简介与应用场景 -- [x] `requirements.txt`:依赖列表 +- [x] `requirements.txt`:基础依赖列表 +- [x] `requirements-pyqpanda.txt`:pyQPanda 可选依赖列表 - [x] `run_demo.py`:主扫描入口 - [x] `make_plots.py`:图表生成入口 - [x] `pyqpanda_demo.py`:pyQPanda CPUQVM 示例入口 diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py index 1280558..4d231a3 100644 --- a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py @@ -9,10 +9,14 @@ from make_plots import main as make_plots_main +def has_option(name: str) -> bool: + return any(arg == name or arg.startswith(f"{name}=") for arg in sys.argv[1:]) + + if __name__ == "__main__": here = Path(__file__).resolve().parent - if "--input" not in sys.argv: + if not has_option("--input"): sys.argv.extend(["--input", str(here / "outputs" / "summary.json")]) - if "--out" not in sys.argv: + if not has_option("--out"): sys.argv.extend(["--out", str(here / "outputs" / "figures")]) make_plots_main() diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py index 6ebc29b..61f3ba6 100644 --- a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py @@ -9,8 +9,12 @@ from pyqpanda_demo import main as pyqpanda_demo_main +def has_option(name: str) -> bool: + return any(arg == name or arg.startswith(f"{name}=") for arg in sys.argv[1:]) + + if __name__ == "__main__": default_out = str(Path(__file__).resolve().parent / "outputs" / "pyqpanda_counts.json") - if "--out" not in sys.argv: + if not has_option("--out"): sys.argv.extend(["--out", default_out]) pyqpanda_demo_main() diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py index c6bed22..1712791 100644 --- a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/run_tutorial.py @@ -1,6 +1,5 @@ from __future__ import annotations -import argparse import sys from pathlib import Path @@ -10,8 +9,12 @@ from run_demo import main as run_demo_main +def has_option(name: str) -> bool: + return any(arg == name or arg.startswith(f"{name}=") for arg in sys.argv[1:]) + + if __name__ == "__main__": default_out = str(Path(__file__).resolve().parent / "outputs") - if "--out" not in sys.argv: + if not has_option("--out"): sys.argv.extend(["--out", default_out]) run_demo_main() diff --git a/SU2ColorChainTutorial/pyqpanda_demo.py b/SU2ColorChainTutorial/pyqpanda_demo.py index eb4e8b4..6292eea 100644 --- a/SU2ColorChainTutorial/pyqpanda_demo.py +++ b/SU2ColorChainTutorial/pyqpanda_demo.py @@ -2,6 +2,7 @@ import argparse import json +from math import pi from pathlib import Path from typing import Any @@ -10,7 +11,7 @@ def load_pyqpanda() -> Any: try: import pyqpanda as pq except Exception as exc: - raise SystemExit("未安装 pyqpanda。请先运行:py -3 -m pip install pyqpanda") from exc + raise SystemExit("未安装 pyqpanda。请先运行:python -m pip install pyqpanda") from exc return pq @@ -21,6 +22,33 @@ def insert_gate(prog: Any, gate: Any) -> Any: return prog << gate +def append_rzz(pq: Any, prog: Any, qubits: list[Any], left: int, right: int, angle: float) -> None: + insert_gate(prog, pq.CNOT(qubits[left], qubits[right])) + insert_gate(prog, pq.RZ(qubits[right], angle)) + insert_gate(prog, pq.CNOT(qubits[left], qubits[right])) + + +def append_su2_color_probe(pq: Any, prog: Any, qubits: list[Any], left: int, right: int, theta: float) -> None: + rx = getattr(pq, "RX", None) + if not callable(rx): + raise RuntimeError("pyqpanda 未找到 RX gate") + + pair_angle = theta / 2.0 + insert_gate(prog, pq.H(qubits[left])) + insert_gate(prog, pq.H(qubits[right])) + append_rzz(pq, prog, qubits, left, right, pair_angle) + insert_gate(prog, pq.H(qubits[left])) + insert_gate(prog, pq.H(qubits[right])) + + insert_gate(prog, rx(qubits[left], pi / 2.0)) + insert_gate(prog, rx(qubits[right], pi / 2.0)) + append_rzz(pq, prog, qubits, left, right, pair_angle) + insert_gate(prog, rx(qubits[left], -pi / 2.0)) + insert_gate(prog, rx(qubits[right], -pi / 2.0)) + + append_rzz(pq, prog, qubits, left, right, pair_angle) + + def build_singlet_pair_program(pq: Any, qubits: list[Any], cbits: list[Any], theta: float, with_quench: bool) -> Any: prog = pq.QProg() for left in range(0, len(qubits), 2): @@ -33,9 +61,7 @@ def build_singlet_pair_program(pq: Any, qubits: list[Any], cbits: list[Any], the if with_quench: center_left = len(qubits) // 2 - 1 center_right = len(qubits) // 2 - insert_gate(prog, pq.CNOT(qubits[center_left], qubits[center_right])) - insert_gate(prog, pq.RZ(qubits[center_right], theta)) - insert_gate(prog, pq.CNOT(qubits[center_left], qubits[center_right])) + append_su2_color_probe(pq, prog, qubits, center_left, center_right, theta) measure = getattr(pq, "Measure", None) if callable(measure): diff --git a/SU2ColorChainTutorial/requirements-pyqpanda.txt b/SU2ColorChainTutorial/requirements-pyqpanda.txt new file mode 100644 index 0000000..8a7c98b --- /dev/null +++ b/SU2ColorChainTutorial/requirements-pyqpanda.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pyqpanda diff --git a/SU2ColorChainTutorial/requirements.txt b/SU2ColorChainTutorial/requirements.txt index 36f597a..aa094d9 100644 --- a/SU2ColorChainTutorial/requirements.txt +++ b/SU2ColorChainTutorial/requirements.txt @@ -1,3 +1,2 @@ numpy matplotlib -pyqpanda diff --git a/SU2ColorChainTutorial/run_demo.py b/SU2ColorChainTutorial/run_demo.py index 3d1fb7d..2d979b5 100644 --- a/SU2ColorChainTutorial/run_demo.py +++ b/SU2ColorChainTutorial/run_demo.py @@ -47,7 +47,7 @@ def main() -> None: write_text(out_dir / "correlation_slope_chart.txt", ascii_metric_chart(rows, "probe_corr_abs_slope", "SU(2) probe-site color-correlation slope")) write_text(out_dir / "fidelity_loss_chart.txt", ascii_metric_chart(rows, "fidelity_loss_to_previous", "SU(2) ground-state fidelity loss between adjacent delta values")) write_text(out_dir / "state_prep_singlet_pairs.qasm", singlet_pair_state_prep_qasm(args.num_qubits)) - write_text(out_dir / "color_quench_probe.qasm", su2_color_quench_probe_qasm(args.num_qubits)) + write_text(out_dir / "color_quench_probe.qasm", su2_color_quench_probe_qasm(args.num_qubits, theta=args.theta)) print("SU(2) color-chain quantum simulation 完成") print(f"输出目录:{out_dir}") diff --git a/SU2ColorChainTutorial/src/su2_toy/io_utils.py b/SU2ColorChainTutorial/src/su2_toy/io_utils.py index 9e3170f..20d8f8f 100644 --- a/SU2ColorChainTutorial/src/su2_toy/io_utils.py +++ b/SU2ColorChainTutorial/src/su2_toy/io_utils.py @@ -12,6 +12,8 @@ def write_json(path: Path, payload: dict[str, Any]) -> None: def write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + raise ValueError("write_csv 需要至少一行数据") path.parent.mkdir(parents=True, exist_ok=True) fieldnames = list(rows[0].keys()) with path.open("w", newline="", encoding="utf-8") as handle: @@ -32,7 +34,7 @@ def ascii_metric_chart(rows: list[dict[str, Any]], metric: str, title: str, x_ke for row, value in zip(rows, values): raw = float(row[metric]) bar_len = int(width * value / max_value) if max_value > 0 else 0 - lines.append(f"{float(row[x_key]):9.4f} {raw:24.9f} " + "█" * max(1, bar_len)) + lines.append(f"{float(row[x_key]):9.4f} {raw:24.9f} " + "█" * bar_len) return "\n".join(lines) + "\n" diff --git a/SU2ColorChainTutorial/src/su2_toy/qasm_export.py b/SU2ColorChainTutorial/src/su2_toy/qasm_export.py index e7b7532..3a87917 100644 --- a/SU2ColorChainTutorial/src/su2_toy/qasm_export.py +++ b/SU2ColorChainTutorial/src/su2_toy/qasm_export.py @@ -1,5 +1,33 @@ from __future__ import annotations +from math import pi + + +def _angle(value: float) -> str: + return f"{value:.12g}" + + +def _append_rzz(lines: list[str], left: int, right: int, angle: float) -> None: + lines.append(f"cx q[{left}],q[{right}];") + lines.append(f"rz({_angle(angle)}) q[{right}];") + lines.append(f"cx q[{left}],q[{right}];") + + +def _append_xx(lines: list[str], left: int, right: int, angle: float) -> None: + lines.append(f"h q[{left}];") + lines.append(f"h q[{right}];") + _append_rzz(lines, left, right, angle) + lines.append(f"h q[{left}];") + lines.append(f"h q[{right}];") + + +def _append_yy(lines: list[str], left: int, right: int, angle: float) -> None: + lines.append(f"rx({_angle(pi / 2.0)}) q[{left}];") + lines.append(f"rx({_angle(pi / 2.0)}) q[{right}];") + _append_rzz(lines, left, right, angle) + lines.append(f"rx({_angle(-pi / 2.0)}) q[{left}];") + lines.append(f"rx({_angle(-pi / 2.0)}) q[{right}];") + def singlet_pair_state_prep_qasm(num_qubits: int, measure: bool = True) -> str: if num_qubits % 2 != 0: @@ -23,16 +51,17 @@ def singlet_pair_state_prep_qasm(num_qubits: int, measure: bool = True) -> str: return "\n".join(lines) + "\n" -def su2_color_quench_probe_qasm(num_qubits: int) -> str: +def su2_color_quench_probe_qasm(num_qubits: int, theta: float = 0.35) -> str: if num_qubits % 2 != 0: raise ValueError("SU(2) quench probe ansatz 需要偶数 qubit") lines = singlet_pair_state_prep_qasm(num_qubits, measure=False).splitlines() center_left = num_qubits // 2 - 1 center_right = num_qubits // 2 - lines.append(f"cx q[{center_left}],q[{center_right}];") - lines.append(f"rz(0.35) q[{center_right}];") - lines.append(f"cx q[{center_left}],q[{center_right}];") + pair_angle = theta / 2.0 + _append_xx(lines, center_left, center_right, pair_angle) + _append_yy(lines, center_left, center_right, pair_angle) + _append_rzz(lines, center_left, center_right, pair_angle) for q in range(num_qubits): lines.append(f"measure q[{q}] -> c[{q}];") return "\n".join(lines) + "\n" diff --git a/SU2ColorChainTutorial/src/su2_toy/scan.py b/SU2ColorChainTutorial/src/su2_toy/scan.py index 73484ef..8e171b6 100644 --- a/SU2ColorChainTutorial/src/su2_toy/scan.py +++ b/SU2ColorChainTutorial/src/su2_toy/scan.py @@ -3,32 +3,70 @@ from dataclasses import asdict from typing import Iterable +import numpy as np + from .model import ( ToyConfig, central_quench, connected_probe_correlation, energy, - ground_state, hamiltonian, - lowest_sector_energies, - spectral_weights, - spectrum_spin_table, spin_from_s2, - total_spin_expectation, + total_spin_squared_operator, ) +from .operators import expectation + + +def _eigensystem(h: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + values, vectors = np.linalg.eigh(h) + order = np.argsort(values.real) + return values[order].real, vectors[:, order] + + +def _spin_table(vectors: np.ndarray, values: np.ndarray, s2_op: np.ndarray, levels: int) -> list[dict[str, float]]: + table = [] + for level in range(min(levels, len(values))): + s2_value = float(expectation(vectors[:, level], s2_op).real) + table.append({"level": level, "energy": float(values[level]), "s2": s2_value, "spin": spin_from_s2(s2_value)}) + return table + + +def _lowest_sector_energies(vectors: np.ndarray, values: np.ndarray, s2_op: np.ndarray) -> dict[str, float]: + lowest_singlet = float("nan") + lowest_triplet = float("nan") + for level, value in enumerate(values): + spin = spin_from_s2(float(expectation(vectors[:, level], s2_op).real)) + if np.isnan(lowest_singlet) and abs(spin - 0.0) < 0.35: + lowest_singlet = float(value) + if np.isnan(lowest_triplet) and abs(spin - 1.0) < 0.35: + lowest_triplet = float(value) + if not np.isnan(lowest_singlet) and not np.isnan(lowest_triplet): + break + return { + "lowest_singlet_e": lowest_singlet, + "lowest_triplet_e": lowest_triplet, + "singlet_triplet_gap": lowest_triplet - lowest_singlet, + } + + +def _spectral_weights(state: np.ndarray, vectors: np.ndarray, levels: int) -> list[float]: + kept = min(levels, vectors.shape[1]) + return [float(abs(np.vdot(vectors[:, level], state)) ** 2) for level in range(kept)] def scan_parameter_grid(config: ToyConfig, delta_values: Iterable[float], quench_theta: float = 0.35, levels: int = 8) -> list[dict[str, float | int | list[float]]]: rows = [] states = [] + s2_op = total_spin_squared_operator(config) for delta in delta_values: h = hamiltonian(config, float(delta)) - spectrum, psi0 = ground_state(h) + spectrum, vectors = _eigensystem(h) + psi0 = vectors[:, 0] psiq = central_quench(psi0, config, quench_theta) kept = min(levels, len(spectrum)) - spin_table = spectrum_spin_table(h, config, kept) - sector = lowest_sector_energies(h, config) - s2_ground = total_spin_expectation(psi0, config) + spin_table = _spin_table(vectors, spectrum, s2_op, kept) + sector = _lowest_sector_energies(vectors, spectrum, s2_op) + s2_ground = float(expectation(psi0, s2_op).real) states.append(psi0) rows.append( { @@ -47,7 +85,7 @@ def scan_parameter_grid(config: ToyConfig, delta_values: Iterable[float], quench "singlet_triplet_gap": sector["singlet_triplet_gap"], "probe_color_conn": connected_probe_correlation(psi0, config), "quench_delta_e": energy(psiq, h) - float(spectrum[0]), - "quench_low_level_weight": spectral_weights(psiq, h, kept), + "quench_low_energy_weight": _spectral_weights(psiq, vectors, kept), "fidelity_to_previous": 1.0, "fidelity_loss_to_previous": 0.0, "probe_corr_abs_slope": 0.0, diff --git a/SU2ColorChainTutorial/tests/test_su2_toy.py b/SU2ColorChainTutorial/tests/test_su2_toy.py index 6b84795..e4aa225 100644 --- a/SU2ColorChainTutorial/tests/test_su2_toy.py +++ b/SU2ColorChainTutorial/tests/test_su2_toy.py @@ -9,7 +9,7 @@ sys.path.insert(0, str(ROOT / "src")) from su2_toy.model import ToyConfig, central_quench, ground_state, hamiltonian, total_spin_expectation -from su2_toy.qasm_export import singlet_pair_state_prep_qasm +from su2_toy.qasm_export import singlet_pair_state_prep_qasm, su2_color_quench_probe_qasm from su2_toy.scan import scan_parameter_grid, summarize_scan @@ -48,3 +48,10 @@ def test_singlet_pair_qasm_shape() -> None: assert "OPENQASM 2.0" in qasm assert "cx q[0],q[1];" in qasm assert "cx q[2],q[3];" in qasm + + +def test_color_quench_qasm_uses_theta_and_center_pair() -> None: + qasm = su2_color_quench_probe_qasm(6, theta=0.4) + assert "rz(0.2) q[3];" in qasm + assert "rx(1.57079632679) q[2];" in qasm + assert "measure q[5] -> c[5];" in qasm From 8bc844a8ac73764649c0854ae4f42262f89ccae4 Mon Sep 17 00:00:00 2001 From: Suxeca <127194151+Suxeca@users.noreply.github.com> Date: Thu, 7 May 2026 14:49:20 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E3=80=90=E5=86=85=E5=AE=B9=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91=E5=A4=84=E7=90=86=20SU(2)=20tutorial=20revie?= =?UTF-8?q?w=20=E5=BB=BA=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正 S² expectation 命名、类型标注和教程运行目录说明,提升示例代码与文档清晰度。 Co-Authored-By: Claude Opus 4.7 --- SU2ColorChainTutorial/README.md | 4 ++++ SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md | 3 +++ .../Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md | 3 +++ .../Tutorials/SU2ColorChainTutorial/README.md | 5 ++++- SU2ColorChainTutorial/src/su2_toy/model.py | 4 ++-- SU2ColorChainTutorial/src/su2_toy/operators.py | 3 ++- SU2ColorChainTutorial/src/su2_toy/scan.py | 2 +- SU2ColorChainTutorial/tests/test_su2_toy.py | 4 ++-- 8 files changed, 21 insertions(+), 7 deletions(-) diff --git a/SU2ColorChainTutorial/README.md b/SU2ColorChainTutorial/README.md index baf0a26..470bded 100644 --- a/SU2ColorChainTutorial/README.md +++ b/SU2ColorChainTutorial/README.md @@ -82,6 +82,8 @@ pyQPanda CPUQVM -> executable circuit realization ## 运行示例 +以下命令均在 `SU2ColorChainTutorial/` 目录下执行。 + ### 1. 运行 SU(2) 扫描 ```bash @@ -206,6 +208,8 @@ $$ ## 验证方式 +在 `SU2ColorChainTutorial/` 目录下执行: + ```bash python -m py_compile run_demo.py make_plots.py pyqpanda_demo.py python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 diff --git a/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md b/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md index 73ff61a..a5f7e3b 100644 --- a/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md +++ b/SU2ColorChainTutorial/SUBMISSION_CHECKLIST.md @@ -27,7 +27,10 @@ ## 本地验证命令 +从仓库根目录进入 `SU2ColorChainTutorial/` 后执行: + ```bash +cd SU2ColorChainTutorial python -m py_compile run_demo.py make_plots.py pyqpanda_demo.py python -m py_compile Tutorials/SU2ColorChainTutorial/run_tutorial.py Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py Tutorials/SU2ColorChainTutorial/pyqpanda_cpuqvm_example.py python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md index 2d97f32..e51a398 100644 --- a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/CONTRIBUTION_NOTE.md @@ -75,7 +75,10 @@ $$ ## 本地验证 +从仓库根目录进入 `SU2ColorChainTutorial/` 后执行: + ```bash +cd SU2ColorChainTutorial python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 python Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py python -c "import sys; sys.path.insert(0, 'tests'); import test_su2_toy as t; [getattr(t, n)() for n in dir(t) if n.startswith('test_')]; print('tests ok')" diff --git a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/README.md b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/README.md index fd4f90d..5782fed 100644 --- a/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/README.md +++ b/SU2ColorChainTutorial/Tutorials/SU2ColorChainTutorial/README.md @@ -53,9 +53,10 @@ src/su2_toy/ ## 运行教程扫描 -在项目根目录执行: +从仓库根目录进入 `SU2ColorChainTutorial/` 后执行: ```bash +cd SU2ColorChainTutorial python Tutorials/SU2ColorChainTutorial/run_tutorial.py --num-qubits 6 --points 17 ``` @@ -90,6 +91,8 @@ color_quench_probe.qasm ## 生成图表 +继续在 `SU2ColorChainTutorial/` 目录下执行: + ```bash python Tutorials/SU2ColorChainTutorial/make_tutorial_plots.py ``` diff --git a/SU2ColorChainTutorial/src/su2_toy/model.py b/SU2ColorChainTutorial/src/su2_toy/model.py index ffb30f5..3bfce3f 100644 --- a/SU2ColorChainTutorial/src/su2_toy/model.py +++ b/SU2ColorChainTutorial/src/su2_toy/model.py @@ -94,7 +94,7 @@ def connected_probe_correlation(state: np.ndarray, config: ToyConfig) -> float: return float(conn) -def total_spin_expectation(state: np.ndarray, config: ToyConfig) -> float: +def total_spin_squared_expectation(state: np.ndarray, config: ToyConfig) -> float: return float(expectation(state, total_spin_squared_operator(config)).real) @@ -113,7 +113,7 @@ def spectral_weights(state: np.ndarray, h: np.ndarray, levels: int) -> list[floa return [float(abs(np.vdot(vectors[:, i], state)) ** 2) for i in range(vectors.shape[1])] -def spectrum_spin_table(h: np.ndarray, config: ToyConfig, levels: int) -> list[dict[str, float]]: +def spectrum_spin_table(h: np.ndarray, config: ToyConfig, levels: int) -> list[dict[str, float | int]]: values, vectors = np.linalg.eigh(h) order = np.argsort(values.real) values = values[order].real diff --git a/SU2ColorChainTutorial/src/su2_toy/operators.py b/SU2ColorChainTutorial/src/su2_toy/operators.py index a2e3605..13470cc 100644 --- a/SU2ColorChainTutorial/src/su2_toy/operators.py +++ b/SU2ColorChainTutorial/src/su2_toy/operators.py @@ -42,5 +42,6 @@ def normalize(state: np.ndarray) -> np.ndarray: def pauli_rotation_state(state: np.ndarray, pauli_matrix: np.ndarray, theta: float) -> np.ndarray: - dim = pauli_matrix.shape[0] + if state.shape[0] != pauli_matrix.shape[0]: + raise ValueError("state 与 pauli_matrix 维度不匹配") return np.cos(theta) * state - 1j * np.sin(theta) * (pauli_matrix @ state) diff --git a/SU2ColorChainTutorial/src/su2_toy/scan.py b/SU2ColorChainTutorial/src/su2_toy/scan.py index 8e171b6..c49a1ad 100644 --- a/SU2ColorChainTutorial/src/su2_toy/scan.py +++ b/SU2ColorChainTutorial/src/su2_toy/scan.py @@ -23,7 +23,7 @@ def _eigensystem(h: np.ndarray) -> tuple[np.ndarray, np.ndarray]: return values[order].real, vectors[:, order] -def _spin_table(vectors: np.ndarray, values: np.ndarray, s2_op: np.ndarray, levels: int) -> list[dict[str, float]]: +def _spin_table(vectors: np.ndarray, values: np.ndarray, s2_op: np.ndarray, levels: int) -> list[dict[str, float | int]]: table = [] for level in range(min(levels, len(values))): s2_value = float(expectation(vectors[:, level], s2_op).real) diff --git a/SU2ColorChainTutorial/tests/test_su2_toy.py b/SU2ColorChainTutorial/tests/test_su2_toy.py index e4aa225..9be1fd3 100644 --- a/SU2ColorChainTutorial/tests/test_su2_toy.py +++ b/SU2ColorChainTutorial/tests/test_su2_toy.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) -from su2_toy.model import ToyConfig, central_quench, ground_state, hamiltonian, total_spin_expectation +from su2_toy.model import ToyConfig, central_quench, ground_state, hamiltonian, total_spin_squared_expectation from su2_toy.qasm_export import singlet_pair_state_prep_qasm, su2_color_quench_probe_qasm from su2_toy.scan import scan_parameter_grid, summarize_scan @@ -24,7 +24,7 @@ def test_ground_state_is_normalized_and_near_singlet() -> None: values, psi0 = ground_state(hamiltonian(config, dimer_delta=0.2)) assert values[1] >= values[0] assert np.isclose(np.linalg.norm(psi0), 1.0) - assert total_spin_expectation(psi0, config) < 1e-8 + assert total_spin_squared_expectation(psi0, config) < 1e-8 def test_color_singlet_quench_preserves_norm() -> None: