# 文件名: mechanics.py
# 作者: Gilbert Young
# 修改日期: 2025-09-05
# 文件描述: 实现应力和应变计算器,包括基于 Lennard-Jones 势和EAM势的应力计算器。
import contextlib
import logging
import matplotlib as mpl
import numpy as np
from thermoelasticsim.interfaces.cpp_interface import CppInterface
from thermoelasticsim.utils.utils import TensorConverter
# 设置matplotlib的日志级别为WARNING,屏蔽字体调试信息
mpl.set_loglevel("WARNING")
# 配置我们自己的日志
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger(__name__)
[文档]
class StressCalculator:
r"""应力张量计算器
总应力由两部分组成(体积 :math:`V`):
动能项(动量通量)
.. math::
\sigma^{\text{kin}}_{\alpha\beta}
= -\,\frac{1}{V} \sum_i m_i\, v_{i\alpha} v_{i\beta}
维里项(力-位矢项)
.. math::
\sigma^{\text{vir}}_{\alpha\beta}
= -\,\frac{1}{V} \sum_{i<j} r_{ij,\alpha}\, F_{ij,\beta}
Notes
-----
- 有限差分/晶格导数法(如 :math:`-\partial U/\partial \varepsilon` 或
:math:`-V^{-1}\,\partial U/\partial \mathbf{h}\, \mathbf{h}^T`)是另一种等价
的应力计算方式,用于数值校验;它并非额外“第三项”,不与维里项相加。
- 本实现采用 :math:`\sigma = \sigma^{\text{kin}} + \sigma^{\text{vir}}`。
- 对 EAM 势,使用经特殊处理的多体维里解析形式;如解析不可用,可用有限差分做校验。
- 正负号及指标约定与项目其它模块保持一致。
"""
[文档]
def __init__(self):
self.cpp_interface = CppInterface("stress_calculator")
logger.debug("StressCalculator initialized")
[文档]
def calculate_kinetic_stress(self, cell) -> np.ndarray:
r"""计算动能应力张量
使用的约定:
.. math::
\sigma^{\text{kin}}_{\alpha\beta}
= -\,\frac{1}{V} \sum_i m_i\, v_{i\alpha} v_{i\beta}.
说明:在静止构型或 :math:`T\to 0` 极限,速度趋近于零,因此该项趋近于 0。
Parameters
----------
cell : Cell
包含原子的晶胞对象
Returns
-------
numpy.ndarray
动能应力张量 (3, 3),单位 eV/ų
"""
try:
velocities = cell.get_velocities() # shape: (N, 3)
masses = np.array([atom.mass for atom in cell.atoms], dtype=np.float64)
volume = cell.volume
# 向量化实现:-1/V * (V^T @ (diag(m) @ V))
mv = velocities * masses[:, None] # (N,3)
kinetic_stress = -(velocities.T @ mv) / volume # (3,3)
return kinetic_stress
except Exception as e:
logger.error(f"Error in kinetic stress calculation: {e}")
raise
[文档]
def calculate_virial_stress(self, cell, potential) -> np.ndarray:
r"""计算维里应力张量(相互作用力贡献)
公式:
.. math::
\sigma^{\text{vir}}_{\alpha\beta}
= -\,\frac{1}{V} \sum_{i<j} r_{ij,\alpha}\, F_{ij,\beta}
其中:
:math:`\mathbf{r}_{ij} = \mathbf{r}_i - \mathbf{r}_j`
从原子 :math:`j` 指向原子 :math:`i` 的位移向量
:math:`\mathbf{F}_{ij}`
原子 :math:`j` 作用于 :math:`i` 的力
Parameters
----------
cell : Cell
包含原子的晶胞对象
potential : Potential
势能对象
Returns
-------
np.ndarray
维里应力张量 (3, 3),单位 eV/ų
"""
try:
volume = cell.volume
# 优先使用C++的EAM维里实现(若可用)
virial_tensor = None
try:
if hasattr(potential, "cpp_interface"):
libname = getattr(potential.cpp_interface, "_lib_name", None)
num_atoms = len(cell.atoms)
positions = np.ascontiguousarray(
cell.get_positions(), dtype=np.float64
)
lattice = np.ascontiguousarray(
cell.lattice_vectors, dtype=np.float64
)
if libname == "eam_al1" and hasattr(
potential.cpp_interface, "calculate_eam_al1_virial"
):
virial_tensor = (
potential.cpp_interface.calculate_eam_al1_virial(
num_atoms, positions, lattice.flatten()
)
)
elif libname == "eam_cu1" and hasattr(
potential.cpp_interface, "calculate_eam_cu1_virial"
):
virial_tensor = (
potential.cpp_interface.calculate_eam_cu1_virial(
num_atoms, positions, lattice.flatten()
)
)
elif libname == "tersoff_c1988" and hasattr(
potential, "_calculate_virial_tensor"
):
virial_tensor = potential._calculate_virial_tensor(cell)
except Exception as e_cpp:
logger.debug(
f"C++ EAM virial not available, fallback to Python: {e_cpp}"
)
if virial_tensor is None:
# 去除 Python 通用回退,强制要求势提供 C++/专用实现
raise RuntimeError(
"Virial tensor calculation is not available for this potential. "
"Please build C++ backend or provide a specialized virial implementation."
)
virial_stress = virial_tensor / volume
return virial_stress
except Exception as e:
logger.error(f"Error in virial stress calculation: {e}")
raise
[文档]
def calculate_total_stress(self, cell, potential) -> np.ndarray:
r"""计算总应力张量(动能项 + 维里项)
.. math::
\sigma_{\alpha\beta}
= -\,\frac{1}{V} \left( \sum_i m_i v_{i\alpha} v_{i\beta}
+ \sum_{i<j} r_{ij,\alpha} F_{ij,\beta} \right)
Parameters
----------
cell : Cell
包含原子的晶胞对象
potential : Potential
势能对象
Returns
-------
np.ndarray
总应力张量 (3, 3),单位 eV/ų
"""
try:
# 优先:若势提供专用维里实现(C++/Python),使用“动能(向量化) + 专用维里”以保持物理一致性
try:
kinetic_stress = self.calculate_kinetic_stress(cell)
virial_stress = self.calculate_virial_stress(cell, potential)
total_stress = kinetic_stress + virial_stress
logger.debug(
f"Total stress magnitude: {np.linalg.norm(total_stress):.2e}"
)
return total_stress
except Exception:
# 若无专用维里实现,则使用 C++ 一步式总应力(避免 Python 通用回退)
with contextlib.suppress(Exception):
potential.calculate_forces(cell)
num_atoms = len(cell.atoms)
positions = cell.get_positions()
velocities = cell.get_velocities()
forces = cell.get_forces()
masses = np.array([atom.mass for atom in cell.atoms], dtype=np.float64)
volume = cell.volume
box_lengths = np.linalg.norm(cell.lattice_vectors, axis=1)
stress_tensor = np.zeros((3, 3), dtype=np.float64)
self.cpp_interface.compute_stress(
num_atoms,
positions,
velocities,
forces,
masses,
volume,
box_lengths,
stress_tensor,
)
return stress_tensor
except Exception as e:
logger.error(f"Error in total stress calculation: {e}")
raise
[文档]
def calculate_finite_difference_stress(
self, cell, potential, dr=1e-6
) -> np.ndarray:
"""已移除:有限差分应力路径不再提供,避免误用。"""
raise RuntimeError("Finite-difference stress path has been removed.")
# 已移除能量有限差分路径,避免误用。
[文档]
def get_all_stress_components(self, cell, potential) -> dict[str, np.ndarray]:
"""
计算应力张量的所有分量
Parameters
----------
cell : Cell
包含原子的晶胞对象
potential : Potential
势能对象
Returns
-------
Dict[str, np.ndarray]
包含应力张量分量的字典,键包括:
- "kinetic": 动能应力张量
- "virial": 维里应力张量
- "total": 总应力张量(kinetic + virial)
"""
try:
components = {}
# 计算各个分量
kinetic_stress = self.calculate_kinetic_stress(cell)
virial_stress = self.calculate_virial_stress(cell, potential)
total_stress = self.calculate_total_stress(cell, potential)
# 标准键名
components["kinetic"] = kinetic_stress
components["virial"] = virial_stress
components["total"] = total_stress
return components
except Exception as e:
logger.error(f"Error calculating stress tensors: {e}")
raise
[文档]
def compute_stress(self, cell, potential):
"""
计算应力张量(使用总应力作为主要方法)
Parameters
----------
cell : Cell
包含原子的晶胞对象
potential : Potential
势能对象
Returns
-------
np.ndarray
3x3 应力张量矩阵(总应力 = 动能 + 维里)
"""
# 使用总应力作为主要计算方法
return self.calculate_total_stress(cell, potential)
# 向后兼容的别名方法
[文档]
def calculate_virial_kinetic_stress(self, cell, potential) -> np.ndarray:
"""向后兼容别名 - 指向总应力方法"""
return self.calculate_total_stress(cell, potential)
[文档]
def calculate_stress_basic(self, cell, potential) -> np.ndarray:
"""向后兼容别名 - 指向总应力方法"""
return self.calculate_total_stress(cell, potential)
[文档]
def validate_tensor_symmetry(
self, tensor: np.ndarray, tolerance: float = 1e-10
) -> bool:
"""
验证应力张量是否对称
Parameters
----------
tensor : np.ndarray
应力张量 (3x3)
tolerance : float, optional
对称性的容差, 默认值为1e-10
Returns
-------
bool
如果对称则为True, 否则为False
"""
if tensor.shape != (3, 3):
logger.error(f"Tensor shape is {tensor.shape}, expected (3, 3).")
return False
is_symmetric = np.allclose(tensor, tensor.T, atol=tolerance)
if not is_symmetric:
logger.warning("Stress tensor is not symmetric.")
return is_symmetric
[文档]
class StrainCalculator:
"""
应变计算器类
Parameters
----------
F : numpy.ndarray
3x3 变形矩阵
"""
[文档]
def compute_strain(self, F):
"""
计算应变张量并返回 Voigt 表示法
Parameters
----------
F : numpy.ndarray
3x3 变形矩阵
Returns
-------
numpy.ndarray
应变向量,形状为 (6,)
"""
strain_tensor = 0.5 * (F + F.T) - np.identity(3) # 线性应变张量
# 转换为 Voigt 表示法
strain_voigt = TensorConverter.to_voigt(strain_tensor)
# 对剪切分量乘以 2
strain_voigt[3:] *= 2
return strain_voigt