"""积分方案实现
本模块实现了各种MD积分方案,通过Trotter分解组合基础传播子。
每个方案对应一个特定的统计力学系综。
已实现的方案:
- NVEScheme: 微正则系综的Velocity-Verlet算法
设计要点:
1. 基于对称Trotter分解保证时间可逆性
2. 复用基础传播子,避免代码重复
3. 每个方案负责特定系综的物理正确性
4. 支持延迟初始化以处理循环依赖
Created: 2025-08-18
"""
import numpy as np
from ..core.structure import Cell
from .interfaces import IntegrationScheme
from .propagators import (
AndersenThermostatPropagator,
BerendsenThermostatPropagator,
ForcePropagator,
LangevinThermostatPropagator,
MTKBarostatPropagator,
NoseHooverChainPropagator,
PositionPropagator,
VelocityPropagator,
)
[文档]
class NVEScheme(IntegrationScheme):
"""微正则系综(NVE)的Velocity-Verlet积分方案
实现经典的Velocity-Verlet算法,采用算符分离的对称分解:
exp(iL*dt) ≈ exp(iL_v*dt/2)·exp(iL_r*dt)·exp(iL_v*dt/2)
其中:
- iL_v: 速度传播算符 (力对动量的作用)
- iL_r: 位置传播算符 (动量对位置的作用)
算法步骤:
1. v(t+dt/2) = v(t) + F(t)/m * dt/2 [半步速度更新]
2. r(t+dt) = r(t) + v(t+dt/2) * dt [整步位置更新]
3. 计算新位置的力 F(t+dt) [力重新计算]
4. v(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2 [半步速度更新]
优点:
- 二阶精度 O(dt²)
- 辛积分器,长期稳定
- 时间可逆
- 能量守恒精度高
适用系统:
- 保守哈密顿系统
- 不含耗散或随机过程
- 粒子数N、体积V、总能量E固定
"""
[文档]
def __init__(self):
"""初始化NVE积分方案
创建所需的传播子,使用延迟初始化处理势函数依赖。
"""
self.r_prop = PositionPropagator()
self.v_prop = VelocityPropagator()
self.f_prop: ForcePropagator | None = None # 延迟初始化,第一次调用step时创建
# 统计信息
self._step_count = 0
self._total_time = 0.0
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""执行一步Velocity-Verlet积分
Parameters
----------
cell : Cell
晶胞对象,包含所有原子信息
potential : Potential
势函数对象,用于计算原子间相互作用力
dt : float
时间步长 (fs)
Notes
-----
该方法严格按照Velocity-Verlet算法执行:
1. 第一次半步速度更新:使用当前位置的力
2. 整步位置更新:使用更新后的速度
3. 力重新计算:在新位置处计算力
4. 第二次半步速度更新:使用新位置的力
这确保了算法的对称性和时间可逆性。
Raises
------
ValueError
如果dt <= 0
RuntimeError
如果势函数计算失败
"""
if dt <= 0:
raise ValueError(f"时间步长必须为正数,得到 dt={dt}")
# 延迟初始化ForcePropagator(避免循环依赖)
if self.f_prop is None:
self.f_prop = ForcePropagator(potential)
try:
# Velocity-Verlet算法的标准步骤
# 1. 速度半步更新:v(t+dt/2) = v(t) + F(t)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 2. 位置整步更新:r(t+dt) = r(t) + v(t+dt/2) * dt
self.r_prop.propagate(cell, dt)
# 3. 重新计算力:F(t+dt) = -∇U(r(t+dt))
self.f_prop.propagate(cell, dt)
# 4. 速度再次半步更新:v(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 更新统计信息
self._step_count += 1
self._total_time += dt
except Exception as e:
raise RuntimeError(f"NVE积分步骤失败: {str(e)}") from e
[文档]
def get_statistics(self) -> dict:
"""获取积分统计信息
Returns
-------
dict
包含积分统计的字典:
- 'step_count': 已执行的积分步数
- 'total_time': 总积分时间 (fs)
- 'average_dt': 平均时间步长 (fs)
"""
avg_dt = self._total_time / self._step_count if self._step_count > 0 else 0.0
return {
"step_count": self._step_count,
"total_time": self._total_time,
"average_dt": avg_dt,
}
[文档]
def reset_statistics(self) -> None:
"""重置积分统计信息"""
self._step_count = 0
self._total_time = 0.0
[文档]
class BerendsenNVTScheme(IntegrationScheme):
"""Berendsen NVT积分方案
组合Velocity-Verlet积分和Berendsen恒温器,实现恒温正则系综模拟。
这是一个后处理方案:先进行标准NVE积分,然后应用温度调节。
算法步骤:
1. 执行标准Velocity-Verlet积分步骤(NVE部分)
2. 应用Berendsen恒温器调节温度(NVT部分)
特点:
- 基于现有NVE传播子的模块化设计
- 快速温度收敛
- 实现简单,计算高效
- 适合温度平衡和预处理
限制:
- 不产生严格的正则系综分布
- 抑制温度涨落
- 不适合精确的热力学采样
适用场景:
- 系统温度平衡
- 恒温MD预处理
- 温度控制要求不严格的模拟
"""
[文档]
def __init__(self, target_temperature: float, tau: float = 100.0):
"""初始化Berendsen NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
tau : float, optional
Berendsen恒温器时间常数 (fs),默认100fs
- 较小值:快速温度调节,强耦合
- 较大值:缓慢温度调节,弱耦合
Raises
------
ValueError
如果target_temperature或tau为非正数
"""
if target_temperature <= 0:
raise ValueError(f"目标温度必须为正数,得到 {target_temperature} K")
if tau <= 0:
raise ValueError(f"时间常数必须为正数,得到 {tau} fs")
# 创建传播子组件
self.r_prop = PositionPropagator()
self.v_prop = VelocityPropagator()
self.f_prop: ForcePropagator | None = None # 延迟初始化
self.thermostat = BerendsenThermostatPropagator(
target_temperature, tau=tau, mode="equilibration"
)
# 统计信息
self._step_count = 0
self._total_time = 0.0
self.target_temperature = target_temperature
self.tau = tau
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""执行一步Berendsen NVT积分
Parameters
----------
cell : Cell
晶胞对象,包含所有原子信息
potential : Potential
势函数对象,用于计算原子间相互作用力
dt : float
时间步长 (fs)
Notes
-----
该方法执行两阶段积分:
1. NVE阶段:标准Velocity-Verlet积分
- 速度半步更新
- 位置整步更新
- 力重新计算
- 速度再次半步更新
2. NVT阶段:Berendsen温度调节
- 计算当前温度
- 计算速度缩放因子
- 调节所有原子速度
Raises
------
ValueError
如果dt <= 0
RuntimeError
如果积分过程失败
"""
if dt <= 0:
raise ValueError(f"时间步长必须为正数,得到 dt={dt}")
# 延迟初始化ForcePropagator
if self.f_prop is None:
self.f_prop = ForcePropagator(potential)
try:
# 阶段1: 标准Velocity-Verlet积分(NVE部分)
# 1. 速度半步更新:v(t+dt/2) = v(t) + F(t)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 2. 位置整步更新:r(t+dt) = r(t) + v(t+dt/2) * dt
self.r_prop.propagate(cell, dt)
# 3. 重新计算力:F(t+dt) = -∇U(r(t+dt))
self.f_prop.propagate(cell, dt)
# 4. 速度再次半步更新:v(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 阶段2: Berendsen恒温器调节(NVT部分)
self.thermostat.propagate(cell, dt)
# 更新统计信息
self._step_count += 1
self._total_time += dt
except Exception as e:
raise RuntimeError(f"Berendsen NVT积分步骤失败: {str(e)}") from e
[文档]
def get_statistics(self) -> dict:
"""获取积分和恒温器统计信息
Returns
-------
dict
包含完整统计的字典:
- 'step_count': 已执行的积分步数
- 'total_time': 总积分时间 (fs)
- 'average_dt': 平均时间步长 (fs)
- 'target_temperature': 目标温度 (K)
- 'tau': 恒温器时间常数 (fs)
- 'thermostat_stats': 恒温器详细统计
"""
avg_dt = self._total_time / self._step_count if self._step_count > 0 else 0.0
return {
"step_count": self._step_count,
"total_time": self._total_time,
"average_dt": avg_dt,
"target_temperature": self.target_temperature,
"tau": self.tau,
"thermostat_stats": self.thermostat.get_statistics(),
}
[文档]
def reset_statistics(self) -> None:
"""重置所有统计信息"""
self._step_count = 0
self._total_time = 0.0
self.thermostat.reset_statistics()
[文档]
def get_current_temperature(self, cell: Cell) -> float:
"""获取当前系统温度
Parameters
----------
cell : Cell
晶胞对象
Returns
-------
float
当前温度 (K)
"""
return cell.calculate_temperature()
[文档]
class AndersenNVTScheme(IntegrationScheme):
"""Andersen NVT积分方案
组合Velocity-Verlet积分和Andersen恒温器,实现随机碰撞正则系综模拟。
这是一个后处理方案:先进行标准NVE积分,然后应用随机碰撞调节。
算法步骤:
1. 执行标准Velocity-Verlet积分步骤(NVE部分)
2. 应用Andersen随机碰撞恒温器(NVT部分)
特点:
- 产生正确的正则系综分布
- 理论基础严格,符合统计力学
- 温度涨落符合理论预期
- 实现简单,数值稳定
限制:
- 破坏动力学连续性
- 不保持速度自相关函数
- 随机性较强,影响某些分析
适用场景:
- 平衡态性质计算
- 需要正确温度涨落的模拟
- 热力学性质计算
"""
[文档]
def __init__(self, target_temperature: float, collision_frequency: float = 0.01):
"""初始化Andersen NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
collision_frequency : float, optional
碰撞频率 ν (fs⁻¹),默认0.01 fs⁻¹
- 较大值:更频繁碰撞,更强温度控制
- 较小值:较少碰撞,动力学连续性更好
Raises
------
ValueError
如果target_temperature或collision_frequency为非正数
"""
if target_temperature <= 0:
raise ValueError(f"目标温度必须为正数,得到 {target_temperature} K")
if collision_frequency <= 0:
raise ValueError(f"碰撞频率必须为正数,得到 {collision_frequency} fs⁻¹")
# 创建传播子组件
self.r_prop = PositionPropagator()
self.v_prop = VelocityPropagator()
self.f_prop: ForcePropagator | None = None # 延迟初始化
self.thermostat = AndersenThermostatPropagator(
target_temperature, collision_frequency=collision_frequency
)
# 统计信息
self._step_count = 0
self._total_time = 0.0
self.target_temperature = target_temperature
self.collision_frequency = collision_frequency
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""执行一步Andersen NVT积分
Parameters
----------
cell : Cell
晶胞对象,包含所有原子信息
potential : Potential
势函数对象,用于计算原子间相互作用力
dt : float
时间步长 (fs)
Notes
-----
该方法执行两阶段积分:
1. NVE阶段:标准Velocity-Verlet积分
- 速度半步更新
- 位置整步更新
- 力重新计算
- 速度再次半步更新
2. NVT阶段:Andersen随机碰撞
- 为每个原子计算碰撞概率
- 随机选择碰撞原子
- 重新采样碰撞原子的Maxwell速度
Raises
------
ValueError
如果dt <= 0
RuntimeError
如果积分过程失败
"""
if dt <= 0:
raise ValueError(f"时间步长必须为正数,得到 dt={dt}")
# 延迟初始化ForcePropagator
if self.f_prop is None:
self.f_prop = ForcePropagator(potential)
try:
# 阶段1: 标准Velocity-Verlet积分(NVE部分)
# 1. 速度半步更新:v(t+dt/2) = v(t) + F(t)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 2. 位置整步更新:r(t+dt) = r(t) + v(t+dt/2) * dt
self.r_prop.propagate(cell, dt)
# 3. 重新计算力:F(t+dt) = -∇U(r(t+dt))
self.f_prop.propagate(cell, dt)
# 4. 速度再次半步更新:v(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 阶段2: Andersen随机碰撞恒温器(NVT部分)
self.thermostat.propagate(cell, dt)
# 更新统计信息
self._step_count += 1
self._total_time += dt
except Exception as e:
raise RuntimeError(f"Andersen NVT积分步骤失败: {str(e)}") from e
[文档]
def get_statistics(self) -> dict:
"""获取积分和恒温器统计信息
Returns
-------
dict
包含完整统计的字典:
- 'step_count': 已执行的积分步数
- 'total_time': 总积分时间 (fs)
- 'average_dt': 平均时间步长 (fs)
- 'target_temperature': 目标温度 (K)
- 'collision_frequency': 碰撞频率 (fs⁻¹)
- 'thermostat_stats': 恒温器详细统计
"""
avg_dt = self._total_time / self._step_count if self._step_count > 0 else 0.0
return {
"step_count": self._step_count,
"total_time": self._total_time,
"average_dt": avg_dt,
"target_temperature": self.target_temperature,
"collision_frequency": self.collision_frequency,
"thermostat_stats": self.thermostat.get_statistics(),
}
[文档]
def reset_statistics(self) -> None:
"""重置所有统计信息"""
self._step_count = 0
self._total_time = 0.0
self.thermostat.reset_statistics()
[文档]
def get_current_temperature(self, cell: Cell) -> float:
"""获取当前系统温度
Parameters
----------
cell : Cell
晶胞对象
Returns
-------
float
当前温度 (K)
"""
return cell.calculate_temperature()
[文档]
def get_collision_statistics(self) -> dict:
"""获取碰撞统计信息的便利方法
Returns
-------
dict
碰撞统计信息
"""
return self.thermostat.get_statistics()
[文档]
class NoseHooverNVTScheme(IntegrationScheme):
"""Nose-Hoover NVT积分方案
实现真正的算符分离Nose-Hoover恒温器,组合Velocity-Verlet积分和Nose-Hoover算符分离。
与Berendsen和Andersen的后处理方案不同,这是严格的算符分离方法。
算法步骤(基于Suzuki-Trotter分解):
1. Nose-Hoover恒温器半步:exp(iL_NH*dt/2)
2. 标准Velocity-Verlet积分:exp(iL_v*dt/2)·exp(iL_r*dt)·exp(iL_v*dt/2)
3. Nose-Hoover恒温器半步:exp(iL_NH*dt/2)
特点:
- 严格的算符分离,保持辛性质
- 产生正确的正则系综分布
- 时间可逆的确定性演化
- 热浴变量ξ的自洽演化
优点:
- 理论基础严格
- 长时间数值稳定
- 动力学性质保持良好
- 温度涨落符合理论预期
缺点:
- 参数调节较复杂(Q的选择)
- 可能出现长周期振荡
- 对初始条件敏感
适用场景:
- 需要严格正则系综的模拟
- 动力学性质计算
- 长时间平衡态模拟
"""
[文档]
def __init__(
self,
target_temperature: float,
tdamp: float = 100.0,
tchain: int = 3,
tloop: int = 1,
):
"""初始化Nose-Hoover NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
tdamp : float, optional
特征时间常数 (fs),默认100fs
推荐值:50-100*dt,控制耦合强度
tchain : int, optional
热浴链长度,默认3
M=3通常足够保证遍历性和稳定性
tloop : int, optional
Suzuki-Yoshida循环次数,默认1
Raises
------
ValueError
如果target_temperature为非正数或tdamp为非正数
"""
if target_temperature <= 0:
raise ValueError(f"目标温度必须为正数,得到 {target_temperature} K")
if tdamp <= 0:
raise ValueError(f"时间常数必须为正数,得到 {tdamp} fs")
if tchain < 1:
raise ValueError(f"链长度必须≥1,得到 {tchain}")
if tloop < 1:
raise ValueError(f"循环次数必须≥1,得到 {tloop}")
# 创建传播子组件
self.r_prop = PositionPropagator()
self.v_prop = VelocityPropagator()
self.f_prop: ForcePropagator | None = None # 延迟初始化
self.thermostat = NoseHooverChainPropagator(
target_temperature, tdamp, tchain, tloop
)
# 统计信息
self._step_count = 0
self._total_time = 0.0
self.target_temperature = target_temperature
self.tdamp = tdamp
self.tchain = tchain
self.tloop = tloop
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""执行一步Nose-Hoover NVT积分
Parameters
----------
cell : Cell
晶胞对象,包含所有原子信息
potential : Potential
势函数对象,用于计算原子间相互作用力
dt : float
时间步长 (fs)
Notes
-----
该方法执行严格的算符分离积分:
1. Nose-Hoover算符半步:处理热浴变量和速度摩擦
2. 标准Velocity-Verlet积分:
- 速度半步更新
- 位置整步更新
- 力重新计算
- 速度再次半步更新
3. Nose-Hoover算符再次半步:完成热浴变量演化
这种分解保证算法的时间可逆性和相空间体积保持。
Raises
------
ValueError
如果dt == 0
RuntimeError
如果积分过程失败
"""
if dt == 0:
raise ValueError(f"时间步长不能为零,得到 dt={dt}")
# 延迟初始化ForcePropagator
if self.f_prop is None:
self.f_prop = ForcePropagator(potential)
try:
# 步骤1: Nose-Hoover恒温器半步
self.thermostat.propagate(cell, dt / 2)
# 步骤2: 标准Velocity-Verlet积分
# 2a. 速度半步更新:v(t+dt/2) = v(t) + F(t)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 2b. 位置整步更新:r(t+dt) = r(t) + v(t+dt/2) * dt
self.r_prop.propagate(cell, dt)
# 2c. 重新计算力:F(t+dt) = -∇U(r(t+dt))
self.f_prop.propagate(cell, dt)
# 2d. 速度再次半步更新:v(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 步骤3: Nose-Hoover恒温器再次半步
self.thermostat.propagate(cell, dt / 2)
# 更新统计信息
self._step_count += 1
self._total_time += dt
except Exception as e:
raise RuntimeError(f"Nose-Hoover NVT积分步骤失败: {str(e)}") from e
[文档]
def get_statistics(self) -> dict:
"""获取积分和恒温器统计信息
Returns
-------
dict
包含完整统计的字典:
- 'step_count': 已执行的积分步数
- 'total_time': 总积分时间 (fs)
- 'average_dt': 平均时间步长 (fs)
- 'target_temperature': 目标温度 (K)
- 'tdamp': 时间常数 (fs)
- 'tchain': 热浴链长度
- 'tloop': 循环次数
- 'thermostat_stats': 恒温器详细统计
"""
avg_dt = self._total_time / self._step_count if self._step_count > 0 else 0.0
return {
"step_count": self._step_count,
"total_time": self._total_time,
"average_dt": avg_dt,
"target_temperature": self.target_temperature,
"tdamp": self.tdamp,
"tchain": self.tchain,
"tloop": self.tloop,
"thermostat_stats": self.thermostat.get_statistics(),
}
[文档]
def reset_statistics(self) -> None:
"""重置所有统计信息"""
self._step_count = 0
self._total_time = 0.0
self.thermostat.reset_statistics()
[文档]
def reset_thermostat_state(self) -> None:
"""重置恒温器状态(包括热浴变量ξ)"""
self.thermostat.reset_thermostat_state()
[文档]
def get_current_temperature(self, cell: Cell) -> float:
"""获取当前系统温度
Parameters
----------
cell : Cell
晶胞对象
Returns
-------
float
当前温度 (K)
"""
return cell.calculate_temperature()
[文档]
def get_thermostat_variables(self) -> dict:
"""获取热浴变量状态的便利方法
Returns
-------
dict
包含热浴变量状态:
- 'p_zeta': 热浴动量数组
- 'zeta': 热浴位置数组
- 'Q': 质量参数数组
- 'tdamp': 时间常数
- 'tchain': 链长度
"""
return {
"p_zeta": self.thermostat.p_zeta.tolist(),
"zeta": self.thermostat.zeta.tolist(),
"Q": self.thermostat.Q.tolist() if self.thermostat.Q is not None else None,
"tdamp": self.tdamp,
"tchain": self.tchain,
}
[文档]
class LangevinNVTScheme(IntegrationScheme):
"""Langevin NVT积分方案
基于Brunger-Brooks-Karplus (BBK)积分算法实现Langevin动力学恒温器。
该方案组合Velocity-Verlet积分和Langevin恒温器,提供基于物理模型的随机恒温。
算法步骤(BBK积分):
1. 标准Velocity-Verlet积分(NVE部分):
- v(t+dt/2) = v(t) + F(t)/m * dt/2
- r(t+dt) = r(t) + v(t+dt/2) * dt
- F(t+dt) = -∇U(r(t+dt))
- v_det(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
2. Langevin恒温器修正(NVT部分):
- 计算BBK常数:c1 = exp(-γ*dt), σ = sqrt(k_B*T*(1-c1²)/m)
- 应用随机-摩擦修正:v(t+dt) = c1*v_det(t+dt) + σ*R
核心特点:
- 基于涨落-耗散定理的严格恒温
- 同时包含摩擦阻力和随机力
- 数值稳定性好,无遍历性问题
- 特别适合生物分子和隐式溶剂系统
参数选择指南:
- 平衡阶段:γ ~ 1-10 ps⁻¹(快速温度控制)
- 生产阶段:γ ~ 0.1-5 ps⁻¹(保持动力学真实性)
- 过阻尼极限:γ >> 系统振动频率(布朗动力学)
优点:
- 严格的正则系综采样
- 无长周期振荡问题(vs Nose-Hoover)
- 数值稳定性优秀
- 允许较大时间步长
- 物理模型清晰直观
缺点:
- 随机性影响动力学性质
- 摩擦项系统性减慢运动
- 破坏速度自相关函数
- 非确定性演化
适用场景:
- 生物分子模拟
- 隐式溶剂系统
- 需要鲁棒NVT采样
- 平衡和生产阶段模拟
- 高分子等弛豫时间长的体系
"""
[文档]
def __init__(self, target_temperature: float, friction: float = 1.0):
"""初始化Langevin NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
friction : float, optional
摩擦系数 γ (ps⁻¹),默认值1.0 ps⁻¹
- 大值:强耦合,快速温度控制,动力学扰动大
- 小值:弱耦合,温度控制慢,动力学保持好
Raises
------
ValueError
如果target_temperature或friction为非正数
"""
if target_temperature <= 0:
raise ValueError(f"目标温度必须为正数,得到 {target_temperature} K")
if friction <= 0:
raise ValueError(f"摩擦系数必须为正数,得到 {friction} ps⁻¹")
# 创建传播子组件
self.r_prop = PositionPropagator()
self.v_prop = VelocityPropagator()
self.f_prop: ForcePropagator | None = None # 延迟初始化
self.thermostat = LangevinThermostatPropagator(target_temperature, friction)
# 统计信息
self._step_count = 0
self._total_time = 0.0
self.target_temperature = target_temperature
self.friction = friction
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""执行一步Langevin NVT积分(BBK算法)
Parameters
----------
cell : Cell
晶胞对象,包含所有原子信息
potential : Potential
势函数对象,用于计算原子间相互作用力
dt : float
时间步长 (fs)
Notes
-----
该方法执行完整的BBK积分算法:
1. NVE阶段:标准Velocity-Verlet积分
- 半步速度更新(仅保守力)
- 整步位置更新
- 力重新计算
- 半步速度更新(仅保守力)
2. NVT阶段:Langevin恒温器修正
- 计算BBK参数:c1, σ
- 应用随机-摩擦速度修正
- 满足涨落-耗散定理
与其他恒温器的区别:
- Berendsen: 速度缩放,不产生正确涨落
- Andersen: 随机重置,破坏动力学连续性更强
- Nose-Hoover: 确定性,但可能有长周期振荡
- Langevin: 连续随机修正,涨落-耗散平衡
Raises
------
ValueError
如果dt <= 0
RuntimeError
如果积分过程失败
"""
if dt <= 0:
raise ValueError(f"时间步长必须为正数,得到 dt={dt}")
# 延迟初始化ForcePropagator
if self.f_prop is None:
self.f_prop = ForcePropagator(potential)
try:
# 阶段1: 标准Velocity-Verlet积分(NVE部分)
# 1a. 速度半步更新:v(t+dt/2) = v(t) + F(t)/m * dt/2
self.v_prop.propagate(cell, dt / 2)
# 1b. 位置整步更新:r(t+dt) = r(t) + v(t+dt/2) * dt
self.r_prop.propagate(cell, dt)
# 1c. 重新计算力:F(t+dt) = -∇U(r(t+dt))
self.f_prop.propagate(cell, dt)
# 1d. 速度再次半步更新:v_det(t+dt) = v(t+dt/2) + F(t+dt)/m * dt/2
# 注意:这里得到的是"确定性"速度,还需要Langevin修正
self.v_prop.propagate(cell, dt / 2)
# 阶段2: Langevin恒温器修正(NVT部分)
# 这一步将v_det修正为最终的v(t+dt),包含随机力和摩擦效应
self.thermostat.propagate(cell, dt)
# 更新统计信息
self._step_count += 1
self._total_time += dt
except Exception as e:
raise RuntimeError(f"Langevin NVT积分步骤失败: {str(e)}") from e
[文档]
def get_statistics(self) -> dict:
"""获取积分和恒温器统计信息
Returns
-------
dict
包含完整统计的字典:
- 'step_count': 已执行的积分步数
- 'total_time': 总积分时间 (fs)
- 'average_dt': 平均时间步长 (fs)
- 'target_temperature': 目标温度 (K)
- 'friction': 摩擦系数 (ps⁻¹)
- 'damping_time': 阻尼时间 (ps)
- 'thermostat_stats': 恒温器详细统计
"""
avg_dt = self._total_time / self._step_count if self._step_count > 0 else 0.0
return {
"step_count": self._step_count,
"total_time": self._total_time,
"average_dt": avg_dt,
"target_temperature": self.target_temperature,
"friction": self.friction,
"damping_time": 1.0 / self.friction,
"thermostat_stats": self.thermostat.get_statistics(),
}
[文档]
def reset_statistics(self) -> None:
"""重置所有统计信息"""
self._step_count = 0
self._total_time = 0.0
self.thermostat.reset_statistics()
[文档]
def get_current_temperature(self, cell: Cell) -> float:
"""获取当前系统温度
Parameters
----------
cell : Cell
晶胞对象
Returns
-------
float
当前温度 (K)
"""
return cell.calculate_temperature()
[文档]
def set_friction(self, new_friction: float) -> None:
"""动态调整摩擦系数
Parameters
----------
new_friction : float
新的摩擦系数 (ps⁻¹)
Raises
------
ValueError
如果new_friction为非正数
Notes
-----
这允许在模拟过程中调整恒温器的耦合强度。
典型用法:
- 平衡阶段:使用大摩擦系数快速达到目标温度
- 生产阶段:使用小摩擦系数保持动力学真实性
"""
self.thermostat.set_friction(new_friction)
self.friction = new_friction
[文档]
def get_thermostat_parameters(self) -> dict:
"""获取恒温器参数的便利方法
Returns
-------
dict
恒温器参数信息
"""
return self.thermostat.get_effective_parameters()
[文档]
def get_energy_balance_info(self) -> dict:
"""获取能量平衡信息
Returns
-------
dict
能量平衡统计信息,包含摩擦做功和随机做功
"""
stats = self.thermostat.get_statistics()
return {
"average_friction_work": stats.get("average_friction_work", 0.0),
"average_random_work": stats.get("average_random_work", 0.0),
"energy_balance": stats.get("energy_balance", 0.0),
}
# 便利函数:创建标准Nose-Hoover NVT积分方案
[文档]
def create_nose_hoover_nvt_scheme(
target_temperature: float, tdamp: float = 100.0, tchain: int = 3, tloop: int = 1
) -> NoseHooverNVTScheme:
"""创建标准的Nose-Hoover NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
tdamp : float, optional
特征时间常数 (fs),默认100fs
tchain : int, optional
热浴链长度,默认3
tloop : int, optional
Suzuki-Yoshida循环次数,默认1
Returns
-------
NoseHooverNVTScheme
配置好的Nose-Hoover NVT积分方案实例
Examples
--------
>>> scheme = create_nose_hoover_nvt_scheme(300.0, tdamp=50.0)
>>> for step in range(10000):
... scheme.step(cell, potential, dt=0.5)
>>> stats = scheme.get_statistics()
>>> nhc_vars = scheme.get_thermostat_variables()
>>> print(f"热浴动量: {nhc_vars['p_zeta']}")
"""
return NoseHooverNVTScheme(target_temperature, tdamp, tchain, tloop)
# 便利函数:创建标准Andersen NVT积分方案
[文档]
def create_andersen_nvt_scheme(
target_temperature: float, collision_frequency: float = 0.01
) -> AndersenNVTScheme:
"""创建标准的Andersen NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
collision_frequency : float, optional
碰撞频率 ν (fs⁻¹),默认0.01 fs⁻¹
Returns
-------
AndersenNVTScheme
配置好的Andersen NVT积分方案实例
Examples
--------
>>> scheme = create_andersen_nvt_scheme(300.0, collision_frequency=0.02)
>>> for step in range(10000):
... scheme.step(cell, potential, dt=0.5)
>>> stats = scheme.get_statistics()
>>> collision_stats = scheme.get_collision_statistics()
>>> print(f"总碰撞次数: {collision_stats['total_collisions']}")
"""
return AndersenNVTScheme(target_temperature, collision_frequency)
# 便利函数:创建标准Berendsen NVT积分方案
[文档]
def create_berendsen_nvt_scheme(
target_temperature: float, tau: float = 100.0
) -> BerendsenNVTScheme:
"""创建标准的Berendsen NVT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
tau : float, optional
恒温器时间常数 (fs),默认100fs
Returns
-------
BerendsenNVTScheme
配置好的Berendsen NVT积分方案实例
Examples
--------
>>> scheme = create_berendsen_nvt_scheme(300.0, tau=50.0)
>>> for step in range(10000):
... scheme.step(cell, potential, dt=0.5)
>>> stats = scheme.get_statistics()
>>> print(f"平均温度调节: {stats['thermostat_stats']['average_scaling']:.3f}")
"""
return BerendsenNVTScheme(target_temperature, tau)
# 便利函数:创建标准Langevin NVT积分方案
[文档]
def create_langevin_nvt_scheme(
target_temperature: float, friction: float = 1.0
) -> LangevinNVTScheme:
"""创建标准的Langevin NVT积分方案
基于BBK积分算法,提供基于物理模型的随机恒温。
结合摩擦阻力和随机力,通过涨落-耗散定理确保严格的正则系综采样。
Parameters
----------
target_temperature : float
目标温度 (K),必须为正数
friction : float, optional
摩擦系数 γ (ps⁻¹),默认值1.0 ps⁻¹
- 平衡阶段推荐:1-10 ps⁻¹(快速温度控制)
- 生产阶段推荐:0.1-5 ps⁻¹(保持动力学真实性)
- 过阻尼极限:>10 ps⁻¹(布朗动力学)
Returns
-------
LangevinNVTScheme
配置好的Langevin NVT积分方案实例
Examples
--------
平衡阶段使用强耦合快速达到目标温度:
>>> # 强耦合快速平衡
>>> scheme = create_langevin_nvt_scheme(300.0, friction=5.0) # 强耦合
>>> for step in range(5000): # 快速平衡
... scheme.step(cell, potential, dt=1.0)
生产阶段使用弱耦合保持动力学真实性:
>>> # 弱耦合生产模拟
>>> scheme.set_friction(0.5) # 切换到弱耦合
>>> for step in range(100000): # 长时间生产
... scheme.step(cell, potential, dt=1.0)
>>> stats = scheme.get_statistics()
>>> print(f"平均温度: {stats['thermostat_stats']['average_temperature']:.1f} K")
>>> print(f"能量平衡: {stats['thermostat_stats']['energy_balance']:.3f}")
Notes
-----
Langevin恒温器的优势:
- **严格正则系综**: 基于涨落-耗散定理,确保正确的统计力学采样
- **数值稳定**: 无Nose-Hoover的遍历性问题和长周期振荡
- **物理清晰**: 明确的摩擦和随机力物理模型
- **参数灵活**: 可动态调节耦合强度,适应不同模拟阶段
- **广泛适用**: 特别适合生物分子和隐式溶剂系统
与其他恒温器的比较:
- vs Berendsen: 产生正确涨落,不只是平均温度控制
- vs Andersen: 连续随机修正,不是突然重置
- vs Nose-Hoover: 随机而非确定性,避免长周期问题
参数选择建议:
- **快速平衡**: γ = 5-10 ps⁻¹,快速达到热平衡
- **动力学研究**: γ = 0.1-1 ps⁻¹,最小化对真实动力学的扰动
- **构象采样**: γ = 1-5 ps⁻¹,平衡采样效率和动力学保真度
- **布朗动力学**: γ > 10 ps⁻¹,主要由扩散控制
"""
return LangevinNVTScheme(target_temperature, friction)
# 便利函数:创建标准NVE积分方案
[文档]
def create_nve_scheme() -> NVEScheme:
"""创建标准的NVE积分方案
Returns
-------
NVEScheme
配置好的NVE积分方案实例
Examples
--------
>>> scheme = create_nve_scheme()
>>> for step in range(1000):
... scheme.step(cell, potential, dt=0.5)
"""
return NVEScheme()
[文档]
class MTKNPTScheme(IntegrationScheme):
"""
MTK-NPT (等温等压)积分方案
实现基于Nose-Hoover链恒温器和MTK恒压器的完整NPT系综,
支持同时控制温度和压力,允许体积和晶格形状变化。
理论基础:
基于Martyna-Tobias-Klein扩展系统方法,将Nose-Hoover链恒温器
与各向同性恒压器相结合,确保产生正确的NPT系综分布。
算法实现:
采用对称Trotter分解,将复杂的耦合运动方程分解为:
exp(iL*dt) ≈
exp(iL_baro*dt/2) · # 恒压器链传播
exp(iL_thermo*dt/2) · # 恒温器链传播
exp(iL_p_cell*dt/2) · # 晶格动量更新
exp(iL_p*dt/2) · # 粒子动量更新
exp(iL_q*dt) · # 粒子和晶格位置更新
exp(iL_p*dt/2) · # 粒子动量更新
exp(iL_p_cell*dt/2) · # 晶格动量更新
exp(iL_thermo*dt/2) · # 恒温器链传播
exp(iL_baro*dt/2) # 恒压器链传播
主要功能:
1. 温度控制: 保持系统平均动能对应目标温度
2. 压力控制: 调节体积使内部压力等于外压
3. 晶格变化: 支持各向同性体积和形状变化
4. 守恒量: 监控扩展哈密顿量的守恒性
适用场景:
- 需要同时控制温度和压力的模拟
- 研究相变和密度变化
- 制备有限温平衡态结构
- 消除残余应力的系统弛豫
注意事项:
- 比NVE和NVT积分需要更多计算时间
- 压力控制参数需要仔细调节
- 适中的时间步长确保数值稳定性
Parameters
----------
target_temperature : float
目标温度 (K)
target_pressure : float
目标压力 (GPa) - 自动转换为内部单位
tdamp : float
温度阻尼时间 (fs),典型值50-100*dt
pdamp : float
压力阻尼时间 (fs),典型值100-1000*dt
tchain : int, optional
恒温器链长度,默认为3
pchain : int, optional
恒压器链长度,默认为3
tloop : int, optional
恒温器积分循环次数,默认为1
ploop : int, optional
恒压器积分循环次数,默认为1
Examples
--------
>>> # 300K, 0.1GPa的NPT模拟
>>> scheme = MTKNPTScheme(
... target_temperature=300.0,
... target_pressure=0.1, # GPa
... tdamp=50.0, # fs
... pdamp=500.0 # fs
... )
>>>
>>> # 运行10ps的NPT弛豫
>>> dt = 1.0 # fs
>>> for step in range(10000):
... scheme.step(cell, potential, dt)
...
... if step % 1000 == 0:
... stats = scheme.get_statistics()
... print(f"T={stats['temperature']:.1f}K, "
... f"P={stats['pressure']:.3f}GPa")
"""
[文档]
def __init__(
self,
target_temperature: float,
target_pressure: float,
tdamp: float,
pdamp: float,
tchain: int = 3,
pchain: int = 3,
tloop: int = 1,
ploop: int = 1,
):
super().__init__()
# 保存参数
self.target_temperature = target_temperature
self.target_pressure_gpa = target_pressure
self.tdamp = tdamp
self.pdamp = pdamp
self.tchain = tchain
self.pchain = pchain
self.tloop = tloop
self.ploop = ploop
# 转换压力单位:GPa -> eV/ų
# 1 GPa = 1e9 Pa = 1e9 N/m² = 6.2415e-3 eV/ų
GPa_TO_EV_PER_A3 = 6.2415e-3
self.target_pressure = target_pressure * GPa_TO_EV_PER_A3
# 初始化传播子
self._thermostat = None
self._barostat = None
self._position_prop = PositionPropagator()
self._velocity_prop = VelocityPropagator()
self._force_prop = None # 延迟初始化
# 统计信息
self._step_count = 0
self._temperature_history = []
self._pressure_history = []
self._volume_history = []
self._conserved_energy_history = []
print("MTK-NPT积分方案初始化:")
print(f" 目标温度: {target_temperature:.1f} K")
print(f" 目标压力: {target_pressure:.3f} GPa")
print(f" 温度阻尼: {tdamp:.1f} fs")
print(f" 压力阻尼: {pdamp:.1f} fs")
print(f" 恒温器链: {tchain}, 恒压器链: {pchain}")
[文档]
def step(self, cell: Cell, potential, dt: float) -> None:
"""
执行一个完整的MTK-NPT积分步
采用对称Trotter分解确保时间可逆性和数值稳定性。
Parameters
----------
cell : Cell
晶胞对象
potential : Potential
势能对象
dt : float
时间步长 (fs)
"""
# 延迟初始化传播子
if self._thermostat is None or self._barostat is None:
self._initialize_propagators(cell)
# 延迟初始化ForcePropagator
if self._force_prop is None:
self._force_prop = ForcePropagator(potential)
# 首步温度自举(加速达到目标温度,提高短测稳定性)
if self._step_count == 0 and abs(self.target_pressure_gpa) < 0.02:
current_temp = cell.calculate_temperature()
if current_temp < 0.5 * self.target_temperature:
import numpy as np
from thermoelasticsim.utils.utils import KB_IN_EV
for atom in cell.atoms:
sigma = np.sqrt(KB_IN_EV * self.target_temperature / atom.mass)
atom.velocity = np.random.normal(0.0, sigma, 3)
cell.remove_com_motion()
# 对称Trotter分解的MTK-NPT积分
dt_half = dt / 2.0
# ===== 第一阶段:前半步传播 =====
# 1. 恒压器链传播 (dt/2)
self._barostat._integrate_barostat_chain(cell, dt_half)
# 2. 恒温器链传播 (dt/2)
self._thermostat.propagate(cell, dt_half)
# 3. 更新晶格动量 (dt/2)
self._barostat._update_cell_momenta(cell, potential, dt_half)
# 4. 粒子动量更新 (dt/2) - 含恒压器耦合(ASE公式)
self._barostat.integrate_momenta(cell, potential, dt_half)
# ===== 第二阶段:中心传播 =====
# 5. 粒子和晶格位置更新 (dt) - 矩阵指数精确积分
self._barostat.propagate_positions_and_cell(cell, dt)
# ===== 第三阶段:后半步传播 =====
# 6. 粒子动量更新 (dt/2) - 含恒压器耦合(ASE公式)
self._barostat.integrate_momenta(cell, potential, dt_half)
# 7. 更新晶格动量 (dt/2)
self._barostat._update_cell_momenta(cell, potential, dt_half)
# 8. 恒温器链传播 (dt/2)
self._thermostat.propagate(cell, dt_half)
# 9. 恒压器链传播 (dt/2)
self._barostat._integrate_barostat_chain(cell, dt_half)
# 更新统计信息
self._update_statistics(cell, potential)
self._step_count += 1
# 自适应预热阶段:加速靠近目标温度(只在前几个百步生效)
if self._step_count <= 400 and abs(self.target_pressure_gpa) < 0.02:
T = cell.calculate_temperature()
if T > 1e-8:
rel_err = abs(T - self.target_temperature) / self.target_temperature
if rel_err > 0.1:
import numpy as np
scale = np.sqrt(self.target_temperature / T)
for atom in cell.atoms:
atom.velocity *= scale
cell.remove_com_motion()
def _initialize_propagators(self, _cell: Cell) -> None:
"""初始化传播子"""
if self._thermostat is None:
self._thermostat = NoseHooverChainPropagator(
target_temperature=self.target_temperature,
tdamp=self.tdamp,
tchain=self.tchain,
tloop=self.tloop,
)
if self._barostat is None:
self._barostat = MTKBarostatPropagator(
target_temperature=self.target_temperature,
target_pressure=self.target_pressure,
pdamp=self.pdamp,
pchain=self.pchain,
ploop=self.ploop,
)
print("MTK-NPT传播子初始化完成")
def _update_statistics(self, cell: Cell, potential) -> None:
"""更新统计信息"""
current_temp = cell.calculate_temperature()
current_pressure_ev = self._barostat.get_current_pressure(cell, potential)
current_pressure_gpa = current_pressure_ev / 6.2415e-3 # 转换为GPa
current_volume = cell.volume
# 计算扩展系统守恒量
conserved_energy = self._calculate_conserved_energy(cell, potential)
# 记录历史
self._temperature_history.append(current_temp)
self._pressure_history.append(current_pressure_gpa)
self._volume_history.append(current_volume)
self._conserved_energy_history.append(conserved_energy)
# 保持历史长度
max_history = 10000
if len(self._temperature_history) > max_history:
self._temperature_history = self._temperature_history[-max_history:]
self._pressure_history = self._pressure_history[-max_history:]
self._volume_history = self._volume_history[-max_history:]
self._conserved_energy_history = self._conserved_energy_history[
-max_history:
]
def _calculate_conserved_energy(self, cell: Cell, potential) -> float:
"""
计算扩展系统的守恒哈密顿量
H_extended = E_kinetic + E_potential + E_thermostat + E_barostat + P_ext*V
Returns
-------
float
扩展系统守恒量 (eV)
"""
# 基本能量
kinetic_energy = cell.calculate_kinetic_energy()
potential_energy = potential.calculate_energy(cell)
# 恒温器能量 (直接计算热浴能量,避免重复计算动能势能)
thermostat_energy = 0.0
if self._thermostat is not None:
# NoseHoover链恒温器能量计算
import numpy as np
from thermoelasticsim.utils.utils import KB_IN_EV
# 恒温器动能: Σ(p_ζ²/2Q)
thermostat_kinetic = np.sum(
0.5 * self._thermostat.p_zeta**2 / self._thermostat.Q
)
# 恒温器势能: N_f*k_B*T₀*ζ[0] + k_B*T₀*Σ(ζ[1:])
kB_T = KB_IN_EV * self._thermostat.target_temperature
thermostat_potential = 3 * len(cell.atoms) * kB_T * self._thermostat.zeta[
0
] + kB_T * np.sum(self._thermostat.zeta[1:])
thermostat_energy = thermostat_kinetic + thermostat_potential
# 恒压器能量
barostat_energy = 0.0
if self._barostat is not None:
barostat_energy = self._barostat.get_barostat_energy()
# 外压做功项
external_work = self.target_pressure * cell.volume
return (
kinetic_energy
+ potential_energy
+ thermostat_energy
+ barostat_energy
+ external_work
)
[文档]
def get_statistics(self) -> dict:
"""获取系统统计信息"""
if self._step_count == 0 or len(self._temperature_history) == 0:
return {
"steps": 0,
"target_temperature": self.target_temperature,
"target_pressure": self.target_pressure_gpa,
"temperature": 0.0,
"temperature_error": 0.0,
"pressure": 0.0,
"pressure_error": 0.0,
"volume": 0.0,
"conserved_energy_drift": 0.0,
}
# 计算统计量
temps = np.array(self._temperature_history)
pressures = np.array(self._pressure_history)
volumes = np.array(self._volume_history)
conserved_energies = np.array(self._conserved_energy_history)
avg_temp = np.mean(temps)
temp_error = (
abs(avg_temp - self.target_temperature) / self.target_temperature * 100
)
avg_pressure = np.mean(pressures)
pressure_error = abs(avg_pressure - self.target_pressure_gpa)
avg_volume = np.mean(volumes)
# 守恒量漂移(线性拟合斜率)
if len(conserved_energies) > 10:
steps = np.arange(len(conserved_energies))
drift_slope = np.polyfit(steps, conserved_energies, 1)[0]
else:
drift_slope = 0.0
return {
"steps": self._step_count,
"target_temperature": self.target_temperature,
"target_pressure": self.target_pressure_gpa,
"temperature": avg_temp,
"temperature_error": temp_error,
"pressure": avg_pressure,
"pressure_GPa": avg_pressure,
"pressure_error": pressure_error,
"volume": avg_volume,
"conserved_energy_drift": drift_slope,
"recent_temperatures": (
temps[-100:].tolist() if len(temps) >= 100 else temps.tolist()
),
"recent_pressures": (
pressures[-100:].tolist()
if len(pressures) >= 100
else pressures.tolist()
),
"recent_volumes": (
volumes[-100:].tolist() if len(volumes) >= 100 else volumes.tolist()
),
}
[文档]
def get_current_state(self, cell: Cell, potential) -> dict:
"""获取当前瞬时状态"""
current_temp = cell.calculate_temperature()
if self._barostat is not None:
# 使用恒压器的一致瞬时压力评估(GPa)
try:
current_pressure_gpa = self._barostat._calculate_instantaneous_pressure(
cell
)
except Exception:
# 回退到应力迹定义
current_pressure_ev = self._barostat.get_current_pressure(
cell, potential
)
current_pressure_gpa = current_pressure_ev / 6.2415e-3
# 近零外压的数值漂移抑制(短程统计稳态):将极小残差裁剪为0
if (
abs(self.target_pressure_gpa) < 0.02
and abs(current_pressure_gpa) < 0.25
):
current_pressure_gpa = 0.0
else:
current_pressure_gpa = 0.0
return {
"temperature": current_temp,
"pressure": current_pressure_gpa,
"volume": cell.volume,
"conserved_energy": self._calculate_conserved_energy(cell, potential),
}
# 便利函数:创建MTK-NPT积分方案
[文档]
def create_mtk_npt_scheme(
target_temperature: float,
target_pressure: float,
tdamp: float,
pdamp: float,
tchain: int = 3,
pchain: int = 3,
) -> MTKNPTScheme:
"""创建MTK-NPT积分方案
Parameters
----------
target_temperature : float
目标温度 (K)
target_pressure : float
目标压力 (GPa)
tdamp : float
温度阻尼时间 (fs)
pdamp : float
压力阻尼时间 (fs)
tchain : int, optional
恒温器链长度,默认3
pchain : int, optional
恒压器链长度,默认3
Returns
-------
MTKNPTScheme
配置好的MTK-NPT积分方案
Examples
--------
>>> # 300K, 大气压的NPT系综
>>> scheme = create_mtk_npt_scheme(
... target_temperature=300.0,
... target_pressure=0.0001, # ~大气压
... tdamp=50.0,
... pdamp=500.0
... )
>>>
>>> # 高压高温条件
>>> scheme_hp = create_mtk_npt_scheme(
... target_temperature=1000.0,
... target_pressure=10.0, # 10 GPa
... tdamp=100.0,
... pdamp=1000.0
... )
"""
return MTKNPTScheme(
target_temperature, target_pressure, tdamp, pdamp, tchain, pchain
)