File: GMS.py

package info (click to toggle)
mdanalysis 2.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 116,696 kB
  • sloc: python: 92,135; ansic: 8,156; makefile: 215; sh: 138
file content (283 lines) | stat: -rw-r--r-- 9,120 bytes parent folder | download
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; -*-
# 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
#
#

"""GAMESS trajectory reader --- :mod:`MDAnalysis.coordinates.GMS`
=================================================================

Resources: the GMS output format is a common output format for different
GAMESS distributions: US-GAMESS, Firefly (PC-GAMESS) and GAMESS-UK.

Current version was approbated with US-GAMESS & Firefly only.

There appears to be no rigid format definition so it is likely users
will need to tweak the :class:`GMSReader`.

.. autoclass:: GMSReader
   :members:

"""
import os
import errno
import re

from . import base
import MDAnalysis.lib.util as util
from MDAnalysis.lib.util import store_init_arguments


class GMSReader(base.ReaderBase):
    """Reads from an GAMESS output file

    :Data:
        ts
          Timestep object containing coordinates of current frame

    :Methods:
        ``len(out)``
          return number of frames in out
        ``for ts in out:``
          iterate through trajectory

    Note
    ----
    :class:`GMSReader` can read both uncompressed (``foo.out``) and
    compressed (``foo.out.bz2`` or ``foo.out.gz``) files;
    decompression is handled on the fly


    .. versionchanged:: 0.11.0
       Frames now 0-based instead of 1-based.
       Added `dt` and `time_offset` keywords (passed to :class:`Timestep`).

    """

    format = "GMS"

    # these are assumed!
    units = {"time": "ps", "length": "Angstrom"}

    @store_init_arguments
    def __init__(self, outfilename, **kwargs):
        super(GMSReader, self).__init__(outfilename, **kwargs)

        # the filename has been parsed to be either b(g)zipped or not
        self.outfile = util.anyopen(self.filename)

        # note that, like for xtc and trr files, _n_atoms and _n_frames are used quasi-private variables
        # to prevent the properties being recalculated
        # this is because there is no indexing so the way it measures the number of frames is to read the whole file!
        self._n_atoms = None
        self._n_frames = None
        self._runtyp = None

        self.ts = self._Timestep(0)  # need for properties initial calculations

        # update runtyp property
        self.runtyp
        if not self.runtyp in ["optimize", "surface"]:
            raise AttributeError("Wrong RUNTYP= " + self.runtyp)

        self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs)
        # update n_frames property
        self.n_frames

        # Read in the first timestep
        self._read_next_timestep()

    @property
    def runtyp(self):
        """RUNTYP property of the GAMESS run"""
        if self._runtyp is not None:  # return cached value
            return self._runtyp
        try:
            self._runtyp = self._determine_runtyp()
        except IOError:
            return 0
        else:
            return self._runtyp

    def _determine_runtyp(self):
        with util.openany(self.filename) as out:
            for line in out:
                m = re.match(r"^.*RUNTYP=([A-Z]+)\s+.*", line)
                if m is not None:
                    res = m.group(1).lower()
                    break

        return res

    @property
    def n_atoms(self):
        """number of atoms in a frame"""
        if self._n_atoms is not None:  # return cached value
            return self._n_atoms
        try:
            self._n_atoms = self._read_out_natoms()
        except IOError:
            return 0
        else:
            return self._n_atoms

    def _read_out_natoms(self):
        with util.openany(self.filename) as out:
            for line in out:
                m = re.match(
                    r"\s*TOTAL NUMBER OF ATOMS\s*=\s*([0-9]+)\s*", line
                )
                if m is not None:
                    res = int(m.group(1))
                    break

        return res

    @property
    def n_frames(self):
        if self._n_frames is not None:  # return cached value
            return self._n_frames
        try:
            self._n_frames = self._read_out_n_frames()
        except IOError:
            return 0
        else:
            return self._n_frames

    def _read_out_n_frames(self):
        if self.runtyp == "optimize":
            trigger = re.compile(b"^.NSERCH=.*")
        elif self.runtyp == "surface":
            trigger = re.compile(b"^.COORD 1=.*")

        self._offsets = offsets = []
        with util.openany(self.filename, "rb") as out:
            line = True
            while not line == b"":  # while not EOF
                line = out.readline()
                if re.match(trigger, line):
                    offsets.append(out.tell() - len(line))

        return len(offsets)

    def _read_frame(self, frame):
        self.outfile.seek(self._offsets[frame])
        self.ts.frame = frame - 1  # gets +1'd in _read_next
        return self._read_next_timestep()

    def _read_next_timestep(self, ts=None):
        # check that the timestep object exists
        if ts is None:
            ts = self.ts
        # check that the outfile object exists; if not reopen the trajectory
        if self.outfile is None:
            self.open_trajectory()
        x = []
        y = []
        z = []

        flag = 0
        counter = 0

        for line in self.outfile:
            if self.runtyp == "optimize":
                if (flag == 0) and (
                    re.match(r"^.NSERCH=.*", line) is not None
                ):
                    flag = 1
                    continue
                if (flag == 1) and (
                    re.match(r"^ COORDINATES OF ALL ATOMS ARE ", line)
                    is not None
                ):
                    flag = 2
                    continue
                if (flag == 2) and (re.match(r"^\s*-+\s*", line) is not None):
                    flag = 3
                    continue
                if flag == 3 and counter < self.n_atoms:
                    words = line.split()
                    x.append(float(words[2]))
                    y.append(float(words[3]))
                    z.append(float(words[4]))
                    counter += 1

            elif self.runtyp == "surface":
                if (flag == 0) and (
                    re.match(r"^.COORD 1=\s*([-]?[0-9]+\.[0-9]+).*", line)
                    is not None
                ):
                    flag = 1
                    continue
                if (flag == 1) and (
                    re.match(
                        r"^\s*HAS ENERGY VALUE\s*([-]?[0-9]+\.[0-9]+)\s*", line
                    )
                    is not None
                ):
                    flag = 3
                    continue
                if flag == 3 and counter < self.n_atoms:
                    words = line.split()
                    x.append(float(words[1]))
                    y.append(float(words[2]))
                    z.append(float(words[3]))
                    counter += 1

            # stop when the cursor has reached the end of that block
            if counter == self._n_atoms:
                ts._x[:] = (
                    x  # more efficient to do it this way to avoid re-creating the numpy arrays
                )
                ts._y[:] = y
                ts._z[:] = z
                # print ts.frame
                ts.frame += 1
                return ts

        raise EOFError

    def _reopen(self):
        self.close()
        self.open_trajectory()

    def open_trajectory(self):
        if self.outfile is not None:
            raise IOError(
                errno.EALREADY, "GMS file already opened", self.filename
            )
        if not os.path.exists(self.filename):
            # must check; otherwise might segmentation fault
            raise IOError(errno.ENOENT, "GMS file not found", self.filename)

        self.outfile = util.anyopen(self.filename)

        # reset ts
        ts = self.ts
        ts.frame = -1
        return self.outfile

    def close(self):
        """Close out trajectory file if it was open."""
        if self.outfile is None:
            return
        self.outfile.close()
        self.outfile = None