Source code for qmmm_pme.calculators.calculator
#! /usr/bin/env python3
"""A module defining the :class:`Calculator` base class and derived
non-multiscale classes.
"""
from __future__ import annotations
from abc import ABC
from abc import abstractmethod
from dataclasses import astuple
from dataclasses import dataclass
from dataclasses import field
from enum import Enum
from typing import Any
from typing import TYPE_CHECKING
import numpy as np
if TYPE_CHECKING:
from qmmm_pme.interfaces.interface import SoftwareInterface
from qmmm_pme.plugins.plugin import CalculatorPlugin
from qmmm_pme import System
from numpy.typing import NDArray
[docs]class CalculatorType(Enum):
"""Enumeration of types of non-multiscale calculators.
"""
QM = "A QM Calculator."
MM = "An MM Calculator."
ME = "An ME Calculator."
[docs]@dataclass
class Results:
"""A wrapper class for storing the results of a calculation.
:param energy: The energy calculated for the system.
:param forces: The forces calculated for the system.
:components: The components of the energy calculated for the system.
"""
energy: float = 0
forces: NDArray[np.float64] = np.empty(0)
components: dict[str, float] = field(default_factory=dict)
[docs]class ModifiableCalculator(ABC):
"""An abstract :class:`Calculator` base class for interfacing with
plugins.
"""
system: System
_plugins: list[str] = []
[docs] @abstractmethod
def calculate(
self,
return_forces: bool | None = True,
return_components: bool | None = True,
) -> tuple[Any, ...]:
"""Calculate energies and forces for the :class:`System` with
the :class:`Calculator`.
:param return_forces: Whether or not to return forces.
:param return_components: Whether or not to return
the components of the energy.
:return: The energy, forces, and energy components of the
calculation.
"""
[docs] def register_plugin(self, plugin: CalculatorPlugin) -> None:
"""Register a :class:`Plugin` modifying a :class:`Calculator`
routine.
:param plugin: An :class:`CalculatorPlugin` object.
"""
self._plugins.append(type(plugin).__name__)
plugin.modify(self)
[docs] def active_plugins(self) -> list[str]:
"""Get the current list of active plugins.
:return: A list of the active plugins being employed by the
:class:`Calculator`.
"""
return self._plugins
[docs]@dataclass
class StandaloneCalculator(ModifiableCalculator):
"""A :class:`Calculator` class, defining the procedure for
standalone QM or MM calculations.
:param system: |system| to perform calculations on.
:param interface: |interface| to perform calculations with.
:param options: Options to provide to the
:class:`SoftwareInterface`.
"""
system: System
interface: SoftwareInterface
options: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
"""Send notifier functions from the interface to the respective
state or topology variable for monitoring, immediately after
initialization.
"""
state_generator = self.interface.get_state_notifiers().items()
for state_key, state_value in state_generator:
getattr(self.system.state, state_key).register_notifier(
state_value,
)
topology_generator = self.interface.get_topology_notifiers().items()
for topology_key, topology_value in topology_generator:
getattr(self.system.topology, topology_key).register_notifier(
topology_value,
)
[docs] def calculate(
self,
return_forces: bool | None = True,
return_components: bool | None = True,
) -> tuple[Any, ...]:
energy = self.interface.compute_energy(**self.options)
results = Results(energy)
if return_forces:
forces = self.interface.compute_forces(**self.options)
results.forces = forces
if return_components:
components = self.interface.compute_components(**self.options)
results.components = components
return astuple(results)