1
2
3
4
5
6 """
7 :mod:`gromacs.analysis.plugins.gridmatmd` --- Lipid bilayer analysis helper
8 ===========================================================================
9
10 This helper module contains code to drive ``GridMAT-MD.pl``, available
11 from the `GridMAT-MD`_ home page and written by WJ Allen et al
12 [Allen2009]_ . The GromacsWrapper distribution comes with version
13 1.0.2 of ``GridMAT-MD.pl`` and includes a small patch so that it can
14 accept filenames on the command line.
15
16
17
18 References
19 ----------
20
21 .. [Allen2009] W. J. Allen, J. A. Lemkul, and D. R. Bevan. (2009) "GridMAT-MD: A
22 Grid-based Membrane Analysis Tool for Use With Molecular Dynamics."
23 J. Comput. Chem. 30 (12): 1952-1958.
24
25 .. _GridMAT-MD: http://www.bevanlab.biochem.vt.edu/GridMAT-MD/index.html
26
27 Module contents
28 ---------------
29
30 .. autoclass:: GridMatMD
31 :members:
32
33 .. autoclass:: GridMatData
34 :members:
35 :inherited-members:
36
37 .. autoclass:: Grid2D
38 :members: imshow, _midpoints, __add__, __sub__, __mul__, __div__
39
40 """
41 from __future__ import with_statement
42
43 import sys
44 import subprocess
45 import re
46 import glob
47 import cPickle
48 import numpy
49
50 import gromacs.tools
51
53 """Analysis of lipid bilayers with GridMAT-MD.
54
55 It requires a configuration file and a list of structure files
56 (gro or pdb) as input. See the documentation (pdf_) for the format of the
57 config file. Note that the *bilayer* keyword will be ignored in
58 the config file.
59
60 .. _pdf: http://www.bevanlab.biochem.vt.edu/GridMAT-MD/doc/GridMAT-MD_ug_v1.0.2.pdf
61 """
62
63
64 pNX = re.compile("There are (?P<NX>\d+) grid points in the X direction, "
65 "spaced every (?P<DX>[0-9.]+) nanometers")
66 pNY = re.compile("There are (?P<NY>\d+) grid points in the Y direction, "
67 "spaced every (?P<DY>[0-9.]+) nanometers")
68 pTOP = re.compile('The top leaflet "thickness" will be printed to (?P<FILENAME>.*_top_.*\.dat)')
69 pAVG = re.compile('The average bilayer "thickness" will be printed to (?P<FILENAME>.*_average_.*\.dat)')
70 pBOT = re.compile('The bottom leaflet "thickness" will be printed to (?P<FILENAME>.*_bottom_.*\.dat)')
71
72
74 """Set up GridMAT-MD analysis.
75
76 :Arguments:
77 *config* : filename
78 input file for GridMAT-MD (see docs)
79 *filenames* : list or glob-pattern
80 list of gro or pdb files, or a glob pattern that creates
81 such a list
82 """
83 self.config = config
84 if type(filenames) is str:
85
86 _filenames = glob.glob(filenames)
87 else:
88 _filenames = filenames
89
90 self.filenames = _filenames
91
92 self.nx = None
93
94 self.ny = None
95
96 self.dx = None
97
98 self.dy = None
99
100
101 self.thickness = {}
102
103 self.averages = {}
104
105
106
107 self.GridMatMD = gromacs.tools.GridMAT_MD(self.config)
108
110 """Run GridMAT-MD on a single *frame* and return results.
111
112 :Arguments: *frame* is a filename (gro or pdb)
113 :Returns: a dict of :class:`GridMapData` objects; the keys
114 are "top", "bottom", "average"
115 """
116
117 thickness = {}
118 print >> sys.stdout, "Analyzing frame %r (takes a few seconds)..." % frame
119 sys.stdout.flush()
120 rc,output,stderr = self.GridMatMD(frame, stdout=False)
121 for line in output.split('\n'):
122 print >> sys.stdout, '>>> '+line
123 m = self.pNX.match(line)
124 if m:
125 self.nx, self.dx = int(m.group('NX')), float(m.group('DX'))
126 continue
127 m = self.pNY.match(line)
128 if m:
129 self.ny, self.dy = int(m.group('NY')), float(m.group('DY'))
130 continue
131 m = self.pTOP.match(line)
132 if m:
133 thickness['top'] = m.group('FILENAME')
134 continue
135 m = self.pBOT.match(line)
136 if m:
137 thickness['bottom'] = m.group('FILENAME')
138 continue
139 m = self.pAVG.match(line)
140 if m:
141 thickness['average'] = m.group('FILENAME')
142 continue
143
144 d = {}
145 for name, filename in thickness.items():
146 d[name] = GridMatData(filename, shape=(self.nx, self.ny),
147 delta=(self.dx, self.dy))
148 self.thickness = d
149 return d
150
152 """Run analysis on all files and average results."""
153
154 results = {}
155 for f in self.filenames:
156 d = self.run_frame(f)
157 for name,grid2d in d.items():
158 try:
159 results[name].append(grid2d)
160 except KeyError:
161 results[name] = [grid2d]
162
163
164 self.averages = dict([(name,numpy.mean(v)) for name,v in results.items()])
165
166 - def imshow(self, name, **kwargs):
167 """Display array *name* with ``pylab.imshow``."""
168 from pylab import xlabel, ylabel
169 try:
170 self.averages[name].imshow(**kwargs)
171 except KeyError:
172 raise KeyError("name must be one of %r" % self.arrays.keys())
173 xlabel(r'$x/$nm')
174 ylabel(r'$y/$nm')
175
176 - def save(self, name):
177 """Save object as pickle."""
178 with open(name, 'wb') as f:
179 cPickle.dump(self, f, protocol=cPickle.HIGHEST_PROTOCOL)
180
182 """Represents a 2D array with bin sizes.
183
184 Addition and subtraction of grids is defined for the arrays and
185 the bins. Multiplication and division with scalars is also
186 defined. Each operation returns a new :class:`Grid2D` object.
187
188 (Actually, it should work for arrays of any dimension, not just 2D.)
189 """
190
192 """Initialize the Grid2D instance.
193
194 :Arguments:
195 *data*
196 array data, e.g. a list of array
197 *bins*
198 tuple of lists of bin **edges**, one for each dimension
199 """
200 self.array = numpy.asarray(data)
201 self.shape = self.array.shape
202 self.ndim = len(self.shape)
203 self.bins = bins
204 if len(self.bins) != self.ndim:
205 raise ValueError("bins must be a list of bin edges, one for each dimension, D=%(ndim)d" %
206 vars(self))
207 self.midpoints = [self._midpoints(x) for x in self.bins]
208
210 _x = numpy.asarray(x)
211 return 0.5*(_x[1:] + _x[:-1])
212
214 """Display data as a 2D image using :func:`pylab.imshow`."""
215 import pylab
216
217 extent = numpy.concatenate([self.bins[0][[0,-1]], self.bins[1][[0,-1]]])
218 kwargs.setdefault('extent', extent)
219 kwargs['origin'] = 'lower'
220 pylab.imshow(self.array, **kwargs)
221
223 """Add arrays and bins (really only makes sense when averaging)."""
224 if self.array.shape != other.array.shape:
225 raise TypeError("arrays are of incompatible shape")
226 _bins = [self.bins[dim] + other.bins[dim] for dim in xrange(len(self.bins))]
227 _array = self.array + other.array
228 return Grid2D(data=_array, bins=_bins)
229
231 """Subtract other from self (also subtracts bins... which is odd but consistent)."""
232 if self.array.shape != other.array.shape:
233 raise TypeError("arrays are of incompatible shape")
234 _bins = [self.bins[dim] - other.bins[dim] for dim in xrange(len(self.bins))]
235 _array = self.array - other.array
236 return Grid2D(data=_array, bins=_bins)
237
239 """Multiply arrays (and bins) by a scalar *x*."""
240 _bins = [self.bins[dim] * x for dim in xrange(len(self.bins))]
241 _array = self.array * x
242 return Grid2D(data=_array, bins=_bins)
243
245 """Divide arrays (and bins) by a scalar *x*."""
246 _bins = [self.bins[dim] / x for dim in xrange(len(self.bins))]
247 _array = self.array / x
248 return Grid2D(data=_array, bins=_bins)
249
250
251
253 """Represent GridMatMD data file.
254
255 The loaded array data is accessible as a numpy array in
256 :attr:`GridMatData.array` and bins and midpoints as
257 :attr:`GridMatData.bins` and :attr:`GridMatData.midpoints`
258 respectively.
259 """
260
261 DATANAME = re.compile("""
262 (?P<NX>\d+) # number of grid points in X
263 x
264 (?P<NY>\d+) # number of grid points in Y
265 _(top|bottom|average)_.*\.dat # all the rest
266 """, re.VERBOSE)
267
268
269 - def __init__(self, filename, shape=None, delta=None):
270 """Load the data into a numpy array.
271
272 The *filename* is an output file from GridMAT-MD. *shape* and
273 *delta* are optional. The *shape* of the array is parsed from
274 the filename if not provided. The spacing is set to (1,1) if
275 not provided.
276
277 :Arguments:
278 *filename*
279 2D grid as written by GridMAT-MD
280 *shape*
281 Shape tuple (NX, NY) of the array in filename.
282 *delta*
283 Tuple of bin sizes of grid (DX, DY).
284 """
285 self.filename = filename
286 self.shape = shape
287 if shape is None:
288 self.shape = self.parse_filename(filename)
289 if delta is None:
290 self.delta = tuple([1.0] * len(self.shape))
291 else:
292 self.delta = tuple(delta)
293
294
295 _array = numpy.loadtxt(self.filename).reshape(self.shape)
296
297
298 _bins = [numpy.linspace(0,n*dx,n+1) for n,dx in zip(self.shape, self.delta)]
299
300 Grid2D.__init__(self, data=_array, bins=_bins)
301
302
303
304
306 """Get dimensions from filename"""
307 m = self.DATANAME.match(filename)
308 if m is None:
309 raise ValueError("filename %s does not appear to be a standard GridMat-MD output filename")
310 return map(int, m.group('NX', 'NY'))
311