Source code for pyretis.simulation.md_simulation

# -*- coding: utf-8 -*-
# Copyright (c) 2023, PyRETIS Development Team.
# Distributed under the LGPLv2.1+ License. See LICENSE for more info.
"""Definitions of simulation objects for molecular dynamics simulations.

This module contains definitions of classes for performing molecular
dynamics simulations.

Important classes defined here
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SimulationNVE (:py:class:`.SimulationNVE`)
    Definition of a simple NVE simulation. The engine
    used for this simulation must have dynamics equal to NVE.

SimulationMD (:py:class:`.SimulationMD`)
    Definition of a simulation for running somply MD.

SimulationMDFlux (:py:class:`.SimulationMDFlux`)
    Definition of a simulation for determining the initial flux.
    This is used for calculating rates in TIS simulations.
"""
import logging
from pyretis.simulation.simulation import Simulation
from pyretis.core.particlefunctions import calculate_thermo
from pyretis.core.path import check_crossing
logger = logging.getLogger(__name__)  # pylint: disable=invalid-name
logger.addHandler(logging.NullHandler())


__all__ = [
    'SimulationMD',
    'SimulationNVE',
    'SimulationMDFlux'
]


[docs]class SimulationMD(Simulation): """A generic MD simulation. This class is used to define a MD simulation without whistles and bells. """ simulation_type = 'md' simulation_output = [ {'type': 'energy', 'name': 'md-energy-file'}, {'type': 'thermo-file', 'name': 'md-thermo-file'}, {'type': 'traj-xyz', 'name': 'md-traj-file'}, {'type': 'thermo-screen', 'name': 'md-thermo-screen'}, {'type': 'order', 'name': 'md-order-file'}, ]
[docs] def __init__(self, ensemble, settings=None, controls=None): """Only add variable. Parameters ---------- ensemble : dict It contains the simulations info * `system` : object like :py:class:`.System` This is the system we are investigating. * `engine` : object like :py:class:`.EngineBase` This is the integrator that is used to propagate the system in time. * `order_function` : object like :py:class:`.OrderParameter`, optional. A class that can be used to calculate an order parameter, if needed. settings : dict, optional This dictionary contains the settings for the simulation. controls: dict of parameters, optional It contains: * `steps` : int, optional The number of simulation steps to perform. * `startcycle` : int, optional The cycle we start the simulation on, can be useful if restarting. """ super().__init__(settings, controls) self.engine = ensemble['engine'] self.system = ensemble['system'] self.order_function = ensemble.get('order_function') self.system.potential_and_force() # make sure forces are defined. self.ensemble = ensemble
[docs] def run(self): """Run the MD simulation. Yields ------ results : dict The results from a single step in the simulation. """ nsteps = 1 + self.cycle['endcycle'] - self.cycle['step'] integ = self.engine.integrate({'system': self.system, 'order_function': self.order_function}, nsteps, thermo='full') for step in integ: if not self.first_step: self.cycle['step'] += 1 self.cycle['stepno'] += 1 results = {'cycle': self.cycle.copy()} results.update(self.execute_tasks()) if self.first_step: self.first_step = False results.update(step) for task in self.output_tasks: task.output(results) self.write_restart() if self.soft_exit(): yield results break yield results
[docs] def __str__(self): """Return a string with info about the simulation.""" msg = ['MD simulation'] nstep = self.cycle['endcycle'] - self.cycle['startcycle'] msg += [f'Number of steps to do: {nstep}'] msg += [f'Molecular Dynamics engine: {self.engine}'] msg += [f'Time step: {self.engine.timestep}'] return '\n'.join(msg)
[docs]class SimulationNVE(SimulationMD): """A MD NVE simulation class. This class is used to define a NVE simulation. Compared with the :py:class:`.SimulationMD` we here require that the engine supports NVE dynamics. """ simulation_type = 'md-nve' simulation_output = [ {'type': 'energy', 'name': 'nve-energy-file'}, {'type': 'thermo-file', 'name': 'nve-thermo-file'}, {'type': 'traj-xyz', 'name': 'nve-traj-file'}, {'type': 'thermo-screen', 'name': 'nve-thermo-screen'}, {'type': 'order', 'name': 'nve-order-file'}, ]
[docs] def __init__(self, ensemble, settings=None, controls=None): """Initialise the NVE simulation object. Here we will set up the tasks that are to be performed in the simulation, such as the integration and thermodynamics calculation(s). Parameters ---------- ensemble : dict It contains the simulations info * `system` : object like :py:class:`.System` This is the system we are investigating. * `engine` : object like :py:class:`.EngineBase` This is the integrator that is used to propagate the system in time. * `order_function` : object like :py:class:`.OrderParameter`, opt A class that can be used to calculate an order parameter, if needed. settings : dict, optional This dictionary contains the settings for the simulation. controls: dict of parameters, optional It contains: * `steps` : int, optional The number of simulation steps to perform. * `startcycle` : int, optional The cycle we start the simulation on. """ super().__init__(ensemble, settings, controls) dyn = getattr(self.engine, "dynamics", "") if dyn.lower() != 'nve': logger.warning( 'MD integrator %s (%s) might not be suitable' ' for NVE dynamics! Please make sure this is intentional.', self.engine.__class__, self.engine.description )
[docs] def step(self): """Run a single simulation step.""" system = self.ensemble['system'] if self.first_step: system.potential_and_force() self.first_step = False else: self.cycle['step'] += 1 self.cycle['stepno'] += 1 self.engine.integration_step(system) results = {'cycle': self.cycle.copy(), 'thermo': calculate_thermo(system), 'system': system} if self.order_function: results['order'] = self.engine.calculate_order(self.ensemble) return results
[docs] def __str__(self): """Return a string with info about the simulation.""" msg = ['NVE simulation'] nstep = self.cycle['endcycle'] - self.cycle['startcycle'] msg += [f'Number of steps to do: {nstep}'] msg += [f'MD engine: {self.engine}'] msg += [f'Time step: {self.engine.timestep}'] return '\n'.join(msg)
[docs] def restart_info(self): """Return information which can be used to restart the simulation. Returns ------- info : dict, Contains all the updated simulation settings and counters. """ info = super().restart_info() info['engine'] = self.engine.restart_info() return info
[docs] def load_restart_info(self, info): """Load the restart information.""" super().load_restart_info(info) self.engine.load_restart_info(info['engine'])
[docs]class SimulationMDFlux(SimulationMD): """A simulation for obtaining the initial flux for TIS. This class is used to define a MD simulation where the goal is to calculate crossings in order to obtain the initial flux for a TIS calculation. """ simulation_type = 'md-flux' simulation_output = [ {'type': 'energy', 'name': 'flux-energy-file'}, {'type': 'traj-xyz', 'name': 'flux-traj-file'}, {'type': 'thermo-screen', 'name': 'flux-thermo-screen'}, {'type': 'order', 'name': 'flux-order-file'}, {'type': 'cross', 'name': 'flux-cross-file'}, ]
[docs] def __init__(self, ensemble, settings=None, controls=None): """Initialise the MD-Flux simulation object. Parameters ---------- ensemble : dict It contains the simulations info * `system` : object like :py:class:`.System` The system to act on. * `engine` : object like :py:class:`.EngineBase` This is the integrator that is used to propagate the system in time. * `order_function` : object like :py:class:`.OrderParameter` The class used for calculating the order parameters. settings : dict, optional This dictionary contains the settings for the simulation. controls: dict of parameters, optional It contains: * `steps` : int, optional The number of simulation steps to perform. * `startcycle` : int, optional The cycle we start the simulation on, can be useful if restarting. """ super().__init__(ensemble, settings, controls) # set up for initial crossing self.leftside_prev = None self.interfaces = settings['simulation'].get('interfaces')
[docs] def run(self): """Run the MD simulation. Yields ------ results : dict The results from a single step in the simulation. """ nsteps = 1 + self.cycle['endcycle'] - self.cycle['step'] leftside = None integ = self.engine.integrate( {'system': self.system, 'order_function': self.order_function}, nsteps, thermo='full', ) for step in integ: if not self.first_step: self.cycle['step'] += 1 self.cycle['stepno'] += 1 else: self.first_step = False results = {'cycle': self.cycle.copy()} if leftside: self.leftside_prev = leftside leftside, cross = check_crossing(self.cycle['step'], step['order'][0], self.interfaces, self.leftside_prev) results['cross'] = cross results.update(step) for task in self.output_tasks: task.output(results) self.write_restart() if self.soft_exit(): yield results break yield results
[docs] def __str__(self): """Return a string with info about the simulation.""" msg = ['MD-flux simulation'] nstep = self.cycle['endcycle'] - self.cycle['startcycle'] msg += [f'Number of steps to do: {nstep}'] msg += [f'Molecular Dynamics engine: {self.engine}'] msg += [f'Time step: {self.engine.timestep}'] return '\n'.join(msg)
[docs] def restart_info(self): """Return information which can be used to restart the simulation. Returns ------- info : dict Contains all the updated simulation settings and counters. """ info = super().restart_info() info['engine'] = self.engine.restart_info() info['leftside_prev'] = self.leftside_prev return info
[docs] def load_restart_info(self, info): """Load the restart information.""" super().load_restart_info(info) self.leftside_prev = info['leftside_prev'] self.engine.load_restart_info(info['engine'])