Exemple #1
0
    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()
Exemple #2
0
    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)
Exemple #3
0
    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)
Exemple #4
0
    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()
Exemple #5
0
    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
Exemple #6
0
    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)
Exemple #7
0
    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
Exemple #8
0
    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
Exemple #9
0
    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'])
Exemple #11
0
    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)
Exemple #12
0
    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
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #15
0
    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
Exemple #16
0
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)]
Exemple #17
0
    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
Exemple #18
0
    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
Exemple #19
0
    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
Exemple #20
0
    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)
Exemple #22
0
    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'])
Exemple #24
0
    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)
Exemple #25
0
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
Exemple #26
0
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
Exemple #27
0
    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
Exemple #28
0
    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)
Exemple #29
0
    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))
Exemple #30
0
    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))
Exemple #31
0
    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