
简介本资源是一套面向计算物理与量子算法研究者的Python实现工具包聚焦于经典与量子退火优化方法的数值模拟适用于统计物理建模、自旋玻璃系统求解及蒙特卡洛算法教学与科研实践。代码完整实现了模拟退火SA、模拟量子退火SQA及路径积分蒙特卡罗PIQMC三种核心算法支持2D Edwards-Anderson、Sherrington-Kirkpatrick和Wishart Planted Ensemble三类典型自旋模型并在原Hadayat Seddiqi Cython代码基础上修复缺陷、增强全局移动能力、精简冗余模块。资源共184个文件含9个核心Python接口脚本如run_PIQMC_EA.py、models.py、2个Cython源文件.pyx、2个C实现.c、2个Markdown文档含README说明及大量配置与数据文本文件166个.txt整体压缩包仅3.64MB轻量易部署。目前已有700人学习下载读者可直接复现实验、理解PIQMC采样机制、对比SA/SQA收敛行为并基于现有框架快速拓展新模型或优化采样策略。1. 模拟退火SA、模拟量子退火SQA与路径积分蒙特卡罗PIQMC三类采样引擎如何协同求解强关联量子系统你手头有个自旋玻璃模型哈密顿量里既有经典无序项又有横向场和多体相互作用——用传统蒙特卡罗在低温下卡死Metropolis 步长调到发抖也翻不过能垒换梯度优化初始猜错直接陷进局部极小而商用量子硬件又远未达到所需规模。这时候模拟退火SA是你最熟悉的“温度慢降”老朋友模拟量子退火SQA则把热涨落换成量子隧穿在能垒薄但高的地方悄悄钻过去而当系统真正需要刻画量子涨落的路径结构比如玻色子凝聚、拓扑序或非对易基态路径积分蒙特卡罗PIQMC就成了不可绕过的底层引擎——它不假设波函数形式而是把时间维度离散成 P 个副本“虚时间切片”把量子问题映射为经典 P 维空间上的统计采样。本项目不是教科书复现而是交付一套可插拔、可对比、可调试的 Python3 实现框架SA 提供基线收敛速度SQA 揭示量子加速潜力PIQMC 承担高保真基准验证。适合计算物理方向研究生快速搭建原型也适合算法工程师评估量子启发式方法在组合优化中的迁移边界。所有代码零依赖 C/Fortran 扩展纯 Python3 实现支持 NumPy 加速但不强制便于单步调试与参数探查。2. 从物理直觉到代码接口为什么这三类方法必须共用同一套哈密顿量抽象与采样协议2.1 哈密顿量统一建模用Hamiltonian类封装能量、梯度与量子算符三类算法表面差异巨大SA 只需计算能量差SQA 需要构造含横向场的增强哈密顿量并采样路径PIQMC 则必须显式构建 P 个副本间的耦合项。若各自写一套模型后期无法横向比对、参数无法对齐、错误难以隔离。因此核心抽象是Hamiltonian类——它不实现具体算法只定义系统本征属性import numpy as np class Hamiltonian: def __init__(self, J_matrix: np.ndarray, h_vector: np.ndarray, gamma: float 0.0, is_quantum: bool False): 初始化哈密顿量H -∑ᵢⱼ Jᵢⱼ σᵢᶻ σⱼᶻ - ∑ᵢ hᵢ σᵢᶻ - γ ∑ᵢ σᵢˣ 后两项仅当 is_quantumTrue :param J_matrix: N×N 对称耦合矩阵J[i,j] 为自旋 i,j 的 ZZ 耦合强度 :param h_vector: N 维外场向量h[i] 为自旋 i 的 Z 方向局域场 :param gamma: 横向场强度仅量子模型有效 :param is_quantum: 是否启用量子项决定是否加载 σˣ 算符 self.N len(h_vector) self.J J_matrix.copy() self.h h_vector.copy() self.gamma gamma self.is_quantum is_quantum # 预计算经典能量项避免重复计算 self._J_diag np.diag(self.J) self._J_offdiag self.J - np.diag(self._J_diag) def energy_classic(self, spin_config: np.ndarray) - float: 计算经典配置的能量E -∑ᵢⱼ Jᵢⱼ sᵢ sⱼ - ∑ᵢ hᵢ sᵢ s spin_config.astype(np.float64) return -0.5 * np.sum(s self._J_offdiag s) - np.sum(self._J_diag * s**2) - np.sum(self.h * s) def energy_quantum_path(self, path_config: np.ndarray) - float: 计算 SQA 或 PIQMC 路径配置的能量 :param path_config: shape(P, N)P 个虚时间切片每个切片是 N 维自旋配置 :return: 标量能量值 if not self.is_quantum: raise ValueError(Quantum energy requires is_quantumTrue) P, N path_config.shape # 经典项每个切片独立计算 classic_energy sum(self.energy_classic(path_config[t]) for t in range(P)) # 量子项横向场贡献σˣ→ 在路径中表现为相邻切片间自旋翻转惩罚 # 这里采用最简近似-γ ∑ₜ ∑ᵢ sₜᵢ sₜ₊₁ᵢ 周期性边界 quantum_coupling 0.0 for t in range(P): t_next (t 1) % P quantum_coupling np.sum(path_config[t] * path_config[t_next]) return classic_energy - self.gamma * quantum_coupling def get_local_field(self, spin_config: np.ndarray, i: int) - float: 计算第 i 个自旋在当前配置下的局域有效场用于 Metropolis 翻转概率 # 经典部分∑ⱼ Jᵢⱼ sⱼ hᵢ field np.sum(self.J[i] * spin_config) self.h[i] if self.is_quantum: # 量子修正横向场引入额外扰动SQA 中用于构造辅助哈密顿量 field self.gamma * (2 * spin_config[i] - 1) # 粗略等效实际需更精细处理 return field提示energy_quantum_path中的-γ ∑ₜ ∑ᵢ sₜᵢ sₜ₊₁ᵢ是路径积分中横向场的最简 Trotter 近似对应于将 e^(-βH) 分解为 e^(-βH₀) e^(-βΓσˣ) 的乘积。真实 PIQMC 应使用更精确的 Suzuki-Trotter 展开但此接口已预留扩展点如trotter_order2参数。新手可先跑通此版本熟手再替换高阶展开。2.2 采样器协议Sampler抽象基类与三类实现的职责划分算法逻辑与模型解耦的关键在于定义清晰的Sampler接口。它不关心物理模型细节只约定输入Hamiltonian,init_config,steps、输出trajectory,energies,acceptance_rate和核心钩子propose_move,accept_probfrom abc import ABC, abstractmethod from typing import Tuple, Optional class Sampler(ABC): def __init__(self, hamiltonian: Hamiltonian, seed: Optional[int] None): self.ham hamiltonian self.rng np.random.default_rng(seed) self.trajectory [] self.energies [] self.accepts 0 self.total_moves 0 abstractmethod def propose_move(self, current_config: np.ndarray) - np.ndarray: 生成候选新构型 pass abstractmethod def accept_prob(self, current_config: np.ndarray, candidate_config: np.ndarray, temp: float) - float: 计算接受概率Metropolis-Hastings ratio pass def run(self, init_config: np.ndarray, steps: int, temp_schedule: callable None) - Tuple[np.ndarray, np.ndarray]: 主运行循环 :param init_config: 初始构型shape(N,) 或 (P,N) :param steps: 总采样步数 :param temp_schedule: 温度调度函数输入 step 返回当前温度 :return: (final_config, energies_array) config init_config.copy() self.trajectory [config.copy()] self.energies [] for step in range(steps): temp temp_schedule(step) if temp_schedule else 1.0 candidate self.propose_move(config) prob self.accept_prob(config, candidate, temp) if self.rng.random() prob: config candidate self.accepts 1 self.total_moves 1 self.trajectory.append(config.copy()) # 能量计算策略SA/SQA 用经典能量PIQMC 用路径能量 if len(config.shape) 1: # 经典构型 self.energies.append(self.ham.energy_classic(config)) else: # 路径构型 self.energies.append(self.ham.energy_quantum_path(config)) return config, np.array(self.energies)这个设计让三类算法只需专注自身逻辑SimulatedAnnealingSampler实现单点翻转 温度衰减SimulatedQuantumAnnealingSampler构造路径构型 量子耦合项PathIntegralQMC实现切片间交换移动 多重更新。3. SA、SQA、PIQMC 三类采样器的 Python3 实现从单点翻转到虚时间切片交换3.1 模拟退火SA经典基线用单自旋翻转指数降温验证收敛性SA 是所有比较的锚点。其核心在于温度调度与局部更新规则。我们采用最稳健的指数降温T(t) T₀ × exp(-t / τ)其中τ控制退火速率。翻转策略为单自旋随机翻转flip_one_spin确保细致平衡。class SimulatedAnnealingSampler(Sampler): def __init__(self, hamiltonian: Hamiltonian, seed: Optional[int] None): super().__init__(hamiltonian, seed) # SA 不需要量子项强制 is_quantumFalse if hamiltonian.is_quantum: raise ValueError(SA only supports classical Hamiltonians) def propose_move(self, current_config: np.ndarray) - np.ndarray: 随机选择一个自旋并翻转 candidate current_config.copy() i self.rng.integers(0, len(candidate)) candidate[i] * -1 return candidate def accept_prob(self, current_config: np.ndarray, candidate_config: np.ndarray, temp: float) - float: Metropolis 准则min(1, exp(-(E_new - E_old)/T)) E_old self.ham.energy_classic(current_config) E_new self.ham.energy_classic(candidate_config) delta_E E_new - E_old if delta_E 0: return 1.0 return np.exp(-delta_E / temp) def run(self, init_config: np.ndarray, steps: int, T0: float 10.0, tau: float 1000.0) - Tuple[np.ndarray, np.ndarray]: SA 专用 run 方法内置指数降温 :param T0: 初始温度 :param tau: 退火时间常数越大降温越慢 def temp_schedule(step): return T0 * np.exp(-step / tau) return super().run(init_config, steps, temp_schedule)参数说明T010.0适用于中等尺寸自旋玻璃N16~32tau1000意味着约 3τ≈3000 步后温度降至初始值的 5%足够跨越中等能垒。若发现早期就卡住先增大T0若晚期仍震荡增大tau。血泪经验不要用线性降温它在低温区步长过小极易陷入亚稳态。3.2 模拟量子退火SQA用路径构型模拟量子隧穿关键在横向场与切片耦合SQA 的本质是在经典路径空间中引入量子效应。我们将时间维度离散为P个切片每个切片是一个经典自旋配置切片间通过横向场γ耦合。翻转不再局限于单点而是在某个切片上翻转一个自旋并同步更新其前后切片以维持耦合一致性即“世界线翻转”。class SimulatedQuantumAnnealingSampler(Sampler): def __init__(self, hamiltonian: Hamiltonian, P: int 8, seed: Optional[int] None): super().__init__(hamiltonian, seed) if not hamiltonian.is_quantum: raise ValueError(SQA requires quantum Hamiltonian (is_quantumTrue)) self.P P # 虚时间切片数 def _init_path_config(self, N: int) - np.ndarray: 初始化 P×N 路径构型每个切片独立随机 return self.rng.choice([-1, 1], size(self.P, N)) def propose_move(self, current_config: np.ndarray) - np.ndarray: SQA 移动随机选一个切片 t 和一个自旋 i翻转 s[t,i] 并耦合邻切片 candidate current_config.copy() t self.rng.integers(0, self.P) i self.rng.integers(0, current_config.shape[1]) # 翻转当前切片自旋 candidate[t, i] * -1 # 耦合邻切片为保持路径连续性按概率翻转 t-1 和 t1 切片的同一自旋 # 这是简化版“世界线更新”真实实现应基于转移概率 for dt in [-1, 1]: t_adj (t dt) % self.P if self.rng.random() 0.5: # 50% 概率耦合 candidate[t_adj, i] * -1 return candidate def accept_prob(self, current_config: np.ndarray, candidate_config: np.ndarray, temp: float) - float: SQA 接受概率基于路径能量差 E_old self.ham.energy_quantum_path(current_config) E_new self.ham.energy_quantum_path(candidate_config) delta_E E_new - E_old if delta_E 0: return 1.0 return np.exp(-delta_E / temp) def run(self, init_config: Optional[np.ndarray] None, steps: int 10000, T0: float 5.0, tau: float 2000.0, gamma_schedule: callable None) - Tuple[np.ndarray, np.ndarray]: SQA 运行支持横向场强度随时间变化模拟退火中的 Γ(t) :param gamma_schedule: 输入 step返回当前 gamma 值默认恒定 if init_config is None: init_config self._init_path_config(self.ham.N) # 重载 energy_quantum_path 以支持动态 gamma original_gamma self.ham.gamma if gamma_schedule: def temp_energy_func(path): self.ham.gamma gamma_schedule(0) # 占位实际需在 accept_prob 中动态获取 return self.ham.energy_quantum_path(path) # 实际工程中应重构 Ham 为支持动态 gamma此处为简化示意 else: self.ham.gamma original_gamma def temp_schedule(step): return T0 * np.exp(-step / tau) final_config, energies super().run(init_config, steps, temp_schedule) # 恢复原始 gamma self.ham.gamma original_gamma return final_config, energies关键逻辑说明propose_move中的“耦合邻切片”是 SQA 的核心——它模拟了量子涨落导致的自旋在虚时间轴上的相干演化。若只翻转单一切片系统会退化为 P 个独立 SA加入邻切片扰动才体现量子隧穿的非局域性。gamma_schedule允许实现“量子退火”初期γ大量子涨落主导后期γ→0经典极限。这是区别于 SA 的根本设计。3.3 路径积分蒙特卡罗PIQMC高保真基准用切片交换与多重更新突破冻结PIQMC 的目标是无偏采样量子基态而非模拟退火过程。因此它不降温而是在固定逆温度β下用足够多的切片P ≈ β/Δτ逼近连续虚时间。其移动规则必须满足细致平衡且高效穿越路径空间。我们实现两种移动单切片翻转Local Flip同 SQA但接受概率严格按exp(-ΔE/T)切片交换Worldline Swap随机选两个切片t1,t2交换其全部自旋配置。这对玻色子系统尤其高效。class PathIntegralQMC(Sampler): def __init__(self, hamiltonian: Hamiltonian, P: int, beta: float, seed: Optional[int] None): super().__init__(hamiltonian, seed) if not hamiltonian.is_quantum: raise ValueError(PIQMC requires quantum Hamiltonian) self.P P self.beta beta self.delta_tau beta / P # 每个切片的虚时间步长 def _init_path_config(self, N: int) - np.ndarray: 初始化所有切片相同常用基态猜测或随机 # 更优初始化用 SA 预热结果作为起点 return np.ones((self.P, N), dtypeint) # 全上自旋 def propose_move(self, current_config: np.ndarray) - np.ndarray: PIQMC 移动50% 概率单切片翻转50% 概率切片交换 candidate current_config.copy() if self.rng.random() 0.5: # Local Flip: 随机切片 随机自旋 t self.rng.integers(0, self.P) i self.rng.integers(0, current_config.shape[1]) candidate[t, i] * -1 else: # Worldline Swap: 交换两个随机切片 t1, t2 self.rng.choice(self.P, size2, replaceFalse) candidate[[t1, t2]] candidate[[t2, t1]] return candidate def accept_prob(self, current_config: np.ndarray, candidate_config: np.ndarray, temp: float) - float: PIQMC 接受概率温度 T 1/β固定不变 E_old self.ham.energy_quantum_path(current_config) E_new self.ham.energy_quantum_path(candidate_config) delta_E E_new - E_old if delta_E 0: return 1.0 return np.exp(-delta_E * self.beta / self.P) # 注意能量是 P 个切片总和故每切片贡献需除 P def run(self, init_config: Optional[np.ndarray] None, steps: int 50000, thermalize_steps: int 10000) - Tuple[np.ndarray, np.ndarray]: PIQMC 运行包含热化阶段 :param thermalize_steps: 热化步数不计入最终轨迹 if init_config is None: init_config self._init_path_config(self.ham.N) # 先热化 _, _ super().run(init_config, thermalize_steps, lambda s: 1.0 / self.beta) # 清空热化期数据 self.trajectory [] self.energies [] self.accepts 0 self.total_moves 0 # 正式采样 final_config, energies super().run( self.trajectory[-1] if self.trajectory else init_config, steps, lambda s: 1.0 / self.beta ) return final_config, energies参数说明P必须满足P ≥ β × max(|J|, |h|, γ)否则 Trotter 误差主导。例如β10,max|J|2→P≥20。thermalize_steps至少为10×P确保路径充分混合。避坑重点accept_prob中的beta/P是关键——因为energy_quantum_path返回的是 P 个切片的总能量而 Boltzmann 权重是exp(-β H)故需将总能量折算为单切片等效能量。4. 避坑指南三类算法在 Python3 实现中最常踩的 4 个硬核陷阱4.1 现象SA 在低温区接受率骤降至 0.1% 以下能量曲线平台期长达数千步原因温度衰减过快tau太小或初始温度T0不足以覆盖最大能垒高度。更隐蔽的原因是energy_classic计算存在数值溢出当J矩阵元素过大时s J s可能超int64范围。解决① 用np.float64强制转换输入② 估算能垒对随机构型抽样 1000 次取max(E) - min(E)作为T0下限③ 改用T(t) T₀ / log(1t)等更慢衰减代码中替换temp_schedule即可。4.2 现象SQA 路径能量持续上升最终发散gamma越大越严重原因energy_quantum_path中的耦合项-γ ∑ₜ ∑ᵢ sₜᵢ sₜ₊₁ᵢ符号错误。正确形式应为γ ∑ₜ ∑ᵢ sₜᵢ sₜ₊₁ᵢ因为横向场σˣ的 Trotter 展开产生正号耦合。符号反了会导致系统排斥而非吸引路径崩解。解决检查energy_quantum_path第 42 行确认是 self.gamma * quantum_coupling。可在__init__中加断言assert self.gamma 0并在文档注明耦合项物理意义。4.3 现象PIQMC 运行 10 万步后各切片自旋分布完全一致失去虚时间结构原因propose_move中切片交换Worldline Swap概率过高或单切片翻转被抑制。当所有切片趋同系统退化为经典采样丢失量子涨落信息。解决① 降低切片交换概率至 20%if rng.random() 0.2:② 增加“切片内块翻转”移动随机选连续k个切片对其同一自旋位置同时翻转增强纵向关联③ 监控切片间汉明距离若平均距离 0.1*N立即触发警告并增加P。4.4 现象三类采样器在相同J、h、γ下基态能量估计值偏差 5%且无法归因原因Hamiltonian.energy_classic与energy_quantum_path对经典项的计算不一致。前者用s J s后者用循环求和当J非对称或含对角元时结果不同。解决统一经典能量计算入口。在Hamiltonian中新增私有方法_compute_classic_energy_vectorized(s)所有能量函数调用它。并添加单元测试def test_energy_consistency(self): s np.array([1, -1, 1]) J np.array([[0, 1, 0], [1, 0, 2], [0, 2, 0]]) h np.array([0.5, 0, -0.3]) ham Hamiltonian(J, h, is_quantumFalse) E1 ham.energy_classic(s) # 构造单切片路径 path s.reshape(1, -1) E2 ham.energy_quantum_path(path) self.assertAlmostEqual(E1, E2, places10) # 必须精确一致5. 实战验证用 Edwards-Anderson 自旋玻璃模型做三法对比一招识别算法失效5.1 构建标准测试模型Edwards-Anderson 模型的 Python3 实例化我们选用最经典的无序模型N16自旋Jᵢⱼ从[-1,1]均匀采样hᵢ0γ0.5。生成可复现的实例def create_ea_model(N: int 16, seed: int 42) - Hamiltonian: 创建 Edwards-Anderson 自旋玻璃实例 rng np.random.default_rng(seed) # J 矩阵上三角随机下三角镜像对角置 0 J_upper rng.uniform(-1, 1, size(N, N)) J np.triu(J_upper, 1) np.triu(J_upper, 1).T h np.zeros(N) return Hamiltonian(J, h, gamma0.5, is_quantumTrue) # 实例化 ham_ea create_ea_model(N16, seed123) print(fEA model: N{ham_ea.N}, max|J|{np.max(np.abs(ham_ea.J)):.3f})为什么选 EA 模型它具有已知的复杂能谱大量近简并态、强阻挫Jᵢⱼ符号混杂、且无解析解是检验采样算法鲁棒性的黄金标准。N16小到可穷举验证2¹⁶65536 种构型大到足以暴露算法缺陷。5.2 三法同台竞技统一参数、分步验证、交叉诊断关键不是跑得快而是结果可信。我们设计四步验证流水线步骤SASQAPIQMC验证目标1. 热化监测记录acceptance_ratevsstep同左同左接受率 20% 为健康阈值2. 能量收敛energies[-1000:]标准差 0.01同左同左稳态波动小表明采样充分3. 构型多样性计算最后 1000 步构型的平均汉明距离对路径计算切片间平均汉明距离同 SQA距离 0.3×N 表明未坍缩4. 基态交叉验证取energies.min()作为 SA 基态能对路径取min(energies)同左三者极值应落在同一区间# 统一运行参数 steps 50000 T0 8.0 tau 2000.0 # SA sa_sampler SimulatedAnnealingSampler(ham_ea, seed1) sa_init np.random.choice([-1,1], sizeham_ea.N) _, sa_energies sa_sampler.run(sa_init, steps, T0, tau) # SQA (P16) sqa_sampler SimulatedQuantumAnnealingSampler(ham_ea, P16, seed2) sqa_init sqa_sampler._init_path_config(ham_ea.N) _, sqa_energies sqa_sampler.run(sqa_init, steps, T0, tau) # PIQMC (β10, P20) piqmc_sampler PathIntegralQMC(ham_ea, P20, beta10.0, seed3) _, piqmc_energies piqmc_sampler.run(stepssteps, thermalize_steps10000) # 验证计算极值区间 sa_ground sa_energies.min() sqa_ground sqa_energies.min() piqmc_ground piqmc_energies.min() ground_range (min(sa_ground, sqa_ground, piqmc_ground), max(sa_ground, sqa_ground, piqmc_ground)) print(fGround energy range: [{ground_range[0]:.4f}, {ground_range[1]:.4f}] f(width {ground_range[1]-ground_range[0]:.4f})) # 若宽度 0.5说明至少一个算法失效需回溯检查 if ground_range[1] - ground_range[0] 0.5: print(⚠️ 警告基态能量分散过大检查 Hamiltonian 一致性或采样步数)5.3 一招识别算法失效用“能量-接受率联合分布图”定位病灶单纯看最终能量不够。真正的诊断利器是二维直方图横轴能量纵轴该能量点的接受率。健康采样应呈现“倒 U 型”中等能量区接受率最高高低能区下降。若出现异常可精准定位SA 失效图中高能区接受率突增 → 温度没降下来仍是高温采样SQA 失效中能区接受率塌陷高能区却有尖峰 → 耦合项符号错系统在高能区“意外稳定”PIQMC 失效全图接受率 5%且能量集中在某几个值 → 切片数P不足Trotter 误差掩盖了量子涨落。import matplotlib.pyplot as plt def plot_energy_acceptance(energies: np.ndarray, accepts: np.ndarray, title: str, ax: plt.Axes): 绘制能量-接受率联合分布 # 用滑动窗口计算局部接受率 window 500 accept_rates [] energy_bins [] for i in range(0, len(energies)-window, window//2): chunk energies[i:iwindow] energy_bins.append(chunk.mean()) # 计算该 chunk 内的接受事件比例需记录每步是否接受 # 此处简化假设 accepts 是布尔数组长度同 energies accept_rates.append(accepts[i:iwindow].mean()) ax.scatter(energy_bins, accept_rates, s10, alpha0.7) ax.set_xlabel(Energy) ax.set_ylabel(Acceptance Rate) ax.set_title(title) ax.grid(True, alpha0.3) # 需在 Sampler.run 中记录 accepts 数组此处省略实现细节 # fig, axes plt.subplots(1,3, figsize(15,4)) # plot_energy_acceptance(sa_energies, sa_accepts, SA, axes[0]) # plot_energy_acceptance(sqa_energies, sqa_accepts, SQA, axes[1]) # plot_energy_acceptance(piqmc_energies, piqmc_accepts, PIQMC, axes[2]) # plt.tight_layout() # plt.show()我的习惯每次新模型上线必跑这三张图。它比任何收敛判据都诚实——接受率是算法与物理真实的直接对话骗不了人。有一次我调了三天参数图上始终是条直线接受率恒为 0.01最后发现是J矩阵没归零对角元导致energy_classic计算错误。希望帮到你。本文还有配套的精品资源点击获取