"""Contains the classes that are used to initialize data in the simulation. 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 . These classes can either be used to restart a simulation with some different data or used to start a calculation. Any data given in these classes will overwrite data given elsewhere. Classes: Initializer: Holds the functions that are required to initialize objects in the code. Data can be initialized from a file, or according to a particular parameter. An example of the former would be initializing the configurations from a xyz file, an example of the latter would be initializing the velocities according to the physical temperature. InitBase: Simple class that reads data from a string or file. InitIndexed: The same as init base, but can also optionally hold information about which atom or bead to initialize from. Functions: init_xyz: Reads beads data from a xyz file. init_pdb: Reads beads and cell data from a pdb file. init_chk: Reads beads, cell and thermostat data from a checkpoint file. init_beads: Initializes a beads object from an Initializer object. init_vector: Initializes a vector from an Initializer object. set_vector: Initializes a vector from another vector. """ import numpy as np from ipi.engine.beads import Beads from ipi.engine.cell import Cell from ipi.engine.normalmodes import NormalModes from ipi.engine.ensembles import Ensemble 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.depend import dobject from ipi.utils.units import Constants, unit_to_internal from ipi.utils.nmtransform import nm_rescale from ipi.utils.messages import verbosity, warning, info __all__ = ['Initializer', 'InitBase', 'InitIndexed'] class InitBase(dobject): """Base class for initializer objects. Attributes: value: A duck-typed stored value. mode: A string that determines how the value is to be interpreted. units: A string giving which unit the value is in. """ def __init__(self, value="", mode="", units="", **others): """Initializes InitFile. Args: value: A string which specifies what value to initialize the simulation property to. mode: A string specifiying what style of initialization should be used to read the data. units: A string giving which unit the value is in. """ self.value = value self.mode = mode self.units = units for (o, v) in others.items(): self.__dict__[o] = v class InitIndexed(InitBase): """Class to initialize objects which can be set for a particular bead. Attributes: index: Which atom to initialize the value of. bead: Which bead to initialize the value of. """ def __init__(self, value="", mode="", units="", index=-1, bead=-1): """Initializes InitFile. Args: value: A string which specifies what value to initialize the simulation property to. mode: A string specifiying what style of initialization should be used to read the data. units: A string giving which unit the value is in. index: Which atom to initialize the value of. bead: Which bead to initialize the value of. """ super(InitIndexed,self).__init__(value=value, mode=mode, units=units, index=index, bead=bead) def init_xyz(filename): """Reads an xyz file and returns the data contained in it. Args: filename: A string giving the name of the xyz file to be read from. Returns: A list of Atoms objects as read from each frame of the xyz file. """ rfile = open(filename,"r") ratoms = [] while True: #while loop, so that more than one configuration can be given #so multiple beads can be initialized at once. try: myatoms = read_xyz(rfile) except EOFError: break ratoms.append(myatoms) return ratoms def init_pdb(filename): """Reads an pdb file and returns the data contained in it. Args: filename: A string giving the name of the pdb file to be read from. Returns: A list of Atoms objects as read from each frame of the pdb file, and a Cell object as read from the final pdb frame. """ rfile = open(filename,"r") ratoms = [] while True: #while loop, so that more than one configuration can be given #so multiple beads can be initialized at once. try: myatoms, rcell = read_pdb(rfile) except EOFError: break ratoms.append(myatoms) return ( ratoms, rcell ) # if multiple frames, the last cell is returned def init_chk(filename): """Reads an checkpoint file and returns the data contained in it. Args: filename: A string giving the name of the checkpoint file to be read from. Returns: A Beads object, Cell object and Thermostat object as read from the checkpoint file. """ # reads configuration from a checkpoint file rfile = open(filename,"r") xmlchk = xml_parse_file(rfile) # Parses the file. from ipi.inputs.simulation import InputSimulation simchk = InputSimulation() simchk.parse(xmlchk.fields[0][1]) rcell = simchk.cell.fetch() rbeads = simchk.beads.fetch() rthermo = simchk.ensemble.thermostat.fetch() return (rbeads, rcell, rthermo) def init_beads(iif, nbeads): """A file to initialize a beads object from an appropriate initializer object. Args: iif: An Initializer object which has information on the bead positions. nbeads: The number of beads. Raises: ValueError: If called using an Initializer object with a 'manual' mode. """ mode = iif.mode; value = iif.value if mode == "xyz" or mode == "pdb": if mode == "xyz": ratoms = init_xyz(value) if mode == "pdb": ratoms = init_pdb(value)[0] rbeads = Beads(ratoms[0].natoms,len(ratoms)) for i in range(len(ratoms)): rbeads[i] = ratoms[i] elif mode == "chk": rbeads = init_chk(value)[0] elif mode == "manual": raise ValueError("Cannot initialize manually a whole beads object.") return rbeads def init_vector(iif, nbeads, momenta=False): """A file to initialize a vector from an appropriate initializer object. Args: iif: An Initializer object specifying the value of a vector. nbeads: The number of beads. momenta: If bead momenta rather than positions are being initialized from a checkpoint file, this is set to True. """ mode = iif.mode; value = iif.value if mode == "xyz" or mode == "pdb": rq = init_beads(iif, nbeads).q elif mode == "chk": if momenta: rq = init_beads(iif, nbeads).p else: rq = init_beads(iif, nbeads).q elif mode == "manual": rq = value # determines the size of the input data if mode == "manual": if iif.bead >= 0: # if there is a bead specifier then we return a single bead slice nbeads = 1 natoms = len(rq)/nbeads/3 rq.shape = (nbeads,3*natoms) return rq def set_vector(iif, dq, rq): """A file to initialize a vector from an another vector. If the first dimension is different, i.e. the two vectors correspond to a different number of beads, then the ring polymer contraction/expansion is used to rescale the original vector to the one used in the simulation, as described in the paper T. E. Markland and D. E. Manolopoulos, J. Chem. Phys. 129, 024105, (2008). Args: iif: An Initializer object specifying the value of a vector. dq: The vector to be initialized. rq: The vector to initialize from. """ (nbeads, natoms) = rq.shape; natoms /= 3 (dbeads, datoms) = dq.shape; datoms /= 3 # Check that indices make sense if iif.index < 0 and natoms != datoms: raise ValueError("Initialization tries to mix up structures with different atom numbers.") if iif.index >= datoms: raise ValueError("Cannot initialize single atom as atom index %d is larger than the number of atoms" % iif.index) if iif.bead >= dbeads: raise ValueError("Cannot initialize single bead as bead index %d is larger than the number of beads" % iif.bead) if iif.bead < 0: # we are initializing the path res = nm_rescale(nbeads,dbeads) # path rescaler if nbeads != dbeads: info(" # Initialize is rescaling from %5d beads to %5d beads" % (nbeads, dbeads), verbosity.low) if iif.index < 0: dq[:] = res.b1tob2(rq) else: # we are initializing a specific atom dq[:,3*iif.index:3*(iif.index+1)] = res.b1tob2(rq) else: # we are initializing a specific bead if iif.index < 0: dq[iif.bead] = rq else: dq[iif.bead,3*iif.index:3*(iif.index+1)] = rq class Initializer(dobject): """Class that deals with the initialization of data. This can either be used to initialize the atom positions and the cell data from a file, or to initialize them from a beads, atoms or cell object. Currently, we use a ring polymer contraction scheme to create a new beads object from one given in initialize if they have different numbers of beads, as described in the paper T. E. Markland and D. E. Manolopoulos, J. Chem. Phys. 129, 024105, (2008). If the new beads object has more beads than the beads object it was initialized from, we set the higher ring polymer normal modes to zero. Attributes: queue: A list of things to initialize. Each member of the list is a tuple of the form ('type', 'object'), where 'type' specifies what kind of initialization is being done, and 'object' gives the data to initialize it from. """ def __init__(self, nbeads=0, queue=None): """Initializes Initializer. Arguments: nbeads: The number of beads that we need in the simulation. Not necessarily the same as the number of beads of the objects we are initializing the data from. queue: A list of things to initialize. Each member of the list is a tuple of the form ('type', 'object'), where 'type' specifies what kind of initialization is being done, and 'object' gives the data to initialize it from. """ self.nbeads = nbeads if queue is None: self.queue = [] else: self.queue = queue def init_stage1(self, simul): """Initializes the simulation -- first stage. Takes a simulation object, and uses all the data in the initialization queue to fill up the beads and cell data needed to run the simulation. Args: simul: A simulation object to be initialized. Raises: ValueError: Raised if there is a problem with the initialization, if something that should have been has not been, or if the objects that have been specified are not compatible with each other. """ if simul.beads.nbeads == 0: fpos = fmom = fmass = flab = fcell = False # we don't have an explicitly defined beads object yet else: fpos = fmom = fmass = flab = fcell = True for (k,v) in self.queue: info(" # Initializer (stage 1) parsing " + str(k) + " object.", verbosity.high) if k == "cell": if fcell : warning("Overwriting previous cell parameters", verbosity.medium) if v.mode == "pdb": rh = init_pdb(v.value)[1].h elif v.mode == "chk": rh = init_chk(v.value)[1].h else: rh = v.value.reshape((3,3)) rh *= unit_to_internal("length",v.units,1.0) simul.cell.h = rh if simul.cell.V == 0.0: ValueError("Cell provided has zero volume") fcell = True elif k == "masses": if simul.beads.nbeads == 0: raise ValueError("Cannot initialize the masses before the size of the system is known") if fmass: warning("Overwriting previous atomic masses", verbosity.medium) if v.mode == "manual": rm = v.value else: rm = init_beads(v, self.nbeads).m rm *= unit_to_internal("mass",v.units,1.0) if v.bead < 0: # we are initializing the path if (fmom and fmass): warning("Rescaling momenta to make up for changed mass", verbosity.medium) simul.beads.p /= simul.beads.sm3 # go to mass-scaled momenta, that are mass-invariant if v.index < 0: simul.beads.m = rm else: # we are initializing a specific atom simul.beads.m[v.index:v.index+1] = rm if (fmom and fmass): # finishes correcting the momenta simul.beads.p *= simul.beads.sm3 # back to normal momenta else: raise ValueError("Cannot change the mass of a single bead") fmass = True elif k == "labels": if simul.beads.nbeads == 0: raise ValueError("Cannot initialize the labels before the size of the system is known") if flab: warning("Overwriting previous atomic labels", verbosity.medium) if v.mode == "manual": rn = v.value else: rn = init_beads(v, self.nbeads).names if v.bead < 0: # we are initializing the path if v.index < 0: simul.beads.names = rn else: # we are initializing a specific atom simul.beads.names[v.index:v.index+1] = rn else: raise ValueError("Cannot change the label of a single bead") flab = True elif k == "positions": if fpos: warning("Overwriting previous atomic positions", verbosity.medium) # read the atomic positions as a vector rq = init_vector(v, self.nbeads) rq *= unit_to_internal("length",v.units,1.0) (nbeads, natoms) = rq.shape; natoms /= 3 # check if we must initialize the simulation beads if simul.beads.nbeads == 0: if v.index >= 0: raise ValueError("Cannot initialize single atoms before the size of the system is known") simul.beads.resize(natoms,self.nbeads) set_vector(v, simul.beads.q, rq) fpos = True elif (k == "velocities" or k == "momenta") and v.mode == "thermal" : # intercept here thermal initialization, so we don't need to check further down if fmom: warning("Overwriting previous atomic momenta", verbosity.medium) if simul.beads.natoms == 0: raise ValueError("Cannot initialize momenta before the size of the system is known.") if not fmass: raise ValueError("Trying to resample velocities before having masses.") rtemp = v.value * unit_to_internal("temperature",v.units,1.0) if rtemp <= 0: warning("Using the simulation temperature to resample velocities", verbosity.low) rtemp = simul.ensemble.temp else: info(" # Resampling velocities at temperature %s %s" % (v.value, v.units), verbosity.low) # pull together a mock initialization to get NM masses right #without too much code duplication if v.bead >= 0: raise ValueError("Cannot thermalize a single bead") if v.index >= 0: rnatoms = 1 else: rnatoms = simul.beads.natoms rbeads = Beads(rnatoms, simul.beads.nbeads) if v.index < 0: rbeads.m[:] = simul.beads.m else: rbeads.m[:] = simul.beads.m[v.index] rnm = NormalModes(mode=simul.nm.mode, transform_method=simul.nm.transform_method, freqs=simul.nm.nm_freqs) rens = Ensemble(dt=simul.ensemble.dt, temp=simul.ensemble.temp) rnm.bind(rbeads,rens) # then we exploit the sync magic to do a complicated initialization # in the NM representation # with (possibly) shifted-frequencies NM rnm.pnm = simul.prng.gvec((rbeads.nbeads,3*rbeads.natoms))*np.sqrt(rnm.dynm3)*np.sqrt(rbeads.nbeads*rtemp*Constants.kb) if v.index < 0: simul.beads.p = rbeads.p else: simul.beads.p[:,3*v.index:3*(v.index+1)] = rbeads.p fmom = True elif k == "momenta": if fmom: warning("Overwriting previous atomic momenta", verbosity.medium) # read the atomic momenta as a vector rp = init_vector(v, self.nbeads, momenta = True) rp *= unit_to_internal("momentum",v.units,1.0) (nbeads, natoms) = rp.shape; natoms /= 3 # checks if we must initialize the simulation beads if simul.beads.nbeads == 0: if v.index >= 0 : raise ValueError("Cannot initialize single atoms before the size of the system is known") simul.beads.resize(natoms,self.nbeads) rp *= np.sqrt(self.nbeads/nbeads) set_vector(v, simul.beads.p, rp) fmom = True elif k == "velocities": if fmom: warning("Overwriting previous atomic momenta", verbosity.medium) # read the atomic velocities as a vector rv = init_vector(v, self.nbeads) rv *= unit_to_internal("velocity",v.units,1.0) (nbeads, natoms) = rv.shape; natoms /= 3 # checks if we must initialize the simulation beads if simul.beads.nbeads == 0 or not fmass: ValueError("Cannot initialize velocities before the masses of the atoms are known") simul.beads.resize(natoms,self.nbeads) warning("Initializing from velocities uses the previously defined masses -- not the masses inferred from the file -- to build momenta", verbosity.low) if v.index >= 0: rv *= simul.beads.m[v.index] elif v.bead >= 0: rv *= simul.beads.m3[0] else: rv *= simul.beads.m3 rv *= np.sqrt(self.nbeads/nbeads) set_vector(v, simul.beads.p, rv) fmom = True elif k == "thermostat": pass # thermostats must be initialised in a second stage if simul.beads.natoms == 0: raise ValueError("Initializer could not initialize the atomic positions") if simul.cell.V == 0: raise ValueError("Initializer could not initialize the cell") for i in range(simul.beads.natoms): if simul.beads.m[i] <= 0: raise ValueError("Initializer could not initialize the masses") if simul.beads.names[i] == "": raise ValueError("Initializer could not initialize the atom labels") if not fmom: warning("Momenta not specified in initialize. Will start with zero velocity if they are not specified in beads.", verbosity.low) def init_stage2(self, simul): """Initializes the simulation -- second stage. Takes a simulation object which has been fully generated, and restarts additional information such as the thermostat internal state. Args: simul: A simulation object to be initialized. Raises: ValueError: Raised if there is a problem with the initialization, if something that should have been has not been, or if the objects that have been specified are not compatible with each other. """ for (k,v) in self.queue: info(" # Initializer (stage 2) parsing " + str(k) + " object.", verbosity.high) if k == "gle": # read thermostat parameters from file if not ( hasattr(simul.ensemble, "thermostat") ): raise ValueError("Ensemble does not have a thermostat to initialize") if not ( hasattr(simul.ensemble.thermostat, "s") ): raise ValueError("There is nothing to initialize in non-GLE thermostats") ssimul = simul.ensemble.thermostat.s if v.mode == "manual": sinput = v.value.copy() if (sinput.size() != ssimul.size() ): raise ValueError("Size mismatch in thermostat initialization data") sinput.shape = ssimul.shape elif v.mode == "chk": rthermo = init_chk(v.value)[2] if not hasattr(rthermo,"s"): raise ValueError("Checkpoint file does not contain usable thermostat data") sinput = rthermo.s.copy() if sinput.shape != ssimul.shape : raise ValueError("Shape mismatch in thermostat initialization data") # if all the preliminary checks are good, we can initialize the s's ssimul[:] = sinput