#!/usr/bin/env python3
r"""
ThermoElasticSim - Tersoff 势模块
.. moduleauthor:: Gilbert Young
Tersoff 多体势用于共价材料(Si、C 等),本实现提供 C(1988) 版本的参数化,
并通过 C++ 后端计算能量、力与维里张量(支持可选的平衡键长 shift=delta)。
References
----------
- Tersoff, J. (1988). Empirical Interatomic Potential for Carbon, with Applications to Amorphous Carbon.
Phys. Rev. Lett. 61, 2879–2882.
- Tersoff, J. (1988). New empirical approach for the structure and energy of covalent systems.
Phys. Rev. B 37, 6991–7000.
"""
import logging
import numpy as np
from thermoelasticsim.core.structure import Cell
from thermoelasticsim.interfaces.cpp_interface import CppInterface
from thermoelasticsim.utils.utils import NeighborList
from .base import Potential
logger = logging.getLogger(__name__)
[文档]
class TersoffC1988Potential(Potential):
r"""Tersoff (1988, C) 多体势实现(C++ 后端)。
Parameters
----------
params : dict | None, optional
自定义参数;若为 None 则使用 C++ 内置的 1988 年碳默认参数。
delta : float, optional
LAMMPS 风格的可选键长 shift(r → r + delta),默认 0.0。
Notes
-----
- 单位:能量 eV,长度 Å,力 eV/Å。
- 截断半径自动取 :code:`R + D`(由参数决定)。
- 该实现直接使用 C++ 多体解析力,维里按 :math:`-\sum_i r_i \otimes F_i` 计算。
"""
[文档]
def __init__(self, params: dict | None = None, delta: float = 0.0):
self.params = params # 自定义参数(可选)
self.delta = float(delta)
self.cpp_interface = CppInterface("tersoff_c1988")
# 截断:若用户自定义参数,则用其 R+D;否则用 C++ 默认值 R+D=2.10 Å
if isinstance(params, dict) and all(k in params for k in ("R", "D")):
cutoff = float(params["R"]) + float(params["D"]) # type: ignore
else:
cutoff = 1.95 + 0.15 # C1988 默认
super().__init__(parameters=(params or {}), cutoff=cutoff)
def _cpp_args(self):
# 自定义参数路径用通用接口;否则用 C(1988) 默认接口
if isinstance(self.params, dict) and self.params:
p = self.params
return dict(
A=float(p["A"]),
B=float(p["B"]),
lambda1=float(p["lambda1"]),
lambda2=float(p["lambda2"]),
lambda3=float(p.get("lambda3", 0.0)),
beta=float(p["beta"]),
n=float(p["n"]),
c=float(p["c"]),
d=float(p["d"]),
h=float(p["h"]),
R=float(p["R"]),
D=float(p["D"]),
m=int(p.get("m", 3)),
shift_flag=(abs(self.delta) > 0.0),
delta=self.delta,
)
else:
# 标识:使用 C(1988) 内置参数
return None
[文档]
def calculate_forces(
self, cell: Cell, neighbor_list: NeighborList | None = None
) -> None:
"""计算并更新原子受力。
Parameters
----------
cell : Cell
原子系统
neighbor_list : NeighborList | None
邻居列表(未使用)
"""
num_atoms = cell.num_atoms
positions = np.ascontiguousarray(
cell.get_positions(), dtype=np.float64
).flatten()
lattice_vectors = np.ascontiguousarray(
cell.lattice_vectors, dtype=np.float64
).flatten()
forces = np.zeros_like(positions, dtype=np.float64)
args = self._cpp_args()
if args is None:
self.cpp_interface.calculate_tersoff_c1988_forces(
num_atoms,
positions,
lattice_vectors,
forces,
shift_flag=(abs(self.delta) > 0.0),
delta=self.delta,
)
else:
self.cpp_interface.calculate_tersoff_forces(
num_atoms, positions, lattice_vectors, forces, **args
)
f = forces.reshape((num_atoms, 3))
for i, atom in enumerate(cell.atoms):
atom.force = f[i]
try:
max_f = float(np.max(np.linalg.norm(f, axis=1))) if num_atoms > 0 else 0.0
sum_f = np.sum(f, axis=0)
norm_sum_f = float(np.linalg.norm(sum_f))
logger.debug(
"TersoffC1988: 力统计 max|F|=%.3e eV/Å, |ΣF|=%.3e eV/Å",
max_f,
norm_sum_f,
)
except Exception:
pass
[文档]
def calculate_energy(
self, cell: Cell, neighbor_list: NeighborList | None = None
) -> float:
"""计算系统总能量。
Parameters
----------
cell : Cell
原子系统
neighbor_list : NeighborList | None
邻居列表(未使用)
Returns
-------
float
总能量(eV)
"""
num_atoms = cell.num_atoms
positions = np.ascontiguousarray(
cell.get_positions(), dtype=np.float64
).flatten()
lattice_vectors = np.ascontiguousarray(
cell.lattice_vectors, dtype=np.float64
).flatten()
args = self._cpp_args()
if args is None:
energy = self.cpp_interface.calculate_tersoff_c1988_energy(
num_atoms,
positions,
lattice_vectors,
shift_flag=(abs(self.delta) > 0.0),
delta=self.delta,
)
else:
energy = self.cpp_interface.calculate_tersoff_energy(
num_atoms, positions, lattice_vectors, **args
)
e = float(energy)
logger.debug("TersoffC1988: 总能量 E=%.6e eV", e)
return e
# 供 StressCalculator 专用(可选调用)
def _calculate_virial_tensor(self, cell: Cell) -> np.ndarray:
num_atoms = cell.num_atoms
positions = np.ascontiguousarray(
cell.get_positions(), dtype=np.float64
).flatten()
lattice_vectors = np.ascontiguousarray(
cell.lattice_vectors, dtype=np.float64
).flatten()
args = self._cpp_args()
if args is None:
vir = self.cpp_interface.calculate_tersoff_c1988_virial(
num_atoms,
positions,
lattice_vectors,
shift_flag=(abs(self.delta) > 0.0),
delta=self.delta,
)
else:
vir = self.cpp_interface.calculate_tersoff_virial(
num_atoms, positions, lattice_vectors, **args
)
try:
frob = float(np.linalg.norm(vir))
# Python 回退近似:-Σ r ⊗ F(用于诊断)
try:
self.calculate_forces(cell)
pos = cell.get_positions()
frc = cell.get_forces()
vir_py = np.zeros((3, 3), dtype=np.float64)
for i in range(pos.shape[0]):
vir_py -= np.outer(pos[i], frc[i])
frob_py = float(np.linalg.norm(vir_py))
logger.debug(
"TersoffC1988: 维里张量 Frobenius=%.3e | 近似(Σr⊗F)=%.3e",
frob,
frob_py,
)
except Exception:
logger.debug("TersoffC1988: 维里诊断失败(Σr⊗F)")
else:
pass
except Exception:
pass
return vir