1# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
2# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
3#
4# MDAnalysis --- https://www.mdanalysis.org
5# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
6# (see the file AUTHORS for the full list of names)
7#
8# Released under the GNU Public Licence, v2 or any higher version
9#
10# Please cite your use of MDAnalysis in published work:
11#
12# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
13# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
14# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
15# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
16# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
17#
18# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
19# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
20# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
21#
22
23"""
24PDBQT topology parser
25=====================
26
27Use a PDBQT_ file to build a minimum internal structure representation (list of
28atoms), including AutoDock_ atom types (stored as :attr:`Atom.type`) and
29partial charges (:attr:`Atom.charge`).
30
31* Reads a PDBQT file line by line and does not require sequential atom numbering.
32* Multi-model PDBQT files are not supported.
33
34Notes
35-----
36Only reads atoms and their names; connectivity is not
37deduced. Masses are guessed and set to 0 if unknown.
38
39
40See Also
41--------
42`MDAnalysis.coordinates.PDBQT`
43
44
45Classes
46-------
47.. autoclass:: PDBQTParser
48   :members:
49   :inherited-members:
50
51
52.. _PDBQT:
53   http://autodock.scripps.edu/faqs-help/faq/what-is-the-format-of-a-pdbqt-file
54.. _AutoDock:
55   http://autodock.scripps.edu/
56"""
57from __future__ import absolute_import
58
59import numpy as np
60
61from . import guessers
62from ..lib import util
63from .base import TopologyReaderBase, change_squash
64from ..core.topology import Topology
65from ..core.topologyattrs import (
66    Atomids,
67    Atomnames,
68    AltLocs,
69    Atomtypes,
70    Charges,
71    Masses,
72    Occupancies,
73    RecordTypes,
74    Resids,
75    Resnums,
76    Resnames,
77    Segids,
78    Tempfactors,
79)
80
81
82class PDBQTParser(TopologyReaderBase):
83    """Read topology from a PDBQT file.
84
85    Creates the following Attributes:
86     - atom ids (serial)
87     - atom types
88     - atom names
89     - altLocs
90     - resnames
91     - chainIDs (becomes segid)
92     - resids
93     - record_types (ATOM/HETATM)
94     - icodes
95     - occupancies
96     - tempfactors
97     - charges
98
99    Guesses the following:
100     - elements
101     - masses
102
103    .. versionchanged:: 0.18.0
104       Added parsing of Record types
105    """
106    format = 'PDBQT'
107
108    def parse(self, **kwargs):
109        """Parse atom information from PDBQT file *filename*.
110
111        Returns
112        -------
113        MDAnalysis Topology object
114        """
115        record_types = []
116        serials = []
117        names = []
118        altlocs = []
119        resnames = []
120        chainids = []
121        resids = []
122        icodes = []
123        occupancies = []
124        tempfactors = []
125        charges = []
126        atomtypes = []
127
128        with util.openany(self.filename) as f:
129            for line in f:
130                line = line.strip()
131                if not line.startswith(('ATOM', 'HETATM')):
132                    continue
133                record_types.append(line[:6].strip())
134                serials.append(int(line[6:11]))
135                names.append(line[12:16].strip())
136                altlocs.append(line[16:17].strip())
137                resnames.append(line[17:21].strip())
138                chainids.append(line[21:22].strip())
139                resids.append(int(line[22:26]))
140                icodes.append(line[26:27].strip())
141                occupancies.append(float(line[54:60]))
142                tempfactors.append(float(line[60:66]))
143                charges.append(float(line[66:76]))
144                atomtypes.append(line[77:80].strip())
145
146        n_atoms = len(serials)
147
148        masses = guessers.guess_masses(atomtypes)
149
150        attrs = []
151        for attrlist, Attr, dtype in (
152                (record_types, RecordTypes, object),
153                (serials, Atomids, np.int32),
154                (names, Atomnames, object),
155                (altlocs, AltLocs, object),
156                (occupancies, Occupancies, np.float32),
157                (tempfactors, Tempfactors, np.float32),
158                (charges, Charges, np.float32),
159                (atomtypes, Atomtypes, object),
160        ):
161            attrs.append(Attr(np.array(attrlist, dtype=dtype)))
162        attrs.append(Masses(masses, guessed=True))
163
164        resids = np.array(resids, dtype=np.int32)
165        icodes = np.array(icodes, dtype=object)
166        resnames = np.array(resnames, dtype=object)
167        chainids = np.array(chainids, dtype=object)
168
169        residx, (resids, icodes, resnames, chainids) = change_squash(
170            (resids, icodes), (resids, icodes, resnames, chainids))
171        n_residues = len(resids)
172        attrs.append(Resids(resids))
173        attrs.append(Resnums(resids.copy()))
174        attrs.append(Resnames(resnames))
175
176        segidx, (segids,) = change_squash((chainids,), (chainids,))
177        n_segments = len(segids)
178        attrs.append(Segids(segids))
179
180        top = Topology(n_atoms, n_residues, n_segments,
181                       attrs=attrs,
182                       atom_resindex=residx,
183                       residue_segindex=segidx)
184
185        return top
186