"""Contains the classes that deal with the different dynamics required in different types of ensembles. Copyright (C) 2013, Joshua More and Michele Ceriotti This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Holds the algorithms required for normal mode propagators, and the objects to do the constant temperature and pressure algorithms. Also calculates the appropriate conserved energy quantity for the ensemble of choice. Classes: Ensemble: Base ensemble class with generic methods and attributes. NVEEnsemble: Deals with constant energy dynamics. NVTEnsemble: Deals with constant temperature dynamics. NPTEnsemble: Deals with constant pressure dynamics. ReplayEnsemble: Takes a trajectory, and simply sets the atom positions to match it, rather than doing dynamics. In this way new properties can be calculated on an old simulation, without having to rerun it from scratch. """ __all__ = ['Ensemble', 'NVEEnsemble', 'NVTEnsemble', 'NPTEnsemble', 'ReplayEnsemble'] import numpy as np import time from ipi.utils.depend import * from ipi.utils import units from ipi.utils.softexit import softexit from ipi.utils.io.io_xyz import read_xyz from ipi.utils.io.io_pdb import read_pdb from ipi.utils.io.io_xml import xml_parse_file from ipi.utils.units import Constants, unit_to_internal from ipi.inputs.thermostats import InputThermo from ipi.inputs.barostats import InputBaro from ipi.engine.thermostats import * from ipi.engine.barostats import * class Ensemble(dobject): """Base ensemble class. Gives the standard methods and attributes needed in all the ensemble classes. Attributes: beads: A beads object giving the atoms positions. cell: A cell object giving the system box. forces: A forces object giving the virial and the forces acting on each bead. prng: A random number generator object. nm: An object which does the normal modes transformation. fixcom: A boolean which decides whether the centre of mass motion will be constrained or not. Depend objects: econs: The conserved energy quantity appropriate to the given ensemble. Depends on the various energy terms which make it up, which are different depending on the ensemble. temp: The system temperature. dt: The timestep for the algorithms. ntemp: The simulation temperature. Will be nbeads times higher than the system temperature as PIMD calculations are done at this effective classical temperature. """ def __init__(self, dt, temp, fixcom=False): """Initialises Ensemble. Args: dt: The timestep of the simulation algorithms. temp: The temperature. fixcom: An optional boolean which decides whether the centre of mass motion will be constrained or not. Defaults to False. """ dset(self, "econs", depend_value(name='econs', func=self.get_econs)) dset(self, "temp", depend_value(name='temp', value=temp)) dset(self, "dt", depend_value(name='dt', value=dt)) self.fixcom = fixcom def bind(self, beads, nm, cell, bforce, prng): """Binds beads, cell, bforce and prng to the ensemble. This takes a beads object, a cell object, a forcefield object and a random number generator object and makes them members of the ensemble. It also then creates the objects that will hold the data needed in the ensemble algorithms and the dependency network. Note that the conserved quantity is defined in the init, but as each ensemble has a different conserved quantity the dependencies are defined in bind. Args: beads: The beads object from whcih the bead positions are taken. nm: A normal modes object used to do the normal modes transformation. cell: The cell object from which the system box is taken. bforce: The forcefield object from which the force and virial are taken. prng: The random number generator object which controls random number generation. """ # store local references to the different bits of the simulation self.beads = beads self.cell = cell self.forces = bforce self.prng = prng self.nm = nm # n times the temperature dset(self,"ntemp", depend_value(name='ntemp',func=self.get_ntemp, dependencies=[dget(self,"temp")])) # dependencies of the conserved quantity dget(self,"econs").add_dependency(dget(self.beads, "kin")) dget(self,"econs").add_dependency(dget(self.forces, "pot")) dget(self,"econs").add_dependency(dget(self.beads, "vpath")) def get_ntemp(self): """Returns the PI simulation temperature (P times the physical T).""" return self.temp*self.beads.nbeads def pstep(self): """Dummy momenta propagator which does nothing.""" pass def qcstep(self): """Dummy centroid position propagator which does nothing.""" pass def step(self): """Dummy simulation time step which does nothing.""" pass def get_econs(self): """Calculates the conserved energy quantity for constant energy ensembles. """ return self.beads.vpath*self.nm.omegan2 + self.nm.kin + self.forces.pot class NVEEnsemble(Ensemble): """Ensemble object for constant energy simulations. Has the relevant conserved quantity and normal mode propagator for the constant energy ensemble. Note that a temperature of some kind must be defined so that the spring potential can be calculated. Attributes: ptime: The time taken in updating the velocities. qtime: The time taken in updating the positions. ttime: The time taken in applying the thermostat steps. Depend objects: econs: Conserved energy quantity. Depends on the bead kinetic and potential energy, and the spring potential energy. """ def __init__(self, dt, temp, fixcom=False): """Initialises NVEEnsemble. Args: dt: The simulation timestep. temp: The system temperature. fixcom: An optional boolean which decides whether the centre of mass motion will be constrained or not. Defaults to False. """ super(NVEEnsemble,self).__init__(dt=dt,temp=temp, fixcom=fixcom) def rmcom(self): """This removes the centre of mass contribution to the kinetic energy. Calculates the centre of mass momenta, then removes the mass weighted contribution from each atom. If the ensemble defines a thermostat, then the contribution to the conserved quantity due to this subtraction is added to the thermostat heat energy, as it is assumed that the centre of mass motion is due to the thermostat. If there is a choice of thermostats, the thermostat connected to the centroid is chosen. """ if (self.fixcom): pcom = np.zeros(3,float); na3 = self.beads.natoms*3 nb = self.beads.nbeads p = depstrip(self.beads.p) m = depstrip(self.beads.m3)[:,0:na3:3] M = self.beads[0].M for i in range(3): pcom[i] = p[:,i:na3:3].sum() if hasattr(self,"thermostat"): if hasattr(self.thermostat, "_thermos"): self.thermostat._thermos[0].ethermo += np.dot(pcom,pcom)/(2.0*M*nb) else: self.thermostat.ethermo += np.dot(pcom,pcom)/(2.0*M*nb) # subtracts COM _velocity_ pcom *= 1.0/(nb*M) for i in range(3): self.beads.p[:,i:na3:3] -= m*pcom[i] def pstep(self): """Velocity Verlet momenta propagator.""" self.beads.p += depstrip(self.forces.f)*(self.dt*0.5) def qcstep(self): """Velocity Verlet centroid position propagator.""" self.nm.qnm[0,:] += depstrip(self.nm.pnm)[0,:]/depstrip(self.beads.m3)[0]*self.dt def step(self): """Does one simulation time step.""" self.ptime = -time.time() self.pstep() self.ptime += time.time() self.qtime = -time.time() self.qcstep() self.nm.free_qstep() self.qtime += time.time() self.ptime -= time.time() self.pstep() self.ptime += time.time() self.ttime = -time.time() self.rmcom() self.ttime += time.time() class NVTEnsemble(NVEEnsemble): """Ensemble object for constant temperature simulations. Has the relevant conserved quantity and normal mode propagator for the constant temperature ensemble. Contains a thermostat object containing the algorithms to keep the temperature constant. Attributes: thermostat: A thermostat object to keep the temperature constant. Depend objects: econs: Conserved energy quantity. Depends on the bead kinetic and potential energy, the spring potential energy and the heat transferred to the thermostat. """ def __init__(self, dt, temp, thermostat=None, fixcom=False): """Initialises NVTEnsemble. Args: dt: The simulation timestep. temp: The system temperature. thermostat: A thermostat object to keep the temperature constant. Defaults to Thermostat() fixcom: An optional boolean which decides whether the centre of mass motion will be constrained or not. Defaults to False. """ super(NVTEnsemble,self).__init__(dt=dt,temp=temp, fixcom=fixcom) if thermostat is None: self.thermostat = Thermostat() else: self.thermostat = thermostat def bind(self, beads, nm, cell, bforce, prng): """Binds beads, cell, bforce and prng to the ensemble. This takes a beads object, a cell object, a forcefield object and a random number generator object and makes them members of the ensemble. It also then creates the objects that will hold the data needed in the ensemble algorithms and the dependency network. Also note that the thermostat timestep and temperature are defined relative to the system temperature, and the the thermostat temperature is held at the higher simulation temperature, as is appropriate. Args: beads: The beads object from whcih the bead positions are taken. nm: A normal modes object used to do the normal modes transformation. cell: The cell object from which the system box is taken. bforce: The forcefield object from which the force and virial are taken. prng: The random number generator object which controls random number generation. """ super(NVTEnsemble,self).bind(beads, nm, cell, bforce, prng) fixdof = None if self.fixcom: fixdof = 3 # first makes sure that the thermostat has the correct temperature, then proceed with binding it. deppipe(self,"ntemp", self.thermostat,"temp") deppipe(self,"dt", self.thermostat, "dt") #decides whether the thermostat will work in the normal mode or #the bead representation. if isinstance(self.thermostat,ThermoNMGLE) or isinstance(self.thermostat,ThermoNMGLEG) or isinstance(self.thermostat,ThermoPILE_L) or isinstance(self.thermostat,ThermoPILE_G): self.thermostat.bind(nm=self.nm,prng=prng,fixdof=fixdof ) else: self.thermostat.bind(beads=self.beads,prng=prng, fixdof=fixdof) dget(self,"econs").add_dependency(dget(self.thermostat, "ethermo")) def step(self): """Does one simulation time step.""" self.ttime = -time.time() self.thermostat.step() self.rmcom() self.ttime += time.time() self.ptime = -time.time() self.pstep() self.ptime += time.time() self.qtime = -time.time() self.qcstep() self.nm.free_qstep() self.qtime += time.time() self.ptime -= time.time() self.pstep() self.ptime += time.time() self.ttime -= time.time() self.thermostat.step() self.rmcom() self.ttime += time.time() def get_econs(self): """Calculates the conserved energy quantity for constant temperature ensemble. """ return NVEEnsemble.get_econs(self) + self.thermostat.ethermo class NPTEnsemble(NVTEnsemble): """Ensemble object for constant pressure simulations. Has the relevant conserved quantity and normal mode propagator for the constant pressure ensemble. Contains a thermostat object containing the algorithms to keep the temperature constant, and a barostat to keep the pressure constant. Attributes: barostat: A barostat object to keep the pressure constant. Depend objects: econs: Conserved energy quantity. Depends on the bead and cell kinetic and potential energy, the spring potential energy, the heat transferred to the beads and cell thermostat, the temperature and the cell volume. pext: External pressure. """ def __init__(self, dt, temp, pext, thermostat=None, barostat=None, fixcom=False): """Initialises NPTEnsemble. Args: dt: The simulation timestep. temp: The system temperature. pext: The external pressure. thermostat: A thermostat object to keep the temperature constant. Defaults to Thermostat(). barostat: A barostat object to keep the pressure constant. Defaults to Barostat(). fixcom: An optional boolean which decides whether the centre of mass motion will be constrained or not. Defaults to False. """ super(NPTEnsemble,self).__init__(dt, temp, thermostat, fixcom=fixcom) if barostat == None: self.barostat = Barostat() else: self.barostat = barostat dset(self,"pext",depend_value(name='pext')) if not pext is None: self.pext = pext else: self.pext = 0.0 def bind(self, beads, nm, cell, bforce, prng): """Binds beads, cell, bforce and prng to the ensemble. This takes a beads object, a cell object, a forcefield object and a random number generator object and makes them members of the ensemble. It also then creates the objects that will hold the data needed in the ensemble algorithms and the dependency network. Also note that the cell thermostat timesteps and temperatures are defined relative to the system temperature, and the the thermostat temperatures are held at the higher simulation temperature, as is appropriate. Args: beads: The beads object from whcih the bead positions are taken. nm: A normal modes object used to do the normal modes transformation. cell: The cell object from which the system box is taken. bforce: The forcefield object from which the force and virial are taken. prng: The random number generator object which controls random number generation. """ fixdof = None if self.fixcom: fixdof = 3 super(NPTEnsemble,self).bind(beads, nm, cell, bforce, prng) self.barostat.bind(beads, nm, cell, bforce, prng=prng, fixdof=fixdof) deppipe(self,"ntemp", self.barostat, "temp") deppipe(self,"dt", self.barostat, "dt") deppipe(self,"pext", self.barostat, "pext") dget(self,"econs").add_dependency(dget(self.barostat, "ebaro")) def get_econs(self): """Calculates the conserved energy quantity for the constant pressure ensemble. """ return NVTEnsemble.get_econs(self) + self.barostat.ebaro def step(self): """NPT time step. Note that the barostat only propagates the centroid coordinates. If this approximation is made a centroid virial pressure and stress estimator can be defined, so this gives the best statistical convergence. This is allowed as the normal mode propagation is approximately unaffected by volume fluctuations as long as the system box is much larger than the radius of gyration of the ring polymers. """ self.ttime = -time.time() self.thermostat.step() self.barostat.thermostat.step() self.rmcom() self.ttime += time.time() self.ptime = -time.time() self.barostat.pstep() self.ptime += time.time() self.qtime = -time.time() self.barostat.qcstep() self.nm.free_qstep() self.qtime += time.time() self.ptime -= time.time() self.barostat.pstep() self.ptime += time.time() self.ttime -= time.time() self.barostat.thermostat.step() self.thermostat.step() self.rmcom() self.ttime += time.time() class ReplayEnsemble(Ensemble): """Ensemble object that just loads snapshots from an external file in sequence. Has the relevant conserved quantity and normal mode propagator for the constant energy ensemble. Note that a temperature of some kind must be defined so that the spring potential can be calculated. Attributes: intraj: The input trajectory file. ptime: The time taken in updating the velocities. qtime: The time taken in updating the positions. ttime: The time taken in applying the thermostat steps. Depend objects: econs: Conserved energy quantity. Depends on the bead kinetic and potential energy, and the spring potential energy. """ def __init__(self, dt, temp, fixcom=False, intraj=None): """Initialises ReplayEnsemble. Args: dt: The simulation timestep. temp: The system temperature. fixcom: An optional boolean which decides whether the centre of mass motion will be constrained or not. Defaults to False. intraj: The input trajectory file. """ super(ReplayEnsemble,self).__init__(dt=dt,temp=temp,fixcom=fixcom) if intraj == None: raise ValueError("Must provide an initialized InitFile object to read trajectory from") self.intraj = intraj if intraj.mode == "manual": raise ValueError("Replay can only read from PDB or XYZ files -- or a single frame from a CHK file") self.rfile = open(self.intraj.value,"r") def step(self): """Does one simulation time step.""" self.ptime = self.ttime = 0 self.qtime = -time.time() try: if (self.intraj.mode == "xyz"): for b in self.beads: myatoms = read_xyz(self.rfile) myatoms.q *= unit_to_internal("length",self.intraj.units,1.0) b.q[:] = myatoms.q elif (self.intraj.mode == "pdb"): for b in self.beads: myatoms, mycell = read_pdb(self.rfile) myatoms.q *= unit_to_internal("length",self.intraj.units,1.0) mycell.h *= unit_to_internal("length",self.intraj.units,1.0) b.q[:] = myatoms.q self.cell.h[:] = mycell.h elif (self.intraj.mode == "chk" or self.intraj.mode == "checkpoint"): # reads configuration from a checkpoint file xmlchk = xml_parse_file(self.rfile) # Parses the file. from ipi.inputs.simulation import InputSimulation simchk = InputSimulation() simchk.parse(xmlchk.fields[0][1]) mycell = simchk.cell.fetch() mybeads = simchk.beads.fetch() self.cell.h[:] = mycell.h self.beads.q[:] = mybeads.q softexit.trigger(" # Read single checkpoint") except EOFError: softexit.trigger(" # Finished reading re-run trajectory") self.qtime += time.time()