1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- https://www.mdanalysis.org
# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
# (see the file AUTHORS for the full list of names)
#
# Released under the Lesser GNU Public Licence, v2.1 or any higher version
#
# Please cite your use of MDAnalysis in published work:
#
# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
# doi: 10.25080/majora-629e541a-00e
#
# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
#
"""
Base classes for the selection writers
======================================
Specialized SelectionWriters are derived from
:class:`SelectionWriterBase`. Override the :meth:`~SelectionWriterBase._write_head`,
:meth:`~SelectionWriterBase._translate`, and :meth:`~SelectionWriterBase._write_tail`
methods.
.. autoclass:: SelectionWriterBase
:members: __init__, write, _translate, _write_head, _write_tail, comment
.. autofunction:: join
"""
import os.path
from ..lib import util
from . import _SELECTION_WRITERS
def join(seq, string="", func=None):
"""Create a list from sequence.
*string* is appended to each element but the last.
*func* is applied to every element before appending *string*.
"""
if func is None:
func = lambda x: x
return [func(x) + string for x in seq[:-1]] + [func(seq[-1])]
class _Selectionmeta(type):
# Auto register upon class creation
def __init__(cls, name, bases, classdict):
type.__init__(type, name, bases, classdict)
try:
fmt = util.asiterable(classdict["format"])
except KeyError:
pass
else:
for f in fmt:
if f is None:
continue
f = f.upper()
_SELECTION_WRITERS[f] = cls
class SelectionWriterBase(metaclass=_Selectionmeta):
"""Export a selection in MDAnalysis to a format usable in an external package.
The :class:`SelectionWriterBase` writes a selection string to a file
that can be used in another package such as `VMD`_, `PyMOL`_,
`Gromacs`_ or `CHARMM`_. In this way, analysis and visualization
can be done with the best or most convenient tools at hand.
:class:`SelectionWriterBase` is a base class and child classes are
derived with the appropriate customizations for the package file
format.
.. _VMD: http://www.ks.uiuc.edu/Research/vmd/
.. _PyMol: http://www.pymol.org/
.. _CHARMM: http://www.charmm.org/
.. _Gromacs: http://www.gromacs.org/
.. versionchanged:: 0.11.0
Can now also write to a :class:`~MDAnalysis.lib.util.NamedStream` instead
of a normal file (using :class:`~MDAnalysis.lib.util.openany`).
.. versionchanged:: 0.16.0
Remove the `wa` mode. The file is now open when the instance is created
and closed with the :meth:`close` method or when exiting the `with`
statement.
"""
#: Name of the format.
format = None
#: Extension of output files.
ext = None
#: Special character to continue a line across a newline.
continuation = ""
#: Comment format string; should contain '%s' or ``None`` for no comments.
commentfmt = None
default_numterms = 8
def __init__(
self, filename, mode="w", numterms=None, preamble=None, **kwargs
):
"""Set up for writing to *filename*.
Parameters
----------
filename:
output file
mode:
create a new file ("w"), or append ("a") to existing file ["w"]
numterms:
number of individual index numbers per line for output
formats that write multiple entries in one line. If set
to 0 or ``False`` then no special formatting is done [8]
preamble:
string that is written as a comment at the top of the file []
kwargs:
use as defaults for :meth:`write`
"""
self.filename = util.filename(filename, ext=self.ext)
if not mode in ("a", "w"):
raise ValueError(
"mode must be one of 'w', 'a', not {0!r}".format(mode)
)
self.mode = mode
self._current_mode = mode[0]
if numterms is None or numterms < 0:
self.numterms = self.default_numterms
elif numterms is False:
self.numterms = 0
else:
self.numterms = numterms
self.preamble = preamble
self.otherargs = kwargs # hack
self.number = 0
self._outfile = util.anyopen(self.filename, mode=self._current_mode)
self.write_preamble()
def comment(self, s):
"""Return string *s* interpolated into the comment format string.
If no :attr:`SelectionWriterBase.commentfmt` is defined (None) then the
empty string is returned because presumably there is no way to enter
comments into the file.
A newline is appended to non-empty strings.
"""
if self.commentfmt is None:
return ""
return self.commentfmt % s + "\n"
def write_preamble(self):
"""Write a header, depending on the file format."""
if self.preamble is None:
return
self._outfile.write(self.comment(self.preamble))
def write(self, selection, number=None, name=None, frame=None, mode=None):
"""Write selection to the output file.
Parameters
----------
selection:
a :class:`MDAnalysis.core.groups.AtomGroup`
number:
selection will be named "mdanalysis<number>"
(``None`` auto increments between writes; useful
when appending) [``None``]
name:
selection will be named *name* (instead of numbered) [``None``]
frame:
write selection of this frame (or the current one if
``None`` [``None``]
"""
u = selection.universe
if frame is not None:
u.trajectory[frame] # advance to frame
else:
try:
frame = u.trajectory.ts.frame
except AttributeError:
frame = 1 # should catch cases when we are analyzing a single PDB (?)
name = name or self.otherargs.get("name", None)
if name is None:
if number is None:
self.number += 1
number = self.number
name = "mdanalysis{number:03d}".format(**vars())
# build whole selection in one go (cleaner way to deal with
# to deal with line breaks after self.numterms entries)
# selection_list must contain entries to be joined with spaces or linebreaks
selection_terms = self._translate(selection.atoms)
step = self.numterms or len(selection.atoms)
out = self._outfile
self._write_head(out, name=name)
for iatom in range(0, len(selection.atoms), step):
line = selection_terms[iatom : iatom + step]
out.write(" ".join(line))
if len(line) == step and not iatom + step == len(selection.atoms):
out.write(" " + self.continuation + "\n")
out.write(
" "
) # safe so that we don't have to put a space at the start of tail
self._write_tail(out)
out.write("\n") # always terminate with newline
def close(self):
"""Close the file
.. versionadded:: 0.16.0
"""
self._outfile.close()
def _translate(self, atoms, **kwargs):
"""Translate atoms into a list of native selection terms.
- build list of ALL selection terms as if this was a single line, e.g.
``['index 12 |', 'index 22 |', 'index 33']``
- only one term per atom!!
- terms *must* be strings
- something like::
" ".join(terms)
must work
"""
raise NotImplementedError
def _write_head(self, out, **kwargs):
"""Initial output to open file object *out*."""
pass # pylint: disable=unnecessary-pass
def _write_tail(self, out, **kwargs):
"""Last output to open file object *out*."""
pass # pylint: disable=unnecessary-pass
# Context manager support to match Coordinate writers
# all file handles use a with block in their write method, so these do nothing special
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
|