Source code for pydft_qmmm.plugins.plumed.plumed

"""A plugin interface to the Plumed enhanced sampling suite.
"""
from __future__ import annotations

from typing import Callable
from typing import TYPE_CHECKING

import numpy as np

from pydft_qmmm.common import lazy_load
from pydft_qmmm.plugins.plugin import CalculatorPlugin

if TYPE_CHECKING:
    from qmmm_pme.calculators import Calculator
    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 Plumed(CalculatorPlugin): """Apply enhanced sampling biases to energy and force calculations. Args: input_commands: A multi-line string containing all pertinent instructions for Plumed. log_file: A directory for recording output from Plumed. """ def __init__( self, input_commands: str, log_file: str, ) -> None: plumed = lazy_load("plumed") self.input_commands = input_commands self.log_file = log_file self.plumed = plumed.Plumed() self.plumed.cmd("setMDEngine", "python") self.frame = 0
[docs] def modify( self, calculator: Calculator, ) -> None: """Modify the functionality of a calculator and set up Plumed. Args: calculator: The calculator whose functionality will be modified by the plugin. """ self._modifieds.append(type(calculator).__name__) self.system = calculator.system self.plumed.cmd("setNatoms", len(self.system)) self.plumed.cmd("setMDLengthUnits", 1/10) self.plumed.cmd("setMDTimeUnits", 1/1000) self.plumed.cmd("setMDMassUnits", 1.) self.plumed.cmd("setTimestep", 1.) self.plumed.cmd("setKbT", 1.) self.plumed.cmd("setLogFile", self.log_file) self.plumed.cmd("init") for line in self.input_commands.split("\n"): self.plumed.cmd("readInputLine", line) calculator.calculate = self._modify_calculate(calculator.calculate)
def _modify_calculate( self, calculate: CalculateMethod, ) -> CalculateMethod: """Modify the calculate routine to perform biasing afterward. Args: calculate: The calculation routine to modify. Returns: The modified calculation routine which implements Plumed enhanced sampling after performing the unbiased calculation routine. """ def inner( return_forces: bool | None = True, return_components: bool | None = True, ) -> Results: results = calculate(return_forces, return_components) self.plumed.cmd("setStep", self.frame) self.frame += 1 self.plumed.cmd("setBox", self.system.box.T) self.plumed.cmd("setPositions", self.system.positions) self.plumed.cmd("setEnergy", results.energy) self.plumed.cmd("setMasses", self.system.masses) biased_forces = np.zeros(self.system.positions.shape) self.plumed.cmd("setForces", biased_forces) virial = np.zeros((3, 3)) self.plumed.cmd("setVirial", virial) self.plumed.cmd("prepareCalc") self.plumed.cmd("performCalc") biased_energy = np.zeros((1,)) self.plumed.cmd("getBias", biased_energy) results.energy += biased_energy[0] results.forces += biased_forces results.components.update( {"Plumed Bias Energy": biased_energy[0]}, ) return results return inner