1"""Contains the classes that are used to initialize data in the simulation.
2
3Copyright (C) 2013, Joshua More and Michele Ceriotti
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program. If not, see <http.//www.gnu.org/licenses/>.
17
18
19These classes can either be used to restart a simulation with some different
20data or used to start a calculation. Any data given in these classes will
21overwrite data given elsewhere.
22
23Classes:
24   Initializer: Holds the functions that are required to initialize objects in
25      the code. Data can be initialized from a file, or according to a
26      particular parameter. An example of the former would be initializing
27      the configurations from a xyz file, an example of the latter would be
28      initializing the velocities according to the physical temperature.
29   InitBase: Simple class that reads data from a string or file.
30   InitIndexed: The same as init base, but can also optionally hold
31      information about which atom or bead to initialize from.
32
33Functions:
34   init_xyz: Reads beads data from a xyz file.
35   init_pdb: Reads beads and cell data from a pdb file.
36   init_chk: Reads beads, cell and thermostat data from a checkpoint file.
37   init_beads: Initializes a beads object from an Initializer object.
38   init_vector: Initializes a vector from an Initializer object.
39   set_vector: Initializes a vector from another vector.
40"""
41
42import numpy as np
43
44from ipi.engine.beads import Beads
45from ipi.engine.cell import Cell
46from ipi.engine.normalmodes import NormalModes
47from ipi.engine.ensembles import Ensemble
48from ipi.utils.io.io_xyz import read_xyz
49from ipi.utils.io.io_pdb import read_pdb
50from ipi.utils.io.io_xml import xml_parse_file
51from ipi.utils.depend import dobject
52from ipi.utils.units import Constants, unit_to_internal
53from ipi.utils.nmtransform import nm_rescale
54from ipi.utils.messages import verbosity, warning, info
55
56__all__ = ['Initializer', 'InitBase', 'InitIndexed']
57
58class InitBase(dobject):
59   """Base class for initializer objects.
60
61   Attributes:
62      value: A duck-typed stored value.
63      mode: A string that determines how the value is to be interpreted.
64      units: A string giving which unit the value is in.
65   """
66
67   def __init__(self, value="", mode="", units="", **others):
68      """Initializes InitFile.
69
70      Args:
71         value: A string which specifies what value to initialize the
72            simulation property to.
73         mode: A string specifying what style of initialization should be
74            used to read the data.
75         units: A string giving which unit the value is in.
76      """
77
78      self.value = value
79      self.mode = mode
80      self.units = units
81
82      for (o, v) in others.items():
83         self.__dict__[o] = v
84
85
86class InitIndexed(InitBase):
87   """Class to initialize objects which can be set for a particular bead.
88
89   Attributes:
90      index: Which atom to initialize the value of.
91      bead: Which bead to initialize the value of.
92   """
93
94   def __init__(self, value="", mode="", units="", index=-1, bead=-1):
95      """Initializes InitFile.
96
97      Args:
98         value: A string which specifies what value to initialize the
99            simulation property to.
100         mode: A string specifying what style of initialization should be
101            used to read the data.
102         units: A string giving which unit the value is in.
103         index: Which atom to initialize the value of.
104         bead: Which bead to initialize the value of.
105      """
106
107      super(InitIndexed,self).__init__(value=value, mode=mode, units=units, index=index, bead=bead)
108
109
110def init_xyz(filename):
111   """Reads an xyz file and returns the data contained in it.
112
113   Args:
114      filename: A string giving the name of the xyz file to be read from.
115
116   Returns:
117      A list of Atoms objects as read from each frame of the xyz file.
118   """
119
120   rfile = open(filename,"r")
121   ratoms = []
122   while True:
123   #while loop, so that more than one configuration can be given
124   #so multiple beads can be initialized at once.
125      try:
126         myatoms = read_xyz(rfile)
127      except EOFError:
128         break
129      ratoms.append(myatoms)
130   return ratoms
131
132def init_pdb(filename):
133   """Reads an pdb file and returns the data contained in it.
134
135   Args:
136      filename: A string giving the name of the pdb file to be read from.
137
138   Returns:
139      A list of Atoms objects as read from each frame of the pdb file, and
140      a Cell object as read from the final pdb frame.
141   """
142
143   rfile = open(filename,"r")
144   ratoms = []
145   while True:
146   #while loop, so that more than one configuration can be given
147   #so multiple beads can be initialized at once.
148      try:
149         myatoms, rcell  = read_pdb(rfile)
150      except EOFError:
151         break
152      ratoms.append(myatoms)
153   return ( ratoms, rcell ) # if multiple frames, the last cell is returned
154
155def init_chk(filename):
156   """Reads an checkpoint file and returns the data contained in it.
157
158   Args:
159      filename: A string giving the name of the checkpoint file to be read from.
160
161   Returns:
162      A Beads object, Cell object and Thermostat object as read from the
163      checkpoint file.
164   """
165
166   # reads configuration from a checkpoint file
167   rfile = open(filename,"r")
168   xmlchk = xml_parse_file(rfile) # Parses the file.
169
170   from ipi.inputs.simulation import InputSimulation
171   simchk = InputSimulation()
172   simchk.parse(xmlchk.fields[0][1])
173   rcell = simchk.cell.fetch()
174   rbeads = simchk.beads.fetch()
175   rthermo = simchk.ensemble.thermostat.fetch()
176
177   return (rbeads, rcell, rthermo)
178
179def init_beads(iif, nbeads):
180   """A file to initialize a beads object from an appropriate initializer
181   object.
182
183   Args:
184      iif: An Initializer object which has information on the bead positions.
185      nbeads: The number of beads.
186
187   Raises:
188      ValueError: If called using an Initializer object with a 'manual' mode.
189   """
190
191   mode = iif.mode; value = iif.value
192   if mode == "xyz" or mode == "pdb":
193      if mode == "xyz": ratoms = init_xyz(value)
194      if mode == "pdb": ratoms = init_pdb(value)[0]
195      rbeads = Beads(ratoms[0].natoms,len(ratoms))
196      for i in range(len(ratoms)): rbeads[i] = ratoms[i]
197   elif mode == "chk":
198      rbeads = init_chk(value)[0]
199   elif mode == "manual":
200      raise ValueError("Cannot initialize manually a whole beads object.")
201
202   return rbeads
203
204def init_vector(iif, nbeads, momenta=False):
205   """A file to initialize a vector from an appropriate initializer
206   object.
207
208   Args:
209      iif: An Initializer object specifying the value of a vector.
210      nbeads: The number of beads.
211      momenta: If bead momenta rather than positions are being initialized
212         from a checkpoint file, this is set to True.
213   """
214
215   mode = iif.mode; value = iif.value
216   if mode == "xyz" or mode == "pdb":
217      rq = init_beads(iif, nbeads).q
218   elif mode == "chk":
219      if momenta: rq = init_beads(iif, nbeads).p
220      else:       rq = init_beads(iif, nbeads).q
221   elif mode == "manual":
222      rq = value
223
224   # determines the size of the input data
225   if mode == "manual":
226      if iif.bead >= 0: # if there is a bead specifier then we return a single bead slice
227         nbeads = 1
228      natoms = len(rq)/nbeads/3
229      rq.shape = (nbeads,3*natoms)
230
231   return rq
232
233def set_vector(iif, dq, rq):
234   """A file to initialize a vector from an another vector.
235
236   If the first dimension is different, i.e. the two vectors correspond
237   to a different number of beads, then the ring polymer contraction/expansion
238   is used to rescale the original vector to the one used in the simulation,
239   as described in the paper T. E. Markland and D. E. Manolopoulos, J. Chem.
240   Phys. 129, 024105, (2008).
241
242   Args:
243      iif: An Initializer object specifying the value of a vector.
244      dq: The vector to be initialized.
245      rq: The vector to initialize from.
246   """
247
248   (nbeads, natoms) = rq.shape; natoms /= 3
249   (dbeads, datoms) = dq.shape; datoms /= 3
250
251   # Check that indices make sense
252   if iif.index < 0 and natoms != datoms:
253      raise ValueError("Initialization tries to mix up structures with different atom numbers.")
254   if iif.index >= datoms:
255      raise ValueError("Cannot initialize single atom as atom index %d is larger than the number of atoms" % iif.index)
256   if iif.bead >= dbeads:
257      raise ValueError("Cannot initialize single bead as bead index %d is larger than the number of beads" % iif.bead)
258
259   if iif.bead < 0:   # we are initializing the path
260      res = nm_rescale(nbeads,dbeads)  # path rescaler
261      if nbeads != dbeads:
262         info(" # Initialize is rescaling from %5d beads to %5d beads" % (nbeads, dbeads), verbosity.low)
263      if iif.index < 0:
264         dq[:] = res.b1tob2(rq)
265      else: # we are initializing a specific atom
266         dq[:,3*iif.index:3*(iif.index+1)] = res.b1tob2(rq)
267   else:  # we are initializing a specific bead
268      if iif.index < 0:
269         dq[iif.bead] = rq
270      else:
271         dq[iif.bead,3*iif.index:3*(iif.index+1)] = rq
272
273class Initializer(dobject):
274   """Class that deals with the initialization of data.
275
276   This can either be used to initialize the atom positions and the cell data
277   from a file, or to initialize them from a beads, atoms or cell object.
278
279   Currently, we use a ring polymer contraction scheme to create a new beads
280   object from one given in initialize if they have different numbers of beads,
281   as described in the paper T. E. Markland and D. E. Manolopoulos, J. Chem.
282   Phys. 129, 024105, (2008). If the new beads object has more beads than
283   the beads object it was initialized from, we set the higher ring polymer
284   normal modes to zero.
285
286   Attributes:
287      queue: A list of things to initialize. Each member of the list is a tuple
288         of the form ('type', 'object'), where 'type' specifies what kind of
289         initialization is being done, and 'object' gives the data to
290         initialize it from.
291   """
292
293   def __init__(self, nbeads=0, queue=None):
294      """Initializes Initializer.
295
296      Arguments:
297         nbeads: The number of beads that we need in the simulation. Not
298            necessarily the same as the number of beads of the objects we are
299            initializing the data from.
300         queue: A list of things to initialize. Each member of the list is a
301            tuple of the form ('type', 'object'), where 'type' specifies what
302            kind of initialization is being done, and 'object' gives the data to
303            initialize it from.
304      """
305
306      self.nbeads = nbeads
307
308      if queue is None:
309         self.queue = []
310      else:
311         self.queue = queue
312
313   def init_stage1(self, simul):
314      """Initializes the simulation -- first stage.
315
316      Takes a simulation object, and uses all the data in the initialization
317      queue to fill up the beads and cell data needed to run the simulation.
318
319      Args:
320         simul: A simulation object to be initialized.
321
322      Raises:
323         ValueError: Raised if there is a problem with the initialization,
324            if something that should have been has not been, or if the objects
325            that have been specified are not compatible with each other.
326      """
327
328      if simul.beads.nbeads == 0:
329         fpos = fmom = fmass = flab = fcell = False   # we don't have an explicitly defined beads object yet
330      else:
331         fpos = fmom = fmass = flab = fcell = True
332
333      for (k,v) in self.queue:
334         info(" # Initializer (stage 1) parsing " + str(k) + " object.", verbosity.high)
335
336         if k == "cell":
337            if fcell :
338               warning("Overwriting previous cell parameters", verbosity.medium)
339            if v.mode == "pdb":
340               rh = init_pdb(v.value)[1].h
341            elif v.mode == "chk":
342               rh = init_chk(v.value)[1].h
343            else:
344               rh = v.value.reshape((3,3))
345            rh *= unit_to_internal("length",v.units,1.0)
346
347            simul.cell.h = rh
348            if simul.cell.V == 0.0:
349               ValueError("Cell provided has zero volume")
350
351            fcell = True
352         elif k == "masses":
353            if simul.beads.nbeads == 0:
354               raise ValueError("Cannot initialize the masses before the size of the system is known")
355            if fmass:
356               warning("Overwriting previous atomic masses", verbosity.medium)
357            if v.mode == "manual":
358               rm = v.value
359            else:
360               rm = init_beads(v, self.nbeads).m
361            rm *= unit_to_internal("mass",v.units,1.0)
362
363            if v.bead < 0:   # we are initializing the path
364               if (fmom and fmass):
365                  warning("Rescaling momenta to make up for changed mass", verbosity.medium)
366                  simul.beads.p /= simul.beads.sm3   # go to mass-scaled momenta, that are mass-invariant
367               if v.index < 0:
368                  simul.beads.m = rm
369               else: # we are initializing a specific atom
370                  simul.beads.m[v.index:v.index+1] = rm
371               if (fmom and fmass):  # finishes correcting the momenta
372                  simul.beads.p *= simul.beads.sm3  # back to normal momenta
373            else:
374               raise ValueError("Cannot change the mass of a single bead")
375            fmass = True
376
377         elif k == "labels":
378            if simul.beads.nbeads == 0:
379               raise ValueError("Cannot initialize the labels before the size of the system is known")
380            if flab:
381               warning("Overwriting previous atomic labels", verbosity.medium)
382            if v.mode == "manual":
383               rn = v.value
384            else:
385               rn = init_beads(v, self.nbeads).names
386
387            if v.bead < 0:   # we are initializing the path
388               if v.index < 0:
389                  simul.beads.names = rn
390               else: # we are initializing a specific atom
391                  simul.beads.names[v.index:v.index+1] = rn
392            else:
393               raise ValueError("Cannot change the label of a single bead")
394            flab = True
395
396         elif k == "positions":
397            if fpos:
398               warning("Overwriting previous atomic positions", verbosity.medium)
399            # read the atomic positions as a vector
400            rq = init_vector(v, self.nbeads)
401            rq *= unit_to_internal("length",v.units,1.0)
402            (nbeads, natoms) = rq.shape;   natoms /= 3
403
404            # check if we must initialize the simulation beads
405            if simul.beads.nbeads == 0:
406               if v.index >= 0:
407                  raise ValueError("Cannot initialize single atoms before the size of the system is known")
408               simul.beads.resize(natoms,self.nbeads)
409
410            set_vector(v, simul.beads.q, rq)
411            fpos = True
412
413         elif (k == "velocities" or k == "momenta") and v.mode == "thermal" :   # intercept here thermal initialization, so we don't need to check further down
414            if fmom:
415               warning("Overwriting previous atomic momenta", verbosity.medium)
416            if simul.beads.natoms == 0:
417               raise ValueError("Cannot initialize momenta before the size of the system is known.")
418            if not fmass:
419               raise ValueError("Trying to resample velocities before having masses.")
420
421            rtemp = v.value * unit_to_internal("temperature",v.units,1.0)
422            if rtemp <= 0:
423               warning("Using the simulation temperature to resample velocities", verbosity.low)
424               rtemp = simul.ensemble.temp
425            else:
426               info(" # Resampling velocities at temperature %s %s" % (v.value, v.units), verbosity.low)
427
428            # pull together a mock initialization to get NM masses right
429            #without too much code duplication
430            if v.bead >= 0:
431               raise ValueError("Cannot thermalize a single bead")
432            if v.index >= 0:
433               rnatoms = 1
434            else:
435               rnatoms = simul.beads.natoms
436            rbeads = Beads(rnatoms, simul.beads.nbeads)
437            if v.index < 0:
438               rbeads.m[:] = simul.beads.m
439            else:
440               rbeads.m[:] = simul.beads.m[v.index]
441            rnm = NormalModes(mode=simul.nm.mode, transform_method=simul.nm.transform_method, freqs=simul.nm.nm_freqs)
442            rens = Ensemble(dt=simul.ensemble.dt, temp=simul.ensemble.temp)
443            rnm.bind(rbeads,rens)
444            # then we exploit the sync magic to do a complicated initialization
445            # in the NM representation
446            # with (possibly) shifted-frequencies NM
447            rnm.pnm = simul.prng.gvec((rbeads.nbeads,3*rbeads.natoms))*np.sqrt(rnm.dynm3)*np.sqrt(rbeads.nbeads*rtemp*Constants.kb)
448
449            if v.index < 0:
450               simul.beads.p = rbeads.p
451            else:
452               simul.beads.p[:,3*v.index:3*(v.index+1)] = rbeads.p
453            fmom = True
454
455         elif k == "momenta":
456            if fmom:
457               warning("Overwriting previous atomic momenta", verbosity.medium)
458            # read the atomic momenta as a vector
459            rp = init_vector(v, self.nbeads, momenta = True)
460            rp *= unit_to_internal("momentum",v.units,1.0)
461            (nbeads, natoms) = rp.shape;   natoms /= 3
462
463            # checks if we must initialize the simulation beads
464            if simul.beads.nbeads == 0:
465               if v.index >= 0 :
466                  raise ValueError("Cannot initialize single atoms before the size of the system is known")
467               simul.beads.resize(natoms,self.nbeads)
468
469            rp *= np.sqrt(self.nbeads/nbeads)
470            set_vector(v, simul.beads.p, rp)
471            fmom = True
472
473         elif k == "velocities":
474            if fmom:
475               warning("Overwriting previous atomic momenta", verbosity.medium)
476            # read the atomic velocities as a vector
477            rv = init_vector(v, self.nbeads)
478            rv *= unit_to_internal("velocity",v.units,1.0)
479            (nbeads, natoms) = rv.shape;   natoms /= 3
480
481            # checks if we must initialize the simulation beads
482            if simul.beads.nbeads == 0 or not fmass:
483               ValueError("Cannot initialize velocities before the masses of the atoms are known")
484               simul.beads.resize(natoms,self.nbeads)
485
486            warning("Initializing from velocities uses the previously defined masses -- not the masses inferred from the file -- to build momenta", verbosity.low)
487            if v.index >= 0:
488               rv *= simul.beads.m[v.index]
489            elif v.bead >= 0:
490               rv *= simul.beads.m3[0]
491            else:
492               rv *= simul.beads.m3
493            rv *= np.sqrt(self.nbeads/nbeads)
494            set_vector(v, simul.beads.p, rv)
495            fmom = True
496         elif k == "thermostat": pass   # thermostats must be initialized in a second stage
497
498      if simul.beads.natoms == 0:
499         raise ValueError("Initializer could not initialize the atomic positions")
500      if simul.cell.V == 0:
501         raise ValueError("Initializer could not initialize the cell")
502      for i in range(simul.beads.natoms):
503         if simul.beads.m[i] <= 0:
504            raise ValueError("Initializer could not initialize the masses")
505         if simul.beads.names[i] == "":
506            raise ValueError("Initializer could not initialize the atom labels")
507      if not fmom:
508         warning("Momenta not specified in initialize. Will start with zero velocity if they are not specified in beads.", verbosity.low)
509
510   def init_stage2(self, simul):
511      """Initializes the simulation -- second stage.
512
513      Takes a simulation object which has been fully generated,
514      and restarts additional information such as the thermostat internal state.
515
516      Args:
517         simul: A simulation object to be initialized.
518
519      Raises:
520         ValueError: Raised if there is a problem with the initialization,
521            if something that should have been has not been, or if the objects
522            that have been specified are not compatible with each other.
523      """
524
525      for (k,v) in self.queue:
526         info(" # Initializer (stage 2) parsing " + str(k) + " object.", verbosity.high)
527
528         if k == "gle":
529            # read thermostat parameters from file
530            if not ( hasattr(simul.ensemble, "thermostat") ):
531               raise ValueError("Ensemble does not have a thermostat to initialize")
532            if not ( hasattr(simul.ensemble.thermostat, "s") ):
533               raise ValueError("There is nothing to initialize in non-GLE thermostats")
534            ssimul = simul.ensemble.thermostat.s
535            if v.mode == "manual":
536               sinput = v.value.copy()
537               if (sinput.size() != ssimul.size() ):
538                  raise ValueError("Size mismatch in thermostat initialization data")
539               sinput.shape = ssimul.shape
540            elif v.mode == "chk":
541               rthermo = init_chk(v.value)[2]
542               if not hasattr(rthermo,"s"):
543                  raise ValueError("Checkpoint file does not contain usable thermostat data")
544               sinput = rthermo.s.copy()
545               if sinput.shape != ssimul.shape :
546                  raise ValueError("Shape mismatch in thermostat initialization data")
547
548            # if all the preliminary checks are good, we can initialize the s's
549            ssimul[:] = sinput
550