Source code for pydft_qmmm.interfaces.interface

"""Base classes for software interfaces and related classes.

This module contains the software interface base class and the base
classes for QM and MM interfaces, as well as the MM and QM potential
and factory types.
"""
from __future__ import annotations

__all__ = [
    "SoftwareInterface",
    "QMInterface",
    "QMPotential",
    "QMFactory",
    "MMInterface",
    "MMPotential",
    "MMFactory",
]

from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from dataclasses import field
from collections.abc import Callable
from typing import TypeAlias
from typing import TYPE_CHECKING

# These are required outside of the TYPE_CHECKING guard in order to
# produce correct docs.
import numpy as np
from numpy.typing import NDArray

from pydft_qmmm.utils import TheoryLevel
from pydft_qmmm.potentials import AtomicPotential

if TYPE_CHECKING:
    from pydft_qmmm import System
    from pydft_qmmm.potentials import ElectronicPotential


[docs] @dataclass(frozen=True) class SoftwareInterface(ABC): """The abstract software interface base class. Args: system: The system that will inform the interface to the external software. """ system: System
[docs] @dataclass(frozen=True) class MMInterface(SoftwareInterface): """The abstract MM interface base class. Args: system: The system that will inform the interface to the external software. Attributes: theory_level: The level of theory that the software applies in energy and force calculations. """ theory_level: TheoryLevel = field(default=TheoryLevel.MM, init=False)
[docs] @abstractmethod def zero_intramolecular(self, atoms: frozenset[int]) -> None: """Remove intra-molecular interactions for the specified atoms. Args: atoms: The indices of atoms to from which to remove intra-molecular interactions. """
[docs] @abstractmethod def zero_charges(self, atoms: frozenset[int]) -> None: """Remove charges from the specified atoms. Args: atoms: The indices of atoms from which to remove charges. """
[docs] @abstractmethod def zero_intermolecular(self, atoms: frozenset[int]) -> None: """Remove inter-molecular interactions for the specified atoms. Args: atoms: The indices of atoms from which to remove inter-molecular interactions. """
[docs] @abstractmethod def zero_forces(self, atoms: frozenset[int]) -> None: """Zero forces on the specified atoms. Args: atoms: The indices of atoms for which to zero forces. """
[docs] @abstractmethod def add_real_elst( self, atoms: frozenset[int], const: float | int = 1, inclusion: NDArray[np.float64] | None = None, ) -> None: """Add Coulomb interaction for the specified atoms. Args: atoms: The indices of atoms for which to add a Coulomb interaction. const: A constant to multiply at the beginning of the coulomb expression. inclusion: An Nx3 array with values that will be applied to the forces of the Coulomb interaction through element-wise multiplication. """
[docs] @abstractmethod def add_non_elst( self, atoms: frozenset[int], inclusion: NDArray[np.float64] | None = None, ) -> None: """Add a non-electrostatic interaction for the specified atoms. Args: atoms: The indices of atoms for which to add non-electrostatic, non-bonded interactions. inclusion: An Nx3 array with values that will be applied to the forces of the non-electrostatic interaction through element-wise multiplication. """
[docs] @abstractmethod def get_pme_parameters(self) -> tuple[float, tuple[int, int, int], int]: r"""Get the parameters used for PME summation. Returns: The Gaussian width parameter in Ewald summation (:math:`\mathrm{\mathring(A)^{-1}}`), the number of grid points to include along each lattice edge, and the order of splines used on the FFT grid. """
[docs] @dataclass(frozen=True) class QMInterface(SoftwareInterface): """The abstract QM interface base class. Args: system: The system that will inform the interface to the external software. Attributes: theory_level: The level of theory that the software applies in energy and force calculations. """ theory_level: TheoryLevel = field(default=TheoryLevel.QM, init=False)
[docs] @abstractmethod def add_electronic_potential(self, potential: ElectronicPotential) -> None: """Add an electronic potential to apply before calculations. Args: potential: The electronic potential to incorporate into QM calculations. """
[docs] class MMPotential(AtomicPotential, MMInterface): """A subclass of MM interface and potential for typing purposes. """
[docs] class QMPotential(AtomicPotential, QMInterface): """A subclass of QM interface and potential for typing purposes. """
MMFactory: TypeAlias = Callable[..., MMPotential] QMFactory: TypeAlias = Callable[..., QMPotential]