Source code for ufp.terms.zbl
"""ZBL screened nuclear repulsion term."""
from __future__ import annotations
from collections.abc import Sequence
import torch
from ufp.core.input import UFPInput
from ufp.core.output import UFPOutput
from ufp.terms._base import PairTerm
from ufp.terms._shared import (
accumulate_pair_energy_forces,
empty_atomwise_output,
pair_weight,
)
_COULOMB_EV_ANGSTROM = 14.3996454784255
_SCREENING_LENGTH_FACTOR = 0.46850
_ZBL_COEFFS = (0.1818, 0.5099, 0.2802, 0.02817)
_ZBL_EXPONENTS = (3.2, 0.9423, 0.4029, 0.2016)
[docs]
class ZBLRepulsionTerm(PairTerm):
"""
Analytic Ziegler-Biersack-Littmark screened nuclear repulsion.
"""
def __init__(
self,
*,
cutoff: float,
atomic_types: Sequence[int] | None = None,
eps: float = 1.0e-12,
) -> None:
"""Store a fast non-fittable pair repulsion term."""
super().__init__(cutoff=cutoff, atomic_types=atomic_types)
self.eps = float(eps)
@property
def provides_forces(self) -> bool:
"""Report that this term produces analytic pair forces."""
return True
[docs]
def forward(self, inputs: UFPInput) -> UFPOutput:
"""Evaluate ZBL energy and force contributions over covered pairs."""
if inputs.neighbor_list is None:
raise RuntimeError("ZBLRepulsionTerm requires a neighbor list")
pair_mask = None
if self.atomic_types is not None:
pair_category = inputs.pair_category_indices(
self.atomic_types,
symmetric=True,
)
pair_mask = pair_category >= 0
if not torch.any(pair_mask):
return empty_atomwise_output(inputs, forces=True)
first_z, second_z = inputs.pair_atomic_numbers(pair_mask)
distances = inputs.pair_distances(pair_mask).clamp_min(self.eps)
dtype = inputs.dtype
device = inputs.device
z1 = first_z.to(dtype=dtype, device=device)
z2 = second_z.to(dtype=dtype, device=device)
screening = _SCREENING_LENGTH_FACTOR / (
torch.pow(z1, 0.23) + torch.pow(z2, 0.23)
)
scaled = distances / screening
coeffs = torch.tensor(_ZBL_COEFFS, dtype=dtype, device=device)
exponents = torch.tensor(_ZBL_EXPONENTS, dtype=dtype, device=device)
exp_terms = torch.exp(-scaled[:, None] * exponents[None, :])
phi = torch.sum(coeffs[None, :] * exp_terms, dim=1)
dphi_dr = (
-torch.sum(
coeffs[None, :] * exponents[None, :] * exp_terms,
dim=1,
)
/ screening
)
prefactor = _COULOMB_EV_ANGSTROM * z1 * z2
scale = pair_weight(inputs)
weighted_energy = scale * prefactor * phi / distances
weighted_grad = (
scale * prefactor * (dphi_dr / distances - phi / (distances * distances))
)
return accumulate_pair_energy_forces(
inputs,
pair_mask,
weighted_pair_energy=weighted_energy,
weighted_pair_grad=weighted_grad,
eps=self.eps,
)
__all__ = [
"ZBLRepulsionTerm",
]