Source code for dxtb._src.components.classicals.base

# This file is part of dxtb.
#
# SPDX-Identifier: Apache-2.0
# Copyright (C) 2024 Grimme Group
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Classical contributions (ABC)
=============================

This module contains the abstract base class for all classical (i.e., non-
selfconsistent or non-density-dependent) energy terms.

Every contribution contains a :class:`dxtb.components.base.ComponentCache` that
holds position-independent variables. Therefore, the positions must always be
supplied to the :meth:`get_energy` (or :meth:`get_grad``) method.
"""

from __future__ import annotations

from abc import ABC, abstractmethod

import torch

from dxtb import IndexHelper
from dxtb._src.typing import Any, Tensor

from ..base import Component, ComponentCache

__all__ = ["ClassicalABC", "Classical", "ClassicalCache"]


[docs] class ClassicalABC(ABC): """ Abstract base class for calculation of classical contributions. """
[docs] @abstractmethod def get_cache( self, numbers: Tensor, ihelp: IndexHelper | None = None, **kwargs: Any ) -> ComponentCache: """ Store variables for energy calculation. Parameters ---------- numbers : Tensor Atomic numbers for all atoms in the system (shape: ``(..., nat)``). ihelp : IndexHelper Helper class for indexing. Returns ------- Cache Cache class for storage of variables. Note ---- The cache of a classical contribution does not require ``positions`` as it only becomes useful if ``numbers`` remain unchanged and ``positions`` vary, i.e., during geometry optimization. """
[docs] @abstractmethod def get_energy( self, positions: Tensor, cache: ComponentCache, **kwargs: Any ) -> Tensor: """ Obtain energy of the contribution. Parameters ---------- positions : Tensor Cartesian coordinates of all atoms (shape: ``(..., nat, 3)``). cache : Cache Cache for the parameters. Returns ------- Tensor Atomwise energy contributions. """
[docs] class ClassicalCache(ComponentCache): """ Restart data for individual classical contributions. """ __slots__: list[str] = []
[docs] class Classical(ClassicalABC, Component): """ Base class for calculation of classical contributions. """ label: str """Label for the classical contribution.""" def __init__( self, device: torch.device | None = None, dtype: torch.dtype | None = None, ): super().__init__(device, dtype)
[docs] def get_gradient( self, energy: Tensor, positions: Tensor, grad_outputs: Tensor | None = None, ) -> Tensor: """ Calculates nuclear gradient of a classical energy contribution via PyTorch's autograd engine. Parameters ---------- energy : Tensor Energy that will be differentiated. positions : Tensor Nuclear positions. Needs ``requires_grad=True``. grad_outputs : Tensor | None, optional Vector in the vector-Jacobian product. If ``None``, the vector is initialized to ones. Returns ------- Tensor Nuclear gradient of ``energy``. Raises ------ RuntimeError ``positions`` tensor does not have ``requires_grad=True``. """ if positions.requires_grad is False: raise RuntimeError("Position tensor needs ``requires_grad=True``.") # avoid autograd call if energy is zero (autograd fails anyway) if torch.equal(energy, torch.zeros_like(energy)): return torch.zeros_like(positions) g = torch.ones_like(energy) if grad_outputs is None else grad_outputs (gradient,) = torch.autograd.grad(energy, positions, grad_outputs=g) return gradient