thermoelasticsim.interfaces.cpp_interface 源代码

# 文件名: cpp_interface.py
# 作者: Gilbert Young
# 修改日期: 2025-08-24
# 文件描述: 用于在 Python 中调用 C++ 实现的接口类。

"""
接口模块。

该模块定义了 `CppInterface` 类,用于通过 pybind11 调用外部 C++ 函数库。
"""

import logging

import numpy as np

logger = logging.getLogger(__name__)

try:
    # 导入 pybind11 扩展模块
    import thermoelasticsim._cpp_core as _cpp_core  # type: ignore
except ImportError:
    logger.error(
        "Failed to import pybind11 module _cpp_core. Please build the C++ extensions."
    )
    raise ImportError(
        "pybind11 module _cpp_core is not available. Run 'uv pip install -e .' to build."
    ) from None


[文档] class CppInterface: """用于调用 C++ 实现的函数的接口类 @class CppInterface @brief 用于调用 C++ 实现的函数的接口类 Parameters ---------- lib_name : str 库的名称,用于确定可用的函数集合。 """
[文档] def __init__(self, lib_name): self._lib_name = lib_name self._cpp = _cpp_core # 检查当前lib_name在pybind11中是否有对应函数 if lib_name == "lennard_jones": if not ( hasattr(_cpp_core, "calculate_lj_energy") and hasattr(_cpp_core, "calculate_lj_forces") ): raise RuntimeError( "Lennard-Jones functions not available in pybind11 module" ) logger.debug("Using pybind11 backend for Lennard-Jones") elif lib_name == "eam_al1": if not ( hasattr(_cpp_core, "calculate_eam_al1_energy") and hasattr(_cpp_core, "calculate_eam_al1_forces") ): raise RuntimeError("EAM Al1 functions not available in pybind11 module") logger.debug("Using pybind11 backend for EAM Al1") elif lib_name == "eam_cu1": if not ( hasattr(_cpp_core, "calculate_eam_cu1_energy") and hasattr(_cpp_core, "calculate_eam_cu1_forces") ): raise RuntimeError("EAM Cu1 functions not available in pybind11 module") logger.debug("Using pybind11 backend for EAM Cu1") elif lib_name == "tersoff_c1988": need = ( hasattr(_cpp_core, "calculate_tersoff_c1988_energy") and hasattr(_cpp_core, "calculate_tersoff_c1988_forces") and hasattr(_cpp_core, "calculate_tersoff_c1988_virial") ) if not need: raise RuntimeError("Tersoff functions not available in pybind11 module") logger.debug("Using pybind11 backend for Tersoff C1988") elif lib_name == "stress_calculator": if not hasattr(_cpp_core, "compute_stress"): raise RuntimeError( "Stress calculator functions not available in pybind11 module" ) logger.debug("Using pybind11 backend for Stress Calculator") elif lib_name == "nose_hoover": if not hasattr(_cpp_core, "nose_hoover"): raise RuntimeError( "Nose-Hoover functions not available in pybind11 module" ) logger.debug("Using pybind11 backend for Nose-Hoover") elif lib_name == "nose_hoover_chain": if not hasattr(_cpp_core, "nose_hoover_chain"): raise RuntimeError( "Nose-Hoover Chain functions not available in pybind11 module" ) logger.debug("Using pybind11 backend for Nose-Hoover Chain") elif lib_name == "parrinello_rahman_hoover": if not hasattr(_cpp_core, "parrinello_rahman_hoover"): raise RuntimeError( "Parrinello-Rahman-Hoover functions not available in pybind11 module" ) logger.debug("Using pybind11 backend for Parrinello-Rahman-Hoover") else: raise ValueError(f"未知的库名称: {lib_name}")
[文档] def compute_stress( self, num_atoms: int, positions: np.ndarray, velocities: np.ndarray, forces: np.ndarray, masses: np.ndarray, volume: float, box_lengths: np.ndarray, stress_tensor: np.ndarray, ): """ 计算应力张量。 本函数允许输入的 positions, velocities, forces 既可以是 (num_atoms, 3) 也可以是 (3*num_atoms,) 的形状。 同理,对于 stress_tensor,既可以是 (3,3) 也可以是 (9,)。 Parameters ---------- num_atoms : int positions : np.ndarray 原子位置数组,可为 (num_atoms, 3) 或 (3*num_atoms, ) velocities : np.ndarray 原子速度数组,可为 (num_atoms, 3) 或 (3*num_atoms, ) forces : np.ndarray 原子力数组,可为 (num_atoms, 3) 或 (3*num_atoms, ) masses : np.ndarray 原子质量数组 (num_atoms,) volume : float 晶胞体积 box_lengths : np.ndarray 晶胞长度数组 (3,) stress_tensor : np.ndarray 输出应力张量,可为 (3,3) 或 (9,) Returns ------- None """ def ensure_flat(array, length): # 此内部函数将 (num_atoms,3) 转为 (3*num_atoms,) 的视图,或检查本就是(3*num_atoms,) if array.shape == (length,): return array elif array.ndim == 2 and array.shape == (length // 3, 3): return array.reshape(length) else: raise ValueError( f"Array shape {array.shape} not compatible with length {length}." ) # 对 positions/velocities/forces 进行统一处理 positions = ensure_flat(positions, 3 * num_atoms) velocities = ensure_flat(velocities, 3 * num_atoms) forces = ensure_flat(forces, 3 * num_atoms) # masses 和 box_lengths 保持严格要求 if masses.shape != (num_atoms,): raise ValueError( f"Expected masses shape {(num_atoms,)}, got {masses.shape}" ) if box_lengths.shape != (3,): raise ValueError( f"Expected box_lengths shape (3,), got {box_lengths.shape}" ) # 对应力张量同样灵活处理 if stress_tensor.shape == (3, 3): stress_tensor_view = stress_tensor.reshape(9) elif stress_tensor.shape == (9,): stress_tensor_view = stress_tensor else: raise ValueError( f"stress_tensor must be shape (3,3) or (9,), got {stress_tensor.shape}" ) # 确保所有数组是连续和浮点数类型 positions = np.ascontiguousarray(positions, dtype=np.float64) velocities = np.ascontiguousarray(velocities, dtype=np.float64) forces = np.ascontiguousarray(forces, dtype=np.float64) masses = np.ascontiguousarray(masses, dtype=np.float64) box_lengths = np.ascontiguousarray(box_lengths, dtype=np.float64) stress_tensor_view = np.ascontiguousarray(stress_tensor_view, dtype=np.float64) # 使用 pybind11 路径 self._cpp.compute_stress( num_atoms, positions, velocities, forces, masses, volume, box_lengths, stress_tensor_view, ) # 若stress_tensor为(3,3),数据已经更新,因为view共享内存 stress_tensor[:] = stress_tensor_view.reshape(stress_tensor.shape)
[文档] def calculate_lj_forces( self, num_atoms, positions, forces, epsilon, sigma, cutoff, box_lengths, neighbor_pairs, num_pairs, ): """ 调用 C++ 接口计算作用力。 Parameters ---------- num_atoms : int 原子数。 positions : numpy.ndarray 原子位置数组,形状为 (num_atoms, 3)。 forces : numpy.ndarray 力数组,形状为 (num_atoms, 3),将被更新。 epsilon : float Lennard-Jones 势参数 ε,单位 eV。 sigma : float Lennard-Jones 势参数 σ,单位 Å。 cutoff : float 截断距离,单位 Å。 box_lengths : numpy.ndarray 盒子长度数组,形状为 (3,)。 neighbor_pairs : numpy.ndarray 邻居对数组,形状为 (2*num_pairs,)。 num_pairs : int 邻居对的数量。 Returns ------- None """ # 直接调用 pybind11 模块 self._cpp.calculate_lj_forces( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), float(epsilon), float(sigma), float(cutoff), np.ascontiguousarray(box_lengths, dtype=np.float64), np.ascontiguousarray(neighbor_pairs, dtype=np.int32), int(num_pairs), )
[文档] def calculate_lj_energy( self, num_atoms, positions, epsilon, sigma, cutoff, box_lengths, neighbor_pairs, num_pairs, ): """ 调用 C++ 接口计算能量。 Parameters ---------- num_atoms : int 原子数。 positions : numpy.ndarray 原子位置数组,形状为 (num_atoms, 3)。 epsilon : float Lennard-Jones 势参数 ε,单位 eV。 sigma : float Lennard-Jones 势参数 σ,单位 Å。 cutoff : float 截断距离,单位 Å。 box_lengths : numpy.ndarray 盒子长度数组,形状为 (3,)。 neighbor_pairs : numpy.ndarray 邻居对数组,形状为 (2*num_pairs,)。 num_pairs : int 邻居对的数量。 Returns ------- float 总势能,单位 eV。 """ return float( self._cpp.calculate_lj_energy( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), float(epsilon), float(sigma), float(cutoff), np.ascontiguousarray(box_lengths, dtype=np.float64), np.ascontiguousarray(neighbor_pairs, dtype=np.int32), int(num_pairs), ) )
[文档] def calculate_eam_al1_forces( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, forces: np.ndarray, ) -> None: """ 计算EAM Al1势的原子力。 Parameters ---------- num_atoms : int 原子数量 positions : numpy.ndarray 原子位置数组,形状为(num_atoms, 3) lattice_vectors : numpy.ndarray 晶格向量数组,形状为(3, 3)或(9,) forces : numpy.ndarray 输出的力数组,形状为(num_atoms, 3),将被更新 Returns ------- None """ # pybind11路径 self._cpp.calculate_eam_al1_forces( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), )
[文档] def calculate_eam_al1_energy( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray ) -> float: """ 计算EAM Al1势的总能量。 Parameters ---------- num_atoms : int 原子数量 positions : numpy.ndarray 原子位置数组,形状为(num_atoms, 3) lattice_vectors : numpy.ndarray 晶格向量数组,形状为(3, 3)或(9,) Returns ------- float 系统的总能量,单位为eV """ # pybind11路径 return float( self._cpp.calculate_eam_al1_energy( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), ) )
[文档] def calculate_tersoff_energy( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, *, A: float, B: float, lambda1: float, lambda2: float, lambda3: float, beta: float, n: float, c: float, d: float, h: float, R: float, D: float, m: int = 3, shift_flag: bool = False, delta: float = 0.0, ) -> float: """计算Tersoff势能量。""" return float( self._cpp.calculate_tersoff_energy( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), float(A), float(B), float(lambda1), float(lambda2), float(lambda3), float(beta), float(n), float(c), float(d), float(h), float(R), float(D), int(m), bool(shift_flag), float(delta), ) )
[文档] def calculate_tersoff_forces( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, forces: np.ndarray, *, A: float, B: float, lambda1: float, lambda2: float, lambda3: float, beta: float, n: float, c: float, d: float, h: float, R: float, D: float, m: int = 3, shift_flag: bool = False, delta: float = 0.0, ) -> None: """计算Tersoff势力。""" self._cpp.calculate_tersoff_forces( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), float(A), float(B), float(lambda1), float(lambda2), float(lambda3), float(beta), float(n), float(c), float(d), float(h), float(R), float(D), int(m), bool(shift_flag), float(delta), )
[文档] def calculate_tersoff_virial( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, *, A: float, B: float, lambda1: float, lambda2: float, lambda3: float, beta: float, n: float, c: float, d: float, h: float, R: float, D: float, m: int = 3, shift_flag: bool = False, delta: float = 0.0, ) -> np.ndarray: """计算Tersoff势维里张量。""" vir = self._cpp.calculate_tersoff_virial( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), float(A), float(B), float(lambda1), float(lambda2), float(lambda3), float(beta), float(n), float(c), float(d), float(h), float(R), float(D), int(m), bool(shift_flag), float(delta), ) vir = np.ascontiguousarray(vir, dtype=np.float64) return vir.reshape(3, 3)
# 默认 C(1988) 参数版本(不传参数)
[文档] def calculate_tersoff_c1988_energy( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, *, shift_flag: bool = False, delta: float = 0.0, ) -> float: """计算Tersoff C(1988)势能量。""" return float( self._cpp.calculate_tersoff_c1988_energy( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), bool(shift_flag), float(delta), ) )
[文档] def calculate_tersoff_c1988_forces( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, forces: np.ndarray, *, shift_flag: bool = False, delta: float = 0.0, ) -> None: """计算Tersoff C(1988)势力。""" self._cpp.calculate_tersoff_c1988_forces( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), bool(shift_flag), float(delta), )
[文档] def calculate_tersoff_c1988_virial( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, *, shift_flag: bool = False, delta: float = 0.0, ) -> np.ndarray: """计算Tersoff C(1988)势维里张量。""" vir = self._cpp.calculate_tersoff_c1988_virial( int(num_atoms), np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), bool(shift_flag), float(delta), ) vir = np.ascontiguousarray(vir, dtype=np.float64) return vir.reshape(3, 3)
[文档] def calculate_eam_al1_virial( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray ) -> np.ndarray: """计算EAM Al1的维里张量(未除以体积)。返回形状(3,3)。""" pos = np.ascontiguousarray(positions, dtype=np.float64) if pos.ndim == 2 and pos.shape[1] == 3: pos = pos.reshape(-1) lat = np.ascontiguousarray(lattice_vectors, dtype=np.float64) if lat.shape != (9,): lat = lat.reshape(9) if hasattr(self._cpp, "calculate_eam_al1_virial"): vir = self._cpp.calculate_eam_al1_virial(num_atoms, pos, lat) vir = np.ascontiguousarray(vir, dtype=np.float64) else: raise RuntimeError("C++ backend for EAM Al1 virial not available") return vir.reshape(3, 3)
[文档] def calculate_eam_cu1_forces( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray, forces: np.ndarray, ) -> None: """ 计算EAM Cu1势的原子力。 Parameters ---------- num_atoms : int 原子数量 positions : numpy.ndarray 原子位置数组,形状为(num_atoms, 3) lattice_vectors : numpy.ndarray 晶格向量数组,形状为(3, 3)或(9,) forces : numpy.ndarray 输出的力数组,形状为(num_atoms, 3),将被更新 Returns ------- None """ # pybind11路径 self._cpp.calculate_eam_cu1_forces( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), )
[文档] def calculate_eam_cu1_energy( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray ) -> float: """ 计算EAM Cu1势的总能量。 Parameters ---------- num_atoms : int 原子数量 positions : numpy.ndarray 原子位置数组,形状为(num_atoms, 3) lattice_vectors : numpy.ndarray 晶格向量数组,形状为(3, 3)或(9,) Returns ------- float 系统的总能量,单位为eV """ # pybind11路径 return float( self._cpp.calculate_eam_cu1_energy( num_atoms, np.ascontiguousarray(positions, dtype=np.float64), np.ascontiguousarray(lattice_vectors, dtype=np.float64), ) )
[文档] def calculate_eam_cu1_virial( self, num_atoms: int, positions: np.ndarray, lattice_vectors: np.ndarray ) -> np.ndarray: """计算EAM Cu1的维里张量(未除以体积)。返回形状(3,3)。""" pos = np.ascontiguousarray(positions, dtype=np.float64) if pos.ndim == 2 and pos.shape[1] == 3: pos = pos.reshape(-1) lat = np.ascontiguousarray(lattice_vectors, dtype=np.float64) if lat.shape != (9,): lat = lat.reshape(9) if hasattr(self._cpp, "calculate_eam_cu1_virial"): vir = self._cpp.calculate_eam_cu1_virial(num_atoms, pos, lat) vir = np.ascontiguousarray(vir, dtype=np.float64) else: raise RuntimeError("C++ backend for EAM Cu1 virial not available") return vir.reshape(3, 3)
[文档] def nose_hoover( self, dt, num_atoms, masses, velocities, forces, xi_array, Q, target_temperature ): """ 实现 Nose-Hoover 恒温器算法。!!!没有去除质心运动 Parameters ---------- dt : float 时间步长。 num_atoms : int 原子数。 masses : numpy.ndarray 原子质量数组。 velocities : numpy.ndarray 原子速度数组。 forces : numpy.ndarray 原子受力数组。 xi_array : numpy.ndarray Nose-Hoover 热浴变量数组。 Q : float 热浴质量参数。 target_temperature : float 目标温度。 Returns ------- float 更新后的 Nose-Hoover 热浴变量。 """ if not isinstance(xi_array, np.ndarray) or xi_array.size != 1: raise ValueError("xi must be a numpy array with one element.") # pybind11路径 self._cpp.nose_hoover( float(dt), int(num_atoms), np.ascontiguousarray(masses, dtype=np.float64), np.ascontiguousarray(velocities, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), np.ascontiguousarray(xi_array, dtype=np.float64), float(Q), float(target_temperature), ) return xi_array[0]
[文档] def nose_hoover_chain( self, dt, num_atoms, masses, velocities, forces, xi_chain, Q, chain_length, target_temperature, ): """ 实现 Nose-Hoover 链恒温器算法。 Parameters ---------- dt : float 时间步长。 num_atoms : int 原子数。 masses : numpy.ndarray 原子质量数组。 velocities : numpy.ndarray 原子速度数组。 forces : numpy.ndarray 原子受力数组。 xi_chain : numpy.ndarray Nose-Hoover 链的热浴变量数组。 Q : numpy.ndarray 热浴质量参数数组。 chain_length : int Nose-Hoover 链的长度。 target_temperature : float 目标温度。 """ # pybind11路径 self._cpp.nose_hoover_chain( float(dt), int(num_atoms), np.ascontiguousarray(masses, dtype=np.float64), np.ascontiguousarray(velocities, dtype=np.float64), np.ascontiguousarray(forces, dtype=np.float64), np.ascontiguousarray(xi_chain, dtype=np.float64), np.ascontiguousarray(Q, dtype=np.float64), int(chain_length), float(target_temperature), )
[文档] def parrinello_rahman_hoover( self, dt: float, num_atoms: int, masses: np.ndarray, velocities: np.ndarray, forces: np.ndarray, lattice_vectors: np.ndarray, xi: np.ndarray, Q: np.ndarray, total_stress: np.ndarray, # 9 components target_pressure: np.ndarray, # 9 components W: float, ): """执行Parrinello-Rahman-Hoover恒压器积分步骤""" # 检查输入数组的形状 if masses.shape != (num_atoms,): raise ValueError( f"Expected masses shape {(num_atoms,)}, got {masses.shape}" ) if velocities.shape != (num_atoms * 3,): raise ValueError( f"Expected velocities shape {(num_atoms * 3,)}, got {velocities.shape}" ) if forces.shape != (num_atoms * 3,): raise ValueError( f"Expected forces shape {(num_atoms * 3,)}, got {forces.shape}" ) if lattice_vectors.shape != (9,): raise ValueError( f"Expected lattice_vectors shape {(9,)}, got {lattice_vectors.shape}" ) if xi.shape != (9,): raise ValueError(f"Expected xi shape {(9,)}, got {xi.shape}") if Q.shape != (9,): raise ValueError(f"Expected Q shape {(9,)}, got {Q.shape}") if total_stress.shape != (9,): raise ValueError( f"Expected total_stress shape {(9,)}, got {total_stress.shape}" ) if target_pressure.shape != (9,): raise ValueError( f"Expected target_pressure shape {(9,)}, got {target_pressure.shape}" ) # 确保所有数组是连续的并且是浮点类型 masses = np.ascontiguousarray(masses, dtype=np.float64) velocities = np.ascontiguousarray(velocities, dtype=np.float64) forces = np.ascontiguousarray(forces, dtype=np.float64) lattice_vectors = np.ascontiguousarray(lattice_vectors, dtype=np.float64) xi = np.ascontiguousarray(xi, dtype=np.float64) Q = np.ascontiguousarray(Q, dtype=np.float64) total_stress = np.ascontiguousarray(total_stress, dtype=np.float64) target_pressure = np.ascontiguousarray(target_pressure, dtype=np.float64) try: # pybind11路径 self._cpp.parrinello_rahman_hoover( float(dt), int(num_atoms), masses, velocities, forces, lattice_vectors, xi, Q, total_stress, target_pressure, float(W), ) except Exception as e: raise RuntimeError(f"Error in C++ parrinello_rahman_hoover: {e}") from e