Source code for pydft_qmmm.plugins.centroid_partition.centroid_partition

"""A plugin for performing residue-wise system partitioning by centroid.
"""
from __future__ import annotations

from typing import Callable
from typing import TYPE_CHECKING

import numpy as np

from pydft_qmmm.common import Subsystem
from pydft_qmmm.plugins.plugin import PartitionPlugin

if TYPE_CHECKING:
    from pydft_qmmm.calculators import CompositeCalculator
    from pydft_qmmm.common import Results
    import mypy_extensions
    CalculateMethod = Callable[
        [
            mypy_extensions.DefaultArg(
                bool | None,
                "return_forces",  # noqa: F821
            ),
            mypy_extensions.DefaultArg(
                bool | None,
                "return_components",  # noqa: F821
            ),
        ],
        Results,
    ]


[docs] class CentroidPartition(PartitionPlugin): """Partition subsystems residue-wise according to centroid. Args: query: The VMD-like query representing the group of atoms whose subsystem membership will be determined on an residue-wise basis. """ def __init__( self, query: str, ) -> None: self._query = query
[docs] def modify( self, calculator: CompositeCalculator, ) -> None: """Modify the functionality of a calculator. Args: calculator: The calculator whose functionality will be modified by the plugin. """ self._modifieds.append(type(calculator).__name__) self.system = calculator.system self.cutoff = calculator.cutoff calculator.calculate = self._modify_calculate( calculator.calculate, )
def _modify_calculate( self, calculate: CalculateMethod, ) -> CalculateMethod: """Modify the calculate routine to perform residue-wise partitioning. Args: calculate: The calculation routine to modify. Returns: The modified calculation routine which implements residue-wise partitioning according to centroid before calculation. """ def inner( return_forces: bool | None = True, return_components: bool | None = True, ) -> Results: self.generate_partition() results = calculate(return_forces, return_components) return results return inner
[docs] def generate_partition(self) -> None: """Perform the residue-wise system partitioning. """ qm_region = sorted(self.system.select("subsystem I")) qm_centroid = np.average( self.system.positions[qm_region, :], axis=0, ) region_ii: list[int] = [] selection = self.system.select(self._query) for residue in self.system.residue_map.values(): atoms = sorted(residue & selection) not_qm = set(qm_region).isdisjoint(set(atoms)) if not_qm and atoms: nth_centroid = np.average( self.system.positions[atoms, :], axis=0, ) r_vector = nth_centroid - qm_centroid distance = np.sum(r_vector**2)**0.5 if distance < self.cutoff: region_ii.extend(atoms) # Update the topology with the current embedding atoms. temp = [Subsystem.III]*len(self.system) temp = [ Subsystem.II if i in region_ii else x for i, x in enumerate(temp) ] temp = [ Subsystem.I if i in qm_region else x for i, x in enumerate(temp) ] self.system.subsystems = np.array(temp)