# -*- 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 GNU Public Licence, v2 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.
#
# 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
#
"""
PQR file format --- :mod:`MDAnalysis.coordinates.PQR`
=====================================================
"""
from __future__ import absolute_import
from six.moves import zip
import itertools
import numpy as np
import warnings
from ..core import flags
from ..lib import util
from . import base
[docs]class PQRReader(base.SingleFrameReaderBase):
"""Read a PQR_ file into MDAnalysis.
.. _PQR:
http://www.poissonboltzmann.org/file-formats/biomolecular-structurw/pqr
.. versionchanged:: 0.11.0
Frames now 0-based instead of 1-based
"""
format = 'PQR'
units = {'time': None, 'length': 'Angstrom'}
def _read_first_frame(self):
coords = []
unitcell = np.zeros(6, dtype=np.float32)
with util.openany(self.filename) as pqrfile:
for line in pqrfile:
if line.startswith(('ATOM', 'HETATM')):
fields = line.split()
# convert all entries at the end once for optimal speed
coords.append(fields[-5:-2])
self.n_atoms = len(coords)
self.ts = self._Timestep.from_coordinates(
coords, **self._ts_kwargs)
self.ts._unitcell[:] = unitcell
self.ts.frame = 0 # 0-based frame number
if self.convert_units:
# in-place !
self.convert_pos_from_native(self.ts._pos)
self.convert_pos_from_native(self.ts._unitcell[:3])
[docs] def Writer(self, filename, **kwargs):
"""Returns a PQRWriter for *filename*.
Parameters
----------
filename : str
filename of the output PQR file
Returns
-------
:class:`PQRWriter`
"""
return PQRWriter(filename, **kwargs)
[docs]class PQRWriter(base.WriterBase):
"""Write a single coordinate frame in whitespace-separated PQR format.
Charges ("Q") are taken from the
:attr:`MDAnalysis.core.groups.Atom.charge` attribute while
radii are obtaine from the
:attr:`MDAnalysis.core.groups.Atom.radius` attribute.
* If the segid is 'SYSTEM' then it will be set to the empty
string. Otherwise the first letter will be used as the chain ID.
* The serial number always starts at 1 and increments sequentially
for the atoms.
The output format is similar to ``pdb2pqr --whitespace``.
.. versionadded:: 0.9.0
"""
format = 'PQR'
units = {'time': None, 'length': 'Angstrom'}
# serial, atomName, residueName, chainID, residueNumber, XYZ, charge, radius
fmt_ATOM = ("ATOM {serial:6d} {name:<4} {resname:<3} {chainid:1.1}"
" {resid:4d} {pos[0]:-8.3f} {pos[1]:-8.3f}"
" {pos[2]:-8.3f} {charge:-7.4f} {radius:6.4f}\n")
fmt_remark = "REMARK {0} {1}\n"
def __init__(self, filename, convert_units=None, **kwargs):
"""Set up a PQRWriter with full whitespace separation.
Parameters
----------
filename : str
output filename
remarks : str (optional)
remark lines (list of strings) or single string to be added to the
top of the PQR file
"""
self.filename = util.filename(filename, ext='pqr')
if convert_units is None:
convert_units = flags['convert_lengths']
self.convert_units = convert_units # convert length and time to base units
self.remarks = kwargs.pop('remarks', "PQR file written by MDAnalysis")
[docs] def write(self, selection, frame=None):
"""Write selection at current trajectory frame to file.
Parameters
----------
selection : AtomGroup or Universe
MDAnalysis AtomGroup or Universe
frame : int (optional)
optionally move to frame index `frame`; by default, write the
current frame
.. versionchanged:: 0.11.0
Frames now 0-based instead of 1-based
"""
# write() method that complies with the Trajectory API
u = selection.universe
if frame is not None:
u.trajectory[frame] # advance to frame
else:
try:
frame = u.trajectory.ts.frame
except AttributeError:
frame = 0 # should catch cases when we are analyzing a single frame(?)
atoms = selection.atoms # make sure to use atoms (Issue 46)
coordinates = atoms.positions # can write from selection == Universe (Issue 49)
if self.convert_units:
self.convert_pos_to_native(coordinates) # inplace because coordinates is already a copy
# Check atom attributes
# required:
# - name
# - resname
# - chainid
# - resid
# - position
# - charge
# - radius
attrs = {}
missing_topology = []
for attr, dflt in (
('names', itertools.cycle(('X',))),
('resnames', itertools.cycle(('UNK',))),
('resids', itertools.cycle((1,))),
('charges', itertools.cycle((0.0,))),
('radii', itertools.cycle((1.0,))),
):
try:
attrs[attr] = getattr(atoms, attr)
except AttributeError:
attrs[attr] = dflt
missing_topology.append(attr)
# chainids require special handling
# try chainids, then segids
# if neither, use ' '
# if 'SYSTEM', use ' '
try:
attrs['chainids'] = atoms.chainids
except AttributeError:
try:
attrs['chainids'] = atoms.segids
except AttributeError:
pass
if not 'chainids' in attrs or all(attrs['chainids'] == 'SYSTEM'):
attrs['chainids'] = itertools.cycle((' ',))
if 'charges' in missing_topology:
total_charge = 0.0
else:
total_charge = atoms.total_charge()
if missing_topology:
warnings.warn(
"Supplied AtomGroup was missing the following attributes: "
"{miss}. These will be written with default values. "
"".format(miss=', '.join(missing_topology)))
with util.openany(self.filename, 'w') as pqrfile:
# Header / Remarks
# The *remarknumber* is typically 1 but :program:`pdb2pgr`
# also uses 6 for the total charge and 5 for warnings.
for rem in util.asiterable(self.remarks):
pqrfile.write(self.fmt_remark.format(rem, 1))
pqrfile.write(self.fmt_remark.format(
"Input: frame {0} of {1}".format(frame, u.trajectory.filename),
5))
pqrfile.write(self.fmt_remark.format(
"total charge: {0:+8.4f} e".format(total_charge), 6))
# Atom descriptions and coords
for atom_index, (pos, name, resname, chainid, resid, charge, radius) in enumerate(zip(
coordinates, attrs['names'], attrs['resnames'], attrs['chainids'],
attrs['resids'], attrs['charges'], attrs['radii']), start=1):
# pad so that only 4-letter atoms are left-aligned
name = " " + name if len(name) < 4 else name
pqrfile.write(self.fmt_ATOM.format(
serial=atom_index, name=name, resname=resname,
chainid=chainid, resid=resid, pos=pos, charge=charge,
radius=radius))