forked from lijiext/lammps
566 lines
20 KiB
Python
566 lines
20 KiB
Python
"""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 <http.//www.gnu.org/licenses/>.
|
|
|
|
|
|
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()
|
|
|
|
|