def __init__(self, **kwargs): """Set up COM analysis. :Keywords: *group_names* list of index group names *ndx* index file if groups are not in the default index *offset* add the *offset* to the residue numbers [0] *name* plugin name [COM] *simulation* The :class:`gromacs.analysis.Simulation` instance that owns the plugin [None] """ group_names = asiterable(kwargs.pop('group_names', [])) ndx = kwargs.pop('ndx', None) offset = kwargs.pop('offset', 0) super(_COM, self).__init__(**kwargs) self.parameters.group_names = group_names self.parameters.offset = offset self.ndx = ndx if self.simulation is not None: self._register_hook()
def make_paths_relative(self, prefix=os.path.curdir): """Hack to be able to copy directories around: prune basedir from paths. .. Warning:: This is not guaranteed to work for all paths. In particular, check :attr:`mdpow.equil.Simulation.dirs.includes` and adjust manually if necessary. """ def assinglet(m): if len(m) == 1: return m[0] elif len(m) == 0: return None return m basedir = self.dirs.basedir for key, fn in self.files.items(): try: self.files[key] = fn.replace(basedir, prefix) except AttributeError: pass for key, val in self.dirs.items(): fns = asiterable(val) # treat them all as lists try: self.dirs[key] = assinglet( [fn.replace(basedir, prefix) for fn in fns]) except AttributeError: pass for key, fn in self.mdp.items(): try: self.mdp[key] = fn.replace(basedir, prefix) except AttributeError: pass logger.warning( "make_paths_relative(): check/manually adjust %s.dirs.includes = %r !", self.__class__.__name__, self.dirs.includes)
def make_paths_relative(self, prefix=os.path.curdir): """Hack to be able to copy directories around: prune basedir from paths. .. Warning:: This is not guaranteed to work for all paths. In particular, check :attrib:`mdpow.equil.Simulation.dirs.includes` and adjust manually if necessary. """ def assinglet(m): if len(m) == 1: return m[0] elif len(m) == 0: return None return m basedir = self.dirs.basedir for key, fn in self.files.items(): try: self.files[key] = fn.replace(basedir, prefix) except AttributeError: pass for key, val in self.dirs.items(): fns = asiterable(val) # treat them all as lists try: self.dirs[key] = assinglet([fn.replace(basedir, prefix) for fn in fns]) except AttributeError: pass for key, fn in self.mdp: try: self.mdp[key] = fn.replace(basedir, prefix) except AttributeError: pass logger.warn("make_paths_relative(): check/manually adjust %s.dirs.includes = %r !", self.__class__.__name__, self.dirs.includes)
def __init__(self,**kwargs): """Set up COM analysis. :Keywords: *group_names* list of index group names *ndx* index file if groups are not in the default index *offset* add the *offset* to the residue numbers [0] *name* plugin name [COM] *simulation* The :class:`gromacs.analysis.Simulation` instance that owns the plugin [None] """ group_names = asiterable(kwargs.pop('group_names', [])) ndx = kwargs.pop('ndx', None) offset = kwargs.pop('offset', 0) super(_COM, self).__init__(**kwargs) self.parameters.group_names = group_names self.parameters.offset = offset self.ndx = ndx if not self.simulation is None: self._register_hook()
def solvate(self, struct=None, **kwargs): """Solvate structure *struct* in a box of solvent. The solvent is determined with the *solvent* keyword to the constructor. :Keywords: *struct* pdb or gro coordinate file (if not supplied, the value is used that was supplied to the constructor of :class:`~mdpow.equil.Simulation`) *kwargs* All other arguments are passed on to :func:`gromacs.setup.solvate`, but set to sensible default values. *top* and *water* are always fixed. """ self.journal.start('solvate') self.dirs.solvation = realpath(kwargs.setdefault('dirname', self.BASEDIR('solvation'))) kwargs['struct'] = self._checknotempty(struct or self.files.structure, 'struct') kwargs['top'] = self._checknotempty(self.files.topology, 'top') kwargs['water'] = self.solvent.box kwargs.setdefault('mainselection', '"%s"' % self.molecule) # quotes are needed for make_ndx kwargs.setdefault('distance', self.solvent.distance) kwargs['includes'] = asiterable(kwargs.pop('includes',[])) + self.dirs.includes params = gromacs.setup.solvate(**kwargs) self.files.structure = kwargs['struct'] self.files.solvated = params['struct'] self.files.ndx = params['ndx'] # we can also make a processed topology right now self.processed_topology(**kwargs) self.journal.completed('solvate') return params
def array(self, directories): """Return multiline string for simple array jobs over *directories*. .. Warning:: The string is in ``bash`` and hence the template must also be ``bash`` (and *not* ``csh`` or ``sh``). """ if not self.has_arrays(): raise NotImplementedError('Not known how make array jobs for ' 'queuing system %(name)s' % vars(self)) hrule = '#'+60*'-' lines = [ '', hrule, '# job array:', self.array_flag(directories), hrule, '# directories for job tasks', 'declare -a jobdirs'] for i,dirname in enumerate(asiterable(directories)): idx = i+1 # job array indices are 1-based lines.append('jobdirs[%(idx)d]=%(dirname)r' % vars()) lines.extend([ '# Switch to the current tasks directory:', 'wdir="${jobdirs[${%(array_variable)s}]}"' % vars(self), 'cd "$wdir" || { echo "ERROR: failed to enter $wdir."; exit 1; }', hrule, '' ]) return "\n".join(lines)
def solvate(self, struct=None, **kwargs): """Solvate structure *struct* in a box of solvent. The solvent is determined with the *solvent* keyword to the constructor. :Keywords: *struct* pdb or gro coordinate file (if not supplied, the value is used that was supplied to the constructor of :class:`~mdpow.equil.Simulation`) *distance* minimum distance between solute and the closes box face; the default depends on the solvent but can be set explicitly here, too. *bt* any box type understood by :func:`gromacs.editconf` (``-bt``): * "triclinic" is a triclinic box, * "cubic" is a rectangular box with all sides equal; * "dodecahedron" represents a rhombic dodecahedron; * "octahedron" is a truncated octahedron. The default is "dodecahedron". *kwargs* All other arguments are passed on to :func:`gromacs.setup.solvate`, but set to sensible default values. *top* and *water* are always fixed. """ self.journal.start('solvate') self.dirs.solvation = realpath( kwargs.setdefault('dirname', self.BASEDIR('solvation'))) kwargs['struct'] = self._checknotempty(struct or self.files.structure, 'struct') kwargs['top'] = self._checknotempty(self.files.topology, 'top') kwargs['water'] = self.solvent.box kwargs.setdefault('mainselection', '"%s"' % self.molecule) # quotes are needed for make_ndx kwargs.setdefault('distance', self.solvent.distance) boxtype = kwargs.pop('bt', None) boxtype = boxtype if boxtype is not None else "dodecahedron" if boxtype not in ("dodecahedron", "triclinic", "cubic", "octahedron"): msg = "Invalid boxtype '{0}', not suitable for 'gmx editconf'.".format( boxtype) logger.error(msg) raise ValueError(msg) kwargs['bt'] = boxtype kwargs['includes'] = asiterable(kwargs.pop('includes', [])) + self.dirs.includes params = self._setup_solvate(**kwargs) self.files.structure = kwargs['struct'] self.files.solvated = params['struct'] self.files.ndx = params['ndx'] # we can also make a processed topology right now self.processed_topology(**kwargs) self.journal.completed('solvate') return params
def run(self, **kwargs): """Write new trajectory with water index group stripped. kwargs are passed to :meth:`gromacs.cbook.Transformer.strip_water`. Important parameters: :Keywords: *force* ``True`` will always regenerate trajectories even if they already exist, ``False`` raises an exception, ``None`` does the sensible thing in most cases (i.e. notify and then move on). *dt* : float or list of floats only write every dt timestep (in ps); if a list of floats is supplied, write multiple trajectories, one for each dt. *compact* : bool write a compact representation *fit* Create an additional trajectory from the stripped one in which the Protein group is rms-fitted to the initial structure. See :meth:`gromacs.cbook.Transformer.fit` for details. Useful values: - "xy" : perform a rot+trans fit in the x-y plane - "all": rot+trans - ``None``: no fitting If *fit* is not supplied then the constructore-default is used (:attr:`_ProteinOnly.parameters.fit`). *keepalso* List of ``make_ndx`` selections that should also be kept. .. Note:: If set, *dt* is only applied to a fit step; the no-water trajectory is always generated for all time steps of the input. """ dt = kwargs.pop('dt', self.parameters.dt) fit = kwargs.pop('fit', self.parameters.fit) kwargs.setdefault('compact', self.parameters.compact) kwargs.setdefault('force', self.parameters.force) kwargs.setdefault('keepalso', self.parameters.keepalso) newfiles = self.transformer.keep_protein_only(**kwargs) self.parameters.filenames.update(newfiles) if fit is not None: if self.parameters.fit == "xy": xy = True else: xy = False transformer_proteinonly = self.transformer.proteinonly.values()[0] for delta_t in asiterable(dt): transformer_proteinonly.fit(xy=xy, dt=delta_t, force=kwargs['force'])
def run(self, **kwargs): """Write new trajectory with water index group stripped. kwargs are passed to :meth:`gromacs.cbook.Transformer.strip_water`. Important parameters: :Keywords: *force* ``True`` will always regenerate trajectories even if they already exist, ``False`` raises an exception, ``None`` does the sensible thing in most cases (i.e. notify and then move on). *dt* : float or list of floats only write every dt timestep (in ps); if a list of floats is supplied, write multiple trajectories, one for each dt. *compact* : bool write a compact representation *fit* Create an additional trajectory from the stripped one in which the Protein group is rms-fitted to the initial structure. See :meth:`gromacs.cbook.Transformer.fit` for details. Useful values: - "xy" : perform a rot+trans fit in the x-y plane - "all": rot+trans - ``None``: no fitting If *fit* is not supplied then the constructore-default is used (:attr:`_ProteinOnly.parameters.fit`). *keepalso* List of ``make_ndx`` selections that should also be kept. .. Note:: If set, *dt* is only applied to a fit step; the no-water trajectory is always generated for all time steps of the input. """ dt = kwargs.pop('dt', self.parameters.dt) fit = kwargs.pop('fit', self.parameters.fit) kwargs.setdefault('compact', self.parameters.compact) kwargs.setdefault('force', self.parameters.force) kwargs.setdefault('keepalso', self.parameters.keepalso) newfiles = self.transformer.keep_protein_only(**kwargs) self.parameters.filenames.update(newfiles) if fit != None: if self.parameters.fit == "xy": xy = True else: xy = False transformer_proteinonly = self.transformer.proteinonly.values()[0] for delta_t in asiterable(dt): transformer_proteinonly.fit(xy=xy, dt=delta_t, force=kwargs['force'])
def plot(self, **kwargs): """Plot all results in one graph, labelled by the result keys. :Keywords: observables select one or more of the stored results. Can be a list or a string (a key into the results dict). ``None`` plots everything [``None``] figure - ``True``: save figures in the given formats - "name.ext": save figure under this filename (``ext`` -> format) - ``False``: only show on screen [``False``] formats : sequence sequence of all formats that should be saved [('png', 'pdf')] plotargs keyword arguments for pylab.plot() """ import pylab figure = kwargs.pop('figure', False) observables = asiterable(kwargs.pop('observables', self.results.keys())) extensions = kwargs.pop('formats', ('pdf', 'png')) for name in observables: result = self.results[name] try: result.plot( **kwargs ) # This requires result classes with a plot() method!! except AttributeError: warnings.warn( "Sorry, plotting of result {name!r} is not implemented". format(**vars()), category=UserWarning) # quick labels -- relies on the proper ordering labels = [ str(n) + " " + dim for n in self.parameters.group_names for dim in 'xyz' ] if kwargs.get('columns') is not None: # select labels according to columns; only makes sense # if plotting against the time (col 0) if kwargs['columns'][0] == 0: labels = numpy.array([None] + labels)[kwargs['columns'][1:]] else: labels = () pylab.legend(labels, loc='best') if figure is True: for ext in extensions: self.savefig(ext=ext) elif figure: self.savefig(filename=figure)
def plot_coarsened(self, **kwargs): """Plot data like :meth:`XVG.plot` with the range of **all** data shown. Data are reduced to *maxpoints* (good results are obtained with low values such as 100) and the actual range of observed data is plotted as a translucent error band around the mean. Each column in *columns* (except the abscissa, i.e. the first column) is decimated (with :meth:`XVG.decimate`) and the range of data is plotted alongside the mean using :meth:`XVG.errorbar` (see for arguments). Additional arguments: :Kewords: *maxpoints* number of points (bins) to coarsen over *color* single color (used for all plots); sequence of colors (will be repeated as necessary); or a matplotlib colormap (e.g. "jet", see :mod:`matplotlib.cm`). The default is to use the :attr:`XVG.default_color_cycle`. *method* Method to coarsen the data. See :meth:`XVG.decimate` The *demean* keyword has no effect as it is required to be ``True``. .. SeeAlso:: :meth:`XVG.plot`, :meth:`XVG.errorbar` and :meth:`XVG.decimate` """ ax = kwargs.pop('ax', None) columns = kwargs.pop('columns', Ellipsis) # slice for everything if columns is Ellipsis or columns is None: columns = numpy.arange(self.array.shape[0]) if len(columns) < 2: raise MissingDataError("plot_coarsened() assumes that there is at least one column " "of data for the abscissa and one or more for the ordinate.") color = kwargs.pop('color', self.default_color_cycle) try: cmap = matplotlib.cm.get_cmap(color) colors = cmap(matplotlib.colors.Normalize()(numpy.arange(len(columns[1:]), dtype=float))) except TypeError: colors = cycle(utilities.asiterable(color)) if ax is None: ax = plt.gca() t = columns[0] kwargs['demean'] = True kwargs['ax'] = ax for column, color in zip(columns[1:], colors): kwargs['color'] = color self.errorbar(columns=[t, column, column], **kwargs) return ax
def plot_coarsened(self, **kwargs): """Plot data like :meth:`XVG.plot` with the range of **all** data shown. Data are reduced to *maxpoints* (good results are obtained with low values such as 100) and the actual range of observed data is plotted as a translucent error band around the mean. Each column in *columns* (except the abscissa, i.e. the first column) is decimated (with :meth:`XVG.decimate`) and the range of data is plotted alongside the mean using :meth:`XVG.errorbar` (see for arguments). Additional arguments: :Kewords: *maxpoints* number of points (bins) to coarsen over *color* single color (used for all plots); sequence of colors (will be repeated as necessary); or a matplotlib colormap (e.g. "jet", see :mod:`matplotlib.cm`). The default is to use the :attr:`XVG.default_color_cycle`. *method* Method to coarsen the data. See :meth:`XVG.decimate` The *demean* keyword has no effect as it is required to be ``True``. .. SeeAlso:: :meth:`XVG.plot`, :meth:`XVG.errorbar` and :meth:`XVG.decimate` """ from itertools import izip, cycle import matplotlib.cm, matplotlib.colors columns = kwargs.pop('columns', Ellipsis) # slice for everything if columns is Ellipsis or columns is None: columns = numpy.arange(self.array.shape[0]) if len(columns) < 2: raise MissingDataError("plot_coarsened() assumes that there is at least one column " "of data for the abscissa and one or more for the ordinate.") color = kwargs.pop('color', self.default_color_cycle) try: cmap = matplotlib.cm.get_cmap(color) colors = cmap(matplotlib.colors.Normalize()(numpy.arange(len(columns[1:]), dtype=float))) except TypeError: colors = cycle(utilities.asiterable(color)) t = columns[0] kwargs['demean'] = True for column, color in izip(columns[1:], colors): kwargs['color'] = color self.errorbar(columns=[t, column, column], **kwargs)
def plot(self, **kwargs): """Plot all results in one graph, labelled by the result keys. :Keywords: observables select one or more of the stored results. Can be a list or a string (a key into the results dict). ``None`` plots everything [``None``] figure - ``True``: save figures in the given formats - "name.ext": save figure under this filename (``ext`` -> format) - ``False``: only show on screen [``False``] formats : sequence sequence of all formats that should be saved [('png', 'pdf')] plotargs keyword arguments for pylab.plot() """ import pylab figure = kwargs.pop('figure', False) observables = asiterable(kwargs.pop('observables', self.results.keys())) extensions = kwargs.pop('formats', ('pdf','png')) for name in observables: result = self.results[name] try: result.plot(**kwargs) # This requires result classes with a plot() method!! except AttributeError: warnings.warn("Sorry, plotting of result %(name)r is not implemented" % vars(), category=UserWarning) # quick labels -- relies on the proper ordering labels = [str(n)+" "+dim for n in self.parameters.group_names for dim in 'xyz'] if not kwargs.get('columns', None) is None: # select labels according to columns; only makes sense # if plotting against the time (col 0) if kwargs['columns'][0] == 0: labels = numpy.array([None]+labels)[kwargs['columns'][1:]] else: labels = () pylab.legend(labels, loc='best') if figure is True: for ext in extensions: self.savefig(ext=ext) elif figure: self.savefig(filename=figure)
def _MD(self, protocol, **kwargs): """Basic MD driver for this Simulation. Do not call directly.""" self.journal.start(protocol) kwargs.setdefault('dirname', self.BASEDIR(protocol)) kwargs.setdefault('deffnm', self.deffnm) kwargs.setdefault('mdp', config.get_template('NPT_opls.mdp')) self.dirs[protocol] = realpath(kwargs['dirname']) setupMD = kwargs.pop('MDfunc', gromacs.setup.MD) kwargs['top'] = self.files.topology kwargs['includes'] = asiterable(kwargs.pop('includes', [])) + self.dirs.includes kwargs['ndx'] = self.files.ndx kwargs[ 'mainselection'] = None # important for SD (use custom mdp and ndx!, gromacs.setup._MD) self._checknotempty(kwargs['struct'], 'struct') if not os.path.exists(kwargs['struct']): # struct is not reliable as it depends on qscript so now we just try everything... struct = gromacs.utilities.find_first(kwargs['struct'], suffices=['pdb', 'gro']) if struct is None: logger.error( "Starting structure %(struct)r does not exist (yet)" % kwargs) raise IOError(errno.ENOENT, "Starting structure not found", kwargs['struct']) else: logger.info("Found starting structure %r (instead of %r).", struct, kwargs['struct']) kwargs['struct'] = struct # now setup the whole simulation (this is typically gromacs.setup.MD() ) params = setupMD(**kwargs) # params['struct'] is md.gro but could also be md.pdb --- depends entirely on qscript self.files[protocol] = params['struct'] # Gromacs 4.5.x 'mdrun -c PDB' fails if it cannot find 'residuetypes.dat' # so instead of fuffing with GMXLIB we just dump it into the directory try: shutil.copy(config.topfiles['residuetypes.dat'], self.dirs[protocol]) except IOError: logger.warning( "Failed to copy 'residuetypes.dat': mdrun will likely fail to write a final structure" ) self.journal.completed(protocol) return params
def generate_submit_array(templates, directories, **kwargs): """Generate a array job. For each ``work_dir`` in *directories*, the array job will 1. cd into ``work_dir`` 2. run the job as detailed in the template It will use all the queuing system directives found in the template. If more complicated set ups are required, then this function cannot be used. :Arguments: *templates* Basic template for a single job; the job array logic is spliced into the position of the line :: # JOB_ARRAY_PLACEHOLDER The appropriate commands for common queuing systems (Sun Gridengine, PBS) are hard coded here. The queuing system is detected from the suffix of the template. *directories* List of directories under *dirname*. One task is set up for each directory. *dirname* The array script will be placed in this directory. The *directories* **must** be located under *dirname*. *kwargs* See :func:`gromacs.setup.generate_submit_script` for details. """ dirname = kwargs.setdefault('dirname', os.path.curdir) reldirs = [relpath(p, start=dirname) for p in asiterable(directories)] missing = [p for p in (os.path.join(dirname, subdir) for subdir in reldirs) if not os.path.exists(p)] if len(missing) > 0: logger.debug("template=%(template)r: dirname=%(dirname)r reldirs=%(reldirs)r", vars()) logger.error("Some directories are not accessible from the array script: " "%(missing)r", vars()) def write_script(template): qsystem = detect_queuing_system(template) if qsystem is None or not qsystem.has_arrays(): logger.warning("Not known how to make a job array for %(template)r; skipping...", vars()) return None kwargs['jobarray_string'] = qsystem.array(reldirs) return generate_submit_scripts(template, **kwargs)[0] # returns list of length 1 # must use config.get_templates() because we need to access the file for detecting return [write_script(template) for template in gromacs.config.get_templates(templates)]
def energy_minimize(self, **kwargs): """Energy minimize the solvated structure on the local machine. *kwargs* are passed to :func:`gromacs.setup.energ_minimize` but if :meth:`~mdpow.equil.Simulation.solvate` step has been carried out previously all the defaults should just work. """ self.journal.start('energy_minimize') self.dirs.energy_minimization = realpath(kwargs.setdefault('dirname', self.BASEDIR('em'))) kwargs['top'] = self.files.topology kwargs.setdefault('struct', self.files.solvated) kwargs.setdefault('mdp', self.mdp['energy_minimize']) kwargs['mainselection'] = None kwargs['includes'] = asiterable(kwargs.pop('includes',[])) + self.dirs.includes params = gromacs.setup.energy_minimize(**kwargs) self.files.energy_minimized = params['struct'] self.journal.completed('energy_minimize') return params
def _MD(self, protocol, **kwargs): """Basic MD driver for this Simulation. Do not call directly.""" self.journal.start(protocol) kwargs.setdefault('dirname', self.BASEDIR(protocol)) kwargs.setdefault('deffnm', self.deffnm) kwargs.setdefault('mdp', config.get_template('NPT_opls.mdp')) self.dirs[protocol] = realpath(kwargs['dirname']) setupMD = kwargs.pop('MDfunc', gromacs.setup.MD) kwargs['top'] = self.files.topology kwargs['includes'] = asiterable(kwargs.pop('includes',[])) + self.dirs.includes kwargs['ndx'] = self.files.ndx kwargs['mainselection'] = None # important for SD (use custom mdp and ndx!, gromacs.setup._MD) self._checknotempty(kwargs['struct'], 'struct') if not os.path.exists(kwargs['struct']): # struct is not reliable as it depends on qscript so now we just try everything... struct = gromacs.utilities.find_first(kwargs['struct'], suffices=['pdb', 'gro']) if struct is None: logger.error("Starting structure %(struct)r does not exist (yet)" % kwargs) raise IOError(errno.ENOENT, "Starting structure not found", kwargs['struct']) else: logger.info("Found starting structure %r (instead of %r).", struct, kwargs['struct']) kwargs['struct'] = struct # now setup the whole simulation (this is typically gromacs.setup.MD() ) params = setupMD(**kwargs) # params['struct'] is md.gro but could also be md.pdb --- depends entirely on qscript self.files[protocol] = params['struct'] # Gromacs 4.5.x 'mdrun -c PDB' fails if it cannot find 'residuetypes.dat' # so instead of fuffing with GMXLIB we just dump it into the directory try: shutil.copy(config.topfiles['residuetypes.dat'], self.dirs[protocol]) except: logger.warn("Failed to copy 'residuetypes.dat': mdrun will likely fail to write a final structure") self.journal.completed(protocol) return params
def run(self, **kwargs): """Write new trajectory with water index group stripped. kwargs are passed to :meth:`gromacs.cbook.Transformer.strip_water`. Important parameters: :Keywords: *force* ``True`` will always regenerate trajectories even if they already exist, ``False`` raises an exception, ``None`` does the sensible thing in most cases (i.e. notify and then move on). *dt* : float or list of floats only write every dt timestep (in ps); if a list of floats is supplied, write multiple trajectories, one for each dt. *compact* : bool write a compact and centered representation *centergroup* Index group to center on ["Protein"] *fit* Create an additional trajectory from the stripped one in which the *fitgroup* group is rms-fitted to the initial structure. See :meth:`gromacs.cbook.Transformer.fit` for details. Useful values: - "xy" : perform a rot+trans fit in the x-y plane - "all": rot+trans - ``None``: no fitting If *fit* is not supplied then the constructor-default is used (:attr:`_StripWater.parameters.fit`). *fitgroup* Index group to fit to with the *fit* option; must be changed if molecule is not a protein and automatically recognized. Also consider supplying a custom index file. ["backbone" or constructor supplied] *resn* name of the residues that are stripped (typically it is safe to leave this at the default 'SOL') .. Note:: If set, *dt* is only applied to a fit step; the no-water trajectory is always generated for all time steps of the input. """ dt = kwargs.pop('dt', self.parameters.dt) fit = kwargs.pop('fit', self.parameters.fit) fitgroup = kwargs.pop('fitgroup', self.parameters.fitgroup) kwargs.setdefault('centergroup', self.parameters.centergroup) kwargs.setdefault('compact', self.parameters.compact) kwargs.setdefault('resn', self.parameters.resn) kwargs.setdefault('force', self.parameters.force) newfiles = self.transformer.strip_water(**kwargs) self.parameters.filenames.update(newfiles) if fit is not None: if self.parameters.fit == "xy": xy = True else: xy = False transformer_nowater = self.transformer.nowater.values()[0] for delta_t in asiterable(dt): transformer_nowater.fit(xy=xy, dt=delta_t, fitgroup=fitgroup, force=kwargs['force'])
def plot(self, names=None, **kwargs): """Plot the selected data. :Arguments: names : string or list Selects which results should be plotted. ``None`` plots all in separate graphs. columns : list Which columns to plot; typically the default is ok. figure - ``True``: save figures in the given formats - "name.ext": save figure under this filename (``ext`` -> format) - ``False``: only show on screen formats : sequence sequence of all formats that should be saved [('png', 'pdf')] callbacks : dict **hack**: provide a dictionary that contains callback functions to customize the plot. They will be called at the end of generating a subplot and must be indexed by *name*. They will be called with the keyword arguments *name* and *axis* (current subplot axis object):: callback(name=name, axis=ax) kwargs All other keyword arguments are directly passed to meth:`gromacs.formats.XVG.plot`. """ import pylab figure = kwargs.pop('figure', False) extensions = kwargs.pop('formats', ('pdf','png')) callbacks = kwargs.pop('callbacks', None) def ps2ns(a): """Transform first column (in ps) to ns.""" _a = numpy.array(a, copy=True) _a[0] *= 0.001 return _a kwargs.setdefault('transform', ps2ns) kwargs.setdefault('columns', self.default_plot_columns) if names is None: names = self.results.keys() names = asiterable(names) # this is now a list (hopefully of strings) ngraphs = len(names) for plotNum, name in enumerate(names): plotNum += 1 ax = pylab.subplot(1, ngraphs, plotNum) try: data = self.results[name].plot(**kwargs) # results are XVG objects with plot method except KeyError: ax.close() raise KeyError('name = %r not known, choose one of %r' % (name, self.results.keys())) #pylab.title(r'Distances: %s' % name) pylab.xlabel(self.xlabels[name]) pylab.ylabel(self.ylabels[name]) # hack: callbacks for customization if not callbacks is None: try: callbacks[name](name=name, axis=ax) except KeyError: pass # pylab.legend(loc='best') if figure is True: for ext in extensions: self.savefig(ext=ext) elif figure: self.savefig(filename=figure)
def __init__(self, molecule=None, **kwargs): """Set up Simulation instance. The *molecule* of the compound molecule should be supplied. Existing files (which have been generated in previous runs) can also be supplied. :Keywords: *molecule* Identifier for the compound molecule. This is the same as the entry in the ``[ molecule ]`` section of the itp file. ["DRUG"] *filename* If provided and *molecule* is ``None`` then load the instance from the pickle file *filename*, which was generated with :meth:`~mdpow.equil.Simulation.save`. *dirname* base directory; all other directories are created under it *solvent* 'water' or 'octanol' or 'cyclohexane' *solventmodel* ``None`` chooses the default (e.g, :data:`mdpow.forcefields.DEFAULT_WATER_MODEL` for ``solvent == "water"``. Other options are the models defined in :data:`mdpow.forcefields.GROMACS_WATER_MODELS`. At the moment, there are no alternative parameterizations included for other solvents. *mdp* dict with keys corresponding to the stages ``energy_minimize``, ``MD_restrained``, ``MD_relaxed``, ``MD_NPT`` and values *mdp* file names (if no entry then the package defaults are used) *kwargs* advanced keywords for short-circuiting; see :data:`mdpow.equil.Simulation.filekeys`. """ self.__cache = {} filename = kwargs.pop('filename', None) dirname = kwargs.pop('dirname', self.dirname_default) solvent = kwargs.pop('solvent', self.solvent_default) # mdp files --- should get values from default runinput.cfg # None values in the kwarg mdp dict are ignored # self.mdp: key = stage, value = path to MDP file # 'water' will choose the default ('tip4p'), other choices are # 'tip3p', 'spc', 'spce', for water; no choices # available for 'cyclohexane' and 'octanol' solventmodel = kwargs.pop('solventmodel', None) mdp_kw = kwargs.pop('mdp', {}) self.mdp = dict((stage, config.get_template(fn)) for stage,fn in self.mdp_defaults.items()) self.mdp.update(dict((stage, config.get_template(fn)) for stage,fn in mdp_kw.items() if fn is not None)) if molecule is None and filename is not None: # load from pickle file self.load(filename) self.filename = filename kwargs = {} # for super else: self.molecule = molecule or 'DRUG' self.dirs = AttributeDict( basedir=realpath(dirname), # .../Equilibrium/<solvent> includes=list(asiterable(kwargs.pop('includes',[]))) + [config.includedir], ) # pre-set filenames: keyword == variable name self.files = AttributeDict([(k, kwargs.pop(k, None)) for k in self.filekeys]) self.deffnm = kwargs.pop("deffnm", "md") if self.files.topology: # assume that a user-supplied topology lives in a 'standard' top dir # that includes the necessary itp file(s) self.dirs.topology = realpath(os.path.dirname(self.files.topology)) self.dirs.includes.append(self.dirs.topology) self.solvent_type = solvent self.solventmodel_identifier = forcefields.get_solvent_identifier(solvent, solventmodel) if self.solventmodel_identifier is None: msg = "No parameters for solvent {0} and solventmodel {1} available.".format( solvent, solventmodel) logger.error(msg) raise ValueError(msg) self.solventmodel = forcefields.get_solvent_model(self.solventmodel_identifier) distance = kwargs.pop('distance', None) distance = distance if distance is not None else DIST[solvent] self.solvent = AttributeDict(itp=self.solventmodel.itp, box=self.solventmodel.coordinates, distance=distance) self.filename = filename or self.solvent_type+'.simulation' super(Simulation, self).__init__(**kwargs)
def run(self, **kwargs): """Write new trajectory with water index group stripped. kwargs are passed to :meth:`gromacs.cbook.Transformer.strip_water`. Important parameters: :Keywords: *force* ``True`` will always regenerate trajectories even if they already exist, ``False`` raises an exception, ``None`` does the sensible thing in most cases (i.e. notify and then move on). *dt* : float or list of floats only write every dt timestep (in ps); if a list of floats is supplied, write multiple trajectories, one for each dt. *compact* : bool write a compact and centered representation *centergroup* Index group to center on ["Protein"] *fit* Create an additional trajectory from the stripped one in which the *fitgroup* group is rms-fitted to the initial structure. See :meth:`gromacs.cbook.Transformer.fit` for details. Useful values: - "xy" : perform a rot+trans fit in the x-y plane - "all": rot+trans - ``None``: no fitting If *fit* is not supplied then the constructor-default is used (:attr:`_StripWater.parameters.fit`). *fitgroup* Index group to fit to with the *fit* option; must be changed if molecule is not a protein and automatically recognized. Also consider supplying a custom index file. ["backbone" or constructor supplied] *resn* name of the residues that are stripped (typically it is safe to leave this at the default 'SOL') .. Note:: If set, *dt* is only applied to a fit step; the no-water trajectory is always generated for all time steps of the input. """ dt = kwargs.pop('dt', self.parameters.dt) fit = kwargs.pop('fit', self.parameters.fit) fitgroup = kwargs.pop('fitgroup', self.parameters.fitgroup) kwargs.setdefault('centergroup', self.parameters.centergroup) kwargs.setdefault('compact', self.parameters.compact) kwargs.setdefault('resn', self.parameters.resn) kwargs.setdefault('force', self.parameters.force) newfiles = self.transformer.strip_water(**kwargs) self.parameters.filenames.update(newfiles) if fit != None: if self.parameters.fit == "xy": xy = True else: xy = False transformer_nowater = self.transformer.nowater.values()[0] for delta_t in asiterable(dt): transformer_nowater.fit(xy=xy, dt=delta_t, fitgroup=fitgroup, force=kwargs['force'])
def plot(self, names=None, **kwargs): """Plot the selected data. :Arguments: names : string or list Selects which results should be plotted. ``None`` plots all in separate graphs. columns : list Which columns to plot; typically the default is ok. figure - ``True``: save figures in the given formats - "name.ext": save figure under this filename (``ext`` -> format) - ``False``: only show on screen formats : sequence sequence of all formats that should be saved [('png', 'pdf')] callbacks : dict **hack**: provide a dictionary that contains callback functions to customize the plot. They will be called at the end of generating a subplot and must be indexed by *name*. They will be called with the keyword arguments *name* and *axis* (current subplot axis object):: callback(name=name, axis=ax) kwargs All other keyword arguments are directly passed to meth:`gromacs.formats.XVG.plot`. """ import pylab figure = kwargs.pop('figure', False) extensions = kwargs.pop('formats', ('pdf', 'png')) callbacks = kwargs.pop('callbacks', None) def ps2ns(a): """Transform first column (in ps) to ns.""" _a = numpy.array(a, copy=True) _a[0] *= 0.001 return _a kwargs.setdefault('transform', ps2ns) kwargs.setdefault('columns', self.default_plot_columns) if names is None: names = self.results.keys() names = asiterable(names) # this is now a list (hopefully of strings) ngraphs = len(names) for plotNum, name in enumerate(names): plotNum += 1 ax = pylab.subplot(1, ngraphs, plotNum) try: data = self.results[name].plot( **kwargs) # results are XVG objects with plot method except KeyError: ax.close() raise KeyError( 'name = {0!r} not known, choose one of {1!r}'.format( name, self.results.keys())) #pylab.title(r'Distances: %s' % name) pylab.xlabel(self.xlabels[name]) pylab.ylabel(self.ylabels[name]) # hack: callbacks for customization if callbacks is not None: try: callbacks[name](name=name, axis=ax) except KeyError: pass # pylab.legend(loc='best') if figure is True: for ext in extensions: self.savefig(ext=ext) elif figure: self.savefig(filename=figure)
def _setup_MD(dirname, deffnm='md', mdp=config.templates['md_OPLSAA.mdp'], struct=None, top='top/system.top', ndx=None, mainselection='"Protein"', qscript=config.qscript_template, qname=None, startdir=None, mdrun_opts="", budget=None, walltime=1/3., dt=0.002, runtime=1e3, multi=1, **mdp_kwargs): """Generic function to set up a ``mdrun`` MD simulation. See the user functions for usage. @param qname: name of the queing system, may be None. @param multi: setup multiple concurrent simulations. These are based upon deffnm being set, and a set of mdp / tpr are created named [deffnm]0.tpr. [deffnm]1.tpr, ... """ if struct is None: raise ValueError('struct must be set to a input structure') structure = realpath(struct) topology = realpath(top) try: index = realpath(ndx) except AttributeError: # (that's what realpath(None) throws...) index = None # None is handled fine below qname = mdp_kwargs.pop('sgename', qname) # compatibility for old scripts qscript = mdp_kwargs.pop('sge', qscript) # compatibility for old scripts qscript_template = config.get_template(qscript) mdp_template = config.get_template(mdp) nsteps = int(float(runtime)/float(dt)) mainindex = deffnm + '.ndx' final_structure = deffnm + '.pdb' # guess... really depends on templates,could also be DEFFNM.pdb # write the processed topology to the default output mdp_parameters = {'nsteps':nsteps, 'dt':dt} mdp_parameters.update(mdp_kwargs) add_mdp_includes(topology, mdp_parameters) # the basic result dictionary # depending on options, various bits might be added to this. result = {'struct': realpath(os.path.join(dirname, final_structure)), # guess 'top': topology, 'ndx': index, # possibly mainindex 'mainselection': mainselection, 'deffnm': deffnm, # return deffnm (tpr = deffnm.tpr!) } with in_dir(dirname): if not (mdp_parameters.get('Tcoupl','').lower() == 'no' or mainselection is None): logger.info("[%(dirname)s] Automatic adjustment of T-coupling groups" % vars()) # make index file in almost all cases; with mainselection == None the user # takes FULL control and also has to provide the template or index groups = make_main_index(structure, selection=mainselection, oldndx=index, ndx=mainindex) natoms = dict([(g['name'], float(g['natoms'])) for g in groups]) tc_group_names = ('__main__', '__environment__') # defined in make_main_index() try: x = natoms['__main__']/natoms['__environment__'] except KeyError: x = 0 # force using SYSTEM in code below wmsg = "Missing __main__ and/or __environment__ index group.\n" \ "This probably means that you have an atypical system. You can " \ "set mainselection=None and provide your own mdp and index files " \ "in order to set up temperature coupling.\n" \ "If no T-coupling is required then set Tcoupl='no'.\n" \ "For now we will just couple everything to 'System'." logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) if x < 0.1: # couple everything together tau_t = firstof(mdp_parameters.pop('tau_t', 0.1)) ref_t = firstof(mdp_parameters.pop('ref_t', 300)) # combine all in one T-coupling group mdp_parameters['tc-grps'] = 'System' mdp_parameters['tau_t'] = tau_t # this overrides the commandline! mdp_parameters['ref_t'] = ref_t # this overrides the commandline! mdp_parameters['gen-temp'] = mdp_parameters.pop('gen_temp', ref_t) wmsg = "Size of __main__ is only %.1f%% of __environment__ so " \ "we use 'System' for T-coupling and ref_t = %g K and " \ "tau_t = %g 1/ps (can be changed in mdp_parameters).\n" \ % (x * 100, ref_t, tau_t) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) else: # couple protein and bath separately n_tc_groups = len(tc_group_names) tau_t = asiterable(mdp_parameters.pop('tau_t', 0.1)) ref_t = asiterable(mdp_parameters.pop('ref_t', 300)) if len(tau_t) != n_tc_groups: tau_t = n_tc_groups * [tau_t[0]] wmsg = "%d coupling constants should have been supplied for tau_t. "\ "Using %f 1/ps for all of them." % (n_tc_groups, tau_t[0]) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) if len(ref_t) != n_tc_groups: ref_t = n_tc_groups * [ref_t[0]] wmsg = "%d temperatures should have been supplied for ref_t. "\ "Using %g K for all of them." % (n_tc_groups, ref_t[0]) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) mdp_parameters['tc-grps'] = tc_group_names mdp_parameters['tau_t'] = tau_t mdp_parameters['ref_t'] = ref_t mdp_parameters['gen-temp'] = mdp_parameters.pop('gen_temp', ref_t[0]) index = realpath(mainindex) if mdp_parameters.get('Tcoupl','').lower() == 'no': logger.info("Tcoupl == no: disabling all temperature coupling mdp options") mdp_parameters['tc-grps'] = "" mdp_parameters['tau_t'] = "" mdp_parameters['ref_t'] = "" mdp_parameters['gen-temp'] = "" if mdp_parameters.get('Pcoupl','').lower() == 'no': logger.info("Pcoupl == no: disabling all pressure coupling mdp options") mdp_parameters['tau_p'] = "" mdp_parameters['ref_p'] = "" mdp_parameters['compressibility'] = "" # do multiple concurrent simulations - ensemble sampling if multi > 1: for i in range(multi): new_mdp = deffnm + str(i) + ".mdp" mdout = deffnm + "out" + str(i) + ".mdp" pp = "processed" + str(i) + ".top" tpr = deffnm + str(i) + ".tpr" # doing ensemble sampling, so give differnt seeds for each one # if we are using 32 bit gromacs, make seeds are are 32 bit even on # 64 bit machine mdp_parameters["andersen_seed"] = random.randint(0,2**31) mdp_parameters["gen_seed"] = random.randint(0,2**31) mdp_parameters["ld_seed"] = random.randint(0,2**31) unprocessed = gromacs.cbook.edit_mdp(mdp_template, new_mdp=new_mdp, **mdp_parameters) check_mdpargs(unprocessed) gromacs.grompp(f=new_mdp, p=topology, c=structure, n=index, o=tpr, po=mdout, pp=pp, **unprocessed) # only add multi to result if we really are doing multiple runs result["multi"] = multi else: new_mdp = deffnm + '.mdp' tpr = deffnm + '.tpr' unprocessed = gromacs.cbook.edit_mdp(mdp_template, new_mdp=new_mdp, **mdp_parameters) check_mdpargs(unprocessed) gromacs.grompp(f=new_mdp, p=topology, c=structure, n=index, o=tpr, po="mdout.mdp", pp="processed.top", **unprocessed) # generate scripts for queing system if requested if qname is not None: runscripts = gromacs.qsub.generate_submit_scripts( qscript_template, deffnm=deffnm, jobname=qname, budget=budget, startdir=startdir, mdrun_opts=mdrun_opts, walltime=walltime) result["qscript"] =runscripts logger.info("[%(dirname)s] All files set up for a run time of %(runtime)g ps " "(dt=%(dt)g, nsteps=%(nsteps)g)" % vars()) result.update(mdp_kwargs) # return extra mdp args so that one can use them for prod run result.pop('define', None) # but make sure that -DPOSRES does not stay... return result
def _setup_MD( dirname, deffnm="md", mdp=config.templates["md_OPLSAA.mdp"], struct=None, top="top/system.top", ndx=None, mainselection='"Protein"', qscript=config.qscript_template, qname=None, startdir=None, mdrun_opts="", budget=None, walltime=1 / 3.0, dt=0.002, runtime=1e3, **mdp_kwargs ): """Generic function to set up a ``mdrun`` MD simulation. See the user functions for usage. """ if struct is None: raise ValueError("struct must be set to a input structure") structure = realpath(struct) topology = realpath(top) try: index = realpath(ndx) except AttributeError: # (that's what realpath(None) throws...) index = None # None is handled fine below qname = mdp_kwargs.pop("sgename", qname) # compatibility for old scripts qscript = mdp_kwargs.pop("sge", qscript) # compatibility for old scripts qscript_template = config.get_template(qscript) mdp_template = config.get_template(mdp) nsteps = int(float(runtime) / float(dt)) mdp = deffnm + ".mdp" tpr = deffnm + ".tpr" mainindex = deffnm + ".ndx" final_structure = deffnm + ".gro" # guess... really depends on templates,could also be DEFFNM.pdb # write the processed topology to the default output mdp_parameters = {"nsteps": nsteps, "dt": dt, "pp": "processed.top"} mdp_parameters.update(mdp_kwargs) add_mdp_includes(topology, mdp_parameters) logger.info("[%(dirname)s] input mdp = %(mdp_template)r", vars()) with in_dir(dirname): if not (mdp_parameters.get("Tcoupl", "").lower() == "no" or mainselection is None): logger.info("[%(dirname)s] Automatic adjustment of T-coupling groups" % vars()) # make index file in almost all cases; with mainselection == None the user # takes FULL control and also has to provide the template or index groups = make_main_index(structure, selection=mainselection, oldndx=index, ndx=mainindex) natoms = dict([(g["name"], float(g["natoms"])) for g in groups]) tc_group_names = ("__main__", "__environment__") # defined in make_main_index() try: x = natoms["__main__"] / natoms["__environment__"] except KeyError: x = 0 # force using SYSTEM in code below wmsg = ( "Missing __main__ and/or __environment__ index group.\n" "This probably means that you have an atypical system. You can " "set mainselection=None and provide your own mdp and index files " "in order to set up temperature coupling.\n" "If no T-coupling is required then set Tcoupl='no'.\n" "For now we will just couple everything to 'System'." ) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) if x < 0.1: # couple everything together tau_t = firstof(mdp_parameters.pop("tau_t", 0.1)) ref_t = firstof(mdp_parameters.pop("ref_t", 300)) # combine all in one T-coupling group mdp_parameters["tc-grps"] = "System" mdp_parameters["tau_t"] = tau_t # this overrides the commandline! mdp_parameters["ref_t"] = ref_t # this overrides the commandline! mdp_parameters["gen-temp"] = mdp_parameters.pop("gen_temp", ref_t) wmsg = ( "Size of __main__ is only %.1f%% of __environment__ so " "we use 'System' for T-coupling and ref_t = %g K and " "tau_t = %g 1/ps (can be changed in mdp_parameters).\n" % (x * 100, ref_t, tau_t) ) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) else: # couple protein and bath separately n_tc_groups = len(tc_group_names) tau_t = asiterable(mdp_parameters.pop("tau_t", 0.1)) ref_t = asiterable(mdp_parameters.pop("ref_t", 300)) if len(tau_t) != n_tc_groups: tau_t = n_tc_groups * [tau_t[0]] wmsg = ( "%d coupling constants should have been supplied for tau_t. " "Using %f 1/ps for all of them." % (n_tc_groups, tau_t[0]) ) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) if len(ref_t) != n_tc_groups: ref_t = n_tc_groups * [ref_t[0]] wmsg = "%d temperatures should have been supplied for ref_t. " "Using %g K for all of them." % ( n_tc_groups, ref_t[0], ) logger.warn(wmsg) warnings.warn(wmsg, category=AutoCorrectionWarning) mdp_parameters["tc-grps"] = tc_group_names mdp_parameters["tau_t"] = tau_t mdp_parameters["ref_t"] = ref_t mdp_parameters["gen-temp"] = mdp_parameters.pop("gen_temp", ref_t[0]) index = realpath(mainindex) if mdp_parameters.get("Tcoupl", "").lower() == "no": logger.info("Tcoupl == no: disabling all temperature coupling mdp options") mdp_parameters["tc-grps"] = "" mdp_parameters["tau_t"] = "" mdp_parameters["ref_t"] = "" mdp_parameters["gen-temp"] = "" if mdp_parameters.get("Pcoupl", "").lower() == "no": logger.info("Pcoupl == no: disabling all pressure coupling mdp options") mdp_parameters["tau_p"] = "" mdp_parameters["ref_p"] = "" mdp_parameters["compressibility"] = "" unprocessed = gromacs.cbook.edit_mdp(mdp_template, new_mdp=mdp, **mdp_parameters) check_mdpargs(unprocessed) gromacs.grompp(f=mdp, p=topology, c=structure, n=index, o=tpr, **unprocessed) runscripts = gromacs.qsub.generate_submit_scripts( qscript_template, deffnm=deffnm, jobname=qname, budget=budget, startdir=startdir, mdrun_opts=mdrun_opts, walltime=walltime, ) logger.info("[%(dirname)s] output mdp = %(mdp)r", vars()) logger.info("[%(dirname)s] output ndx = %(ndx)r", vars()) logger.info("[%(dirname)s] output tpr = %(tpr)r", vars()) logger.info("[%(dirname)s] output runscripts = %(runscripts)r", vars()) logger.info( "[%(dirname)s] All files set up for a run time of %(runtime)g ps " "(dt=%(dt)g, nsteps=%(nsteps)g)" % vars() ) kwargs = { "struct": realpath(os.path.join(dirname, final_structure)), # guess "top": topology, "ndx": index, # possibly mainindex "qscript": runscripts, "mainselection": mainselection, "deffnm": deffnm, # return deffnm (tpr = deffnm.tpr!) } kwargs.update(mdp_kwargs) # return extra mdp args so that one can use them for prod run return kwargs
def plot(self, **kwargs): """Plot xvg file data. The first column of the data is always taken as the abscissa X. Additional columns are plotted as ordinates Y1, Y2, ... In the special case that there is only a single column then this column is plotted against the index, i.e. (N, Y). :Keywords: *columns* : list Select the columns of the data to be plotted; the list is used as a numpy.array extended slice. The default is to use all columns. Columns are selected *after* a transform. *transform* : function function ``transform(array) -> array`` which transforms the original array; must return a 2D numpy array of shape [X, Y1, Y2, ...] where X, Y1, ... are column vectors. By default the transformation is the identity [``lambda x: x``]. *maxpoints* : int limit the total number of data points; matplotlib has issues processing png files with >100,000 points and pdfs take forever to display. Set to ``None`` if really all data should be displayed. At the moment we simply decimate the data at regular intervals. [10000] *method* method to decimate the data to *maxpoints*, see :meth:`XVG.decimate` for details *color* single color (used for all plots); sequence of colors (will be repeated as necessary); or a matplotlib colormap (e.g. "jet", see :mod:`matplotlib.cm`). The default is to use the :attr:`XVG.default_color_cycle`. *kwargs* All other keyword arguments are passed on to :func:`pylab.plot`. """ from itertools import izip, cycle import matplotlib.cm, matplotlib.colors import pylab columns = kwargs.pop('columns', Ellipsis) # slice for everything maxpoints = kwargs.pop('maxpoints', self.maxpoints_default) transform = kwargs.pop('transform', lambda x: x) # default is identity transformation method = kwargs.pop('method', "mean") if columns is Ellipsis or columns is None: columns = numpy.arange(self.array.shape[0]) if len(columns) == 0: raise MissingDataError("plot() needs at least one column of data") if len(self.array.shape) == 1 or self.array.shape[0] == 1: # special case: plot against index; plot would do this automatically but # we'll just produce our own xdata and pretend that this was X all along a = numpy.ravel(self.array) X = numpy.arange(len(a)) a = numpy.vstack((X, a)) columns = [0] + [c+1 for c in columns] else: a = self.array color = kwargs.pop('color', self.default_color_cycle) try: cmap = matplotlib.cm.get_cmap(color) colors = cmap(matplotlib.colors.Normalize()(numpy.arange(len(columns[1:]), dtype=float))) except TypeError: colors = cycle(utilities.asiterable(color)) # (decimate/smooth o slice o transform)(array) a = self.decimate(method, numpy.asarray(transform(a))[columns], maxpoints=maxpoints) # now deal with infs, nans etc AFTER all transformations (needed for plotting across inf/nan) ma = numpy.ma.MaskedArray(a, mask=numpy.logical_not(numpy.isfinite(a))) # finally plot (each column separately to catch empty sets) for column, color in izip(xrange(1,len(columns)), colors): if len(ma[column]) == 0: warnings.warn("No data to plot for column %(column)d" % vars(), category=MissingDataWarning) kwargs['color'] = color pylab.plot(ma[0], ma[column], **kwargs) # plot all other columns in parallel
def __init__(self, molecule=None, **kwargs): """Set up Simulation instance. The *molecule* of the compound molecule should be supplied. Existing files (which have been generated in previous runs) can also be supplied. :Keywords: *molecule* Identifier for the compound molecule. This is the same as the entry in the ``[ molecule ]`` section of the itp file. ["DRUG"] *filename* If provided and *molecule* is ``None`` then load the instance from the pickle file *filename*, which was generated with :meth:`~mdpow.equil.Simulation.save`. *dirname* base directory; all other directories are created under it *forcefield* 'OPLS-AA' or 'CHARMM' or 'AMBER' *solvent* 'water' or 'octanol' or 'cyclohexane' or 'wetoctanol' *solventmodel* ``None`` chooses the default (e.g, :data:`mdpow.forcefields.DEFAULT_WATER_MODEL` for ``solvent == "water"``. Other options are the models defined in :data:`mdpow.forcefields.GROMACS_WATER_MODELS`. At the moment, there are no alternative parameterizations included for other solvents. *mdp* dict with keys corresponding to the stages ``energy_minimize``, ``MD_restrained``, ``MD_relaxed``, ``MD_NPT`` and values *mdp* file names (if no entry then the package defaults are used) *distance* minimum distance between solute and closest box face *kwargs* advanced keywords for short-circuiting; see :data:`mdpow.equil.Simulation.filekeys`. """ self.__cache = {} filename = kwargs.pop('filename', None) dirname = kwargs.pop('dirname', self.dirname_default) forcefield = kwargs.pop('forcefield', 'OPLS-AA') solvent = kwargs.pop('solvent', self.solvent_default) # mdp files --- should get values from default runinput.cfg # None values in the kwarg mdp dict are ignored # self.mdp: key = stage, value = path to MDP file # 'water' will choose the default ('tip4p'), other choices are # 'tip3p', 'spc', 'spce', 'm24', for water; no choices # available for 'cyclohexane' and 'octanol' solventmodel = kwargs.pop('solventmodel', None) mdp_kw = kwargs.pop('mdp', {}) self.mdp = dict((stage, config.get_template(fn)) for stage, fn in self.mdp_defaults.items()) self.mdp.update( dict((stage, config.get_template(fn)) for stage, fn in mdp_kw.items() if fn is not None)) if molecule is None and filename is not None: # load from pickle file self.load(filename) self.filename = filename kwargs = {} # for super else: self.molecule = molecule or 'DRUG' self.dirs = AttributeDict( basedir=realpath(dirname), # .../Equilibrium/<solvent> includes=list(asiterable(kwargs.pop('includes', []))) + [config.includedir], ) # pre-set filenames: keyword == variable name self.files = AttributeDict([(k, kwargs.pop(k, None)) for k in self.filekeys]) self.deffnm = kwargs.pop("deffnm", "md") if self.files.topology: # assume that a user-supplied topology lives in a 'standard' top dir # that includes the necessary itp file(s) self.dirs.topology = realpath( os.path.dirname(self.files.topology)) self.dirs.includes.append(self.dirs.topology) self.forcefield = forcefield self.solvent_type = solvent self.solventmodel_identifier = forcefields.get_solvent_identifier( solvent, model=solventmodel, forcefield=forcefield, ) if self.solventmodel_identifier is None: msg = "No parameters for solvent {0} and solventmodel {1} available.".format( solvent, solventmodel) logger.error(msg) raise ValueError(msg) self.solventmodel = forcefields.get_solvent_model( self.solventmodel_identifier, forcefield=forcefield, ) distance = kwargs.pop('distance', None) distance = distance if distance is not None else DIST[solvent] self.solvent = AttributeDict(itp=self.solventmodel.itp, box=self.solventmodel.coordinates, distance=distance) self.filename = filename or self.solvent_type + '.simulation' super(Simulation, self).__init__(**kwargs)
def __init__(self, **kwargs): """Set up a Simulation object. :Keywords: *sim* Any object that contains the attributes *tpr*, *xtc*, and optionally *ndx* (e.g. :class:`gromacs.cbook.Transformer`). The individual keywords such as *xtc* override the values in *sim*. *tpr* Gromacs tpr file (**required**) *xtc* Gromacs trajectory, can also be a trr (**required**) *edr* Gromacs energy file (only required for some plugins) *ndx* Gromacs index file *absolute* ``True``: Turn file names into absolute paths (typically required for most plugins); ``False`` keep a they are [``True``] *strict* ``True``: missing required file keyword raises a :exc:`TypeError` and missing the file itself raises a :exc:`IOError`. ``False``: missing required files only give a warning. [``True``] *analysisdir* directory under which derived data are stored; defaults to the directory containing the tpr [None] *plugins* : list plugin instances or tuples (*plugin class*, *kwarg dict*) or tuples (*plugin_class_name*, *kwarg dict*) to be used; more can be added later with :meth:`Simulation.add_plugin`. """ logger.info("Loading simulation data") sim = kwargs.pop('sim', None) strict = kwargs.pop('strict', True) def getpop(attr, required=False, strict=strict): """Return attribute from from kwargs or sim or None""" val = kwargs.pop(attr, None) # must pop from kwargs to clean it if not val is None: return val try: return sim.__getattribute__(attr) except AttributeError: if required: errmsg = "Required attribute %r not found in kwargs or sim" % attr if strict: logger.fatal(errmsg) raise TypeError(errmsg) else: logger.warn(errmsg+"... continuing because of strict=False") warnings.warn(errmsg) return None make_absolute = kwargs.pop('absolute', True) def canonical(*args): """Join *args* and get the :func:`os.path.realpath`.""" if None in args: return None if not make_absolute: return os.path.join(*args) return os.path.realpath(os.path.join(*args)) # required files self.tpr = canonical(getpop('tpr', required=True)) self.xtc = canonical(getpop('xtc', required=True)) # optional files self.ndx = canonical(getpop('ndx')) self.edr = canonical(getpop('edr')) # check existence of required files resolve = "exception" if not strict: resolve = "warn" for v in ('tpr', 'xtc'): self.check_file(v, self.__getattribute__(v), resolve=resolve) self.analysis_dir = kwargs.pop('analysisdir', os.path.dirname(self.tpr)) #: Registry for plugins: This dict is central. self.plugins = AttributeDict() #: Use this plugin if none is explicitly specified. Typically set with #: :meth:`~Simulation.set_plugin`. self.default_plugin_name = None # XXX: Or should we simply add instances and then re-register # all instances using register() ? # XXX: ... this API should be cleaned up. It seems to be connected # back and forth in vicious circles. -- OB 2009-07-10 plugins = kwargs.pop('plugins', []) # list of tuples (plugin, kwargs) or just (plugin,) if no kwords # required (eg if plugin is an instance) for x in plugins: try: P, kwargs = asiterable(x) # make sure to wrap strings, especially 2-letter ones! except ValueError: P = x kwargs = {} self.add_plugin(P, **kwargs) # convenience: if only a single plugin was registered we default to that one if len(self.plugins) == 1: self.set_plugin(self.plugins.keys()[0]) # Is this needed? If done properly, kwargs should be empty by now BUT # because the same list is re-used for all plugins I cannot pop them in # the plugins. I don't think multiple inheritance would work with this # setup so let's not pretend it does: hence comment out the super-init # call: ## super(Simulation, self).__init__(**kwargs) logger.info("Simulation instance initialised:") logger.info(str(self))
def __init__(self, **kwargs): """Set up a Simulation object. :Keywords: *sim* Any object that contains the attributes *tpr*, *xtc*, and optionally *ndx* (e.g. :class:`gromacs.cbook.Transformer`). The individual keywords such as *xtc* override the values in *sim*. *tpr* Gromacs tpr file (**required**) *xtc* Gromacs trajectory, can also be a trr (**required**) *edr* Gromacs energy file (only required for some plugins) *ndx* Gromacs index file *absolute* ``True``: Turn file names into absolute paths (typically required for most plugins); ``False`` keep a they are [``True``] *strict* ``True``: missing required file keyword raises a :exc:`TypeError` and missing the file itself raises a :exc:`IOError`. ``False``: missing required files only give a warning. [``True``] *analysisdir* directory under which derived data are stored; defaults to the directory containing the tpr [None] *plugins* : list plugin instances or tuples (*plugin class*, *kwarg dict*) or tuples (*plugin_class_name*, *kwarg dict*) to be used; more can be added later with :meth:`Simulation.add_plugin`. """ logger.info("Loading simulation data") sim = kwargs.pop('sim', None) strict = kwargs.pop('strict', True) def getpop(attr, required=False, strict=strict): """Return attribute from from kwargs or sim or None""" val = kwargs.pop(attr, None) # must pop from kwargs to clean it if val is not None: return val try: return sim.__getattribute__(attr) except AttributeError: if required: errmsg = "Required attribute {0!r} not found in kwargs or sim".format( attr) if strict: logger.fatal(errmsg) raise TypeError(errmsg) else: logger.warn(errmsg + "... continuing because of strict=False") warnings.warn(errmsg) return None make_absolute = kwargs.pop('absolute', True) def canonical(*args): """Join *args* and get the :func:`os.path.realpath`.""" if None in args: return None if not make_absolute: return os.path.join(*args) return os.path.realpath(os.path.join(*args)) # required files self.tpr = canonical(getpop('tpr', required=True)) self.xtc = canonical(getpop('xtc', required=True)) # optional files self.ndx = canonical(getpop('ndx')) self.edr = canonical(getpop('edr')) # check existence of required files resolve = "exception" if not strict: resolve = "warn" for v in ('tpr', 'xtc'): self.check_file(v, self.__getattribute__(v), resolve=resolve) self.analysis_dir = kwargs.pop('analysisdir', os.path.dirname(self.tpr)) #: Registry for plugins: This dict is central. self.plugins = AttributeDict() #: Use this plugin if none is explicitly specified. Typically set with #: :meth:`~Simulation.set_plugin`. self.default_plugin_name = None # XXX: Or should we simply add instances and then re-register # all instances using register() ? # XXX: ... this API should be cleaned up. It seems to be connected # back and forth in vicious circles. -- OB 2009-07-10 plugins = kwargs.pop('plugins', []) # list of tuples (plugin, kwargs) or just (plugin,) if no kwords # required (eg if plugin is an instance) for x in plugins: try: P, kwargs = asiterable( x) # make sure to wrap strings, especially 2-letter ones! except ValueError: P = x kwargs = {} self.add_plugin(P, **kwargs) # convenience: if only a single plugin was registered we default to that one if len(self.plugins) == 1: self.set_plugin(self.plugins.keys()[0]) # Is this needed? If done properly, kwargs should be empty by now BUT # because the same list is re-used for all plugins I cannot pop them in # the plugins. I don't think multiple inheritance would work with this # setup so let's not pretend it does: hence comment out the super-init # call: ## super(Simulation, self).__init__(**kwargs) logger.info("Simulation instance initialised:") logger.info(str(self))
def plot(self, **kwargs): """Plot xvg file data. The first column of the data is always taken as the abscissa X. Additional columns are plotted as ordinates Y1, Y2, ... In the special case that there is only a single column then this column is plotted against the index, i.e. (N, Y). :Keywords: *columns* : list Select the columns of the data to be plotted; the list is used as a numpy.array extended slice. The default is to use all columns. Columns are selected *after* a transform. *transform* : function function ``transform(array) -> array`` which transforms the original array; must return a 2D numpy array of shape [X, Y1, Y2, ...] where X, Y1, ... are column vectors. By default the transformation is the identity [``lambda x: x``]. *maxpoints* : int limit the total number of data points; matplotlib has issues processing png files with >100,000 points and pdfs take forever to display. Set to ``None`` if really all data should be displayed. At the moment we simply decimate the data at regular intervals. [10000] *method* method to decimate the data to *maxpoints*, see :meth:`XVG.decimate` for details *color* single color (used for all plots); sequence of colors (will be repeated as necessary); or a matplotlib colormap (e.g. "jet", see :mod:`matplotlib.cm`). The default is to use the :attr:`XVG.default_color_cycle`. *kwargs* All other keyword arguments are passed on to :func:`pylab.plot`. """ from itertools import izip, cycle import matplotlib.cm, matplotlib.colors import pylab columns = kwargs.pop('columns', Ellipsis) # slice for everything maxpoints = kwargs.pop('maxpoints', self.maxpoints_default) transform = kwargs.pop('transform', lambda x: x) # default is identity transformation method = kwargs.pop('method', "mean") if columns is Ellipsis or columns is None: columns = numpy.arange(self.array.shape[0]) if len(columns) == 0: raise MissingDataError("plot() needs at least one column of data") if len(self.array.shape) == 1 or self.array.shape[0] == 1: # special case: plot against index; plot would do this automatically but # we'll just produce our own xdata and pretend that this was X all along a = numpy.ravel(self.array) X = numpy.arange(len(a)) a = numpy.vstack((X, a)) columns = [0] + [c+1 for c in columns] else: a = self.array color = kwargs.pop('color', self.default_color_cycle) try: cmap = matplotlib.cm.get_cmap(color) colors = cmap(matplotlib.colors.Normalize()(numpy.arange(len(columns[1:]), dtype=float))) except TypeError: colors = cycle(utilities.asiterable(color)) # (decimate/smooth o slice o transform)(array) a = self.decimate(method, numpy.asarray(transform(a))[columns], maxpoints=maxpoints) # now deal with infs, nans etc AFTER all transformations (needed for plotting across inf/nan) ma = numpy.ma.MaskedArray(a, mask=numpy.logical_not(numpy.isfinite(a))) # finally plot (each column separately to catch empty sets) for column, color in izip(xrange(1,len(columns)), colors): if len(ma[column]) == 0: warnings.warn("No data to plot for column {column:d}".format(**vars()), category=MissingDataWarning) kwargs['color'] = color pylab.plot(ma[0], ma[column], **kwargs) # plot all other columns in parallel