Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DATAWriter, CRD, PQR, and PDBQT compressed file writing #4163

Merged
merged 12 commits into from
Jun 18, 2023
2 changes: 2 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The rules for this file:

Fixes
* Fix deletion of bond/angle/dihedral types (PR #4003, Issue #4001)
* CRD, PQR, and PDBQT writers now write BZ2 and GZ files if requested
(PR #4163, Issue #4159)

Enhancements

Expand Down
7 changes: 5 additions & 2 deletions package/MDAnalysis/coordinates/CRD.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ class CRDWriter(base.WriterBase):
.. versionchanged:: 2.2.0
CRD extended format can now be explicitly requested with the
`extended` keyword
.. versionchanged:: 2.6.0
Files are now writen in `wt` mode, and keep extensions, allowing
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
for files to be written under compressed formats
"""
format = 'CRD'
units = {'time': None, 'length': 'Angstrom'}
Expand Down Expand Up @@ -165,7 +168,7 @@ def __init__(self, filename, **kwargs):
.. versionadded:: 2.2.0
"""

self.filename = util.filename(filename, ext='crd')
self.filename = util.filename(filename, ext='crd', keep=True)
self.crd = None

# account for explicit crd format, if requested
Expand Down Expand Up @@ -247,7 +250,7 @@ def write(self, selection, frame=None):
"{miss}. These will be written with default values. "
"".format(miss=', '.join(missing_topology)))

with util.openany(self.filename, 'w') as crd:
with util.openany(self.filename, 'wt') as crd:
# Write Title
crd.write(self.fmt['TITLE'].format(
frame=frame, where=u.trajectory.filename))
Expand Down
4 changes: 2 additions & 2 deletions package/MDAnalysis/coordinates/LAMMPS.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def __init__(self, filename, convert_units=True, **kwargs):
convert_units : bool, optional
units are converted to the MDAnalysis base format; [``True``]
"""
self.filename = util.filename(filename, ext='data')
self.filename = util.filename(filename, ext='data', keep=True)

self.convert_units = convert_units

Expand Down Expand Up @@ -419,7 +419,7 @@ def write(self, selection, frame=None):
has_velocities = True

features = {}
with util.openany(self.filename, 'w') as self.f:
with util.openany(self.filename, 'wt') as self.f:
self.f.write('LAMMPS data file via MDAnalysis\n')
self.f.write('\n')
self.f.write('{:>12d} atoms\n'.format(len(atoms)))
Expand Down
7 changes: 6 additions & 1 deletion package/MDAnalysis/coordinates/NAMDBIN.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ def Writer(self, filename, **kwargs):
class NAMDBINWriter(base.WriterBase):
"""Writer for NAMD binary coordinate files.



Note
----
* Does not handle writing to bz2 or gz compressed file types.


.. versionadded:: 1.0.0
"""
format = ['COOR', 'NAMDBIN']
Expand Down
9 changes: 7 additions & 2 deletions package/MDAnalysis/coordinates/PDBQT.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ class PDBQTWriter(base.WriterBase):

.. _PDB: http://www.wwpdb.org/documentation/file-format-content/format32/v3.2.html
.. _PDBQT: http://autodock.scripps.edu/faqs-help/faq/what-is-the-format-of-a-pdbqt-file


.. versionchanged:: 2.6.0
Files are now writen in `wt` mode, and keep extensions, allowing
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
for files to be written under compressed formats
"""

fmt = {
Expand All @@ -213,8 +218,8 @@ class PDBQTWriter(base.WriterBase):
pdb_coor_limits = {"min": -999.9995, "max": 9999.9995}

def __init__(self, filename, **kwargs):
self.filename = util.filename(filename, ext='pdbqt')
self.pdb = util.anyopen(self.filename, 'w')
self.filename = util.filename(filename, ext='pdbqt', keep=True)
self.pdb = util.anyopen(self.filename, 'wt')

def close(self):
self.pdb.close()
Expand Down
8 changes: 6 additions & 2 deletions package/MDAnalysis/coordinates/PQR.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ class PQRWriter(base.WriterBase):

The output format is similar to ``pdb2pqr --whitespace``.


.. versionadded:: 0.9.0
.. versionchanged:: 2.6.0
Files are now writen in `wt` mode, and keep extensions, allowing
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
for files to be written under compressed formats
"""
format = 'PQR'
units = {'time': None, 'length': 'Angstrom'}
Expand All @@ -210,7 +214,7 @@ def __init__(self, filename, convert_units=True, **kwargs):
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')
self.filename = util.filename(filename, ext='pqr', keep=True)
self.convert_units = convert_units # convert length and time to base units
self.remarks = kwargs.pop('remarks', "PQR file written by MDAnalysis")

Expand Down Expand Up @@ -299,7 +303,7 @@ def write(self, selection, frame=None):
"{miss}. These will be written with default values. "
"".format(miss=', '.join(missing_topology)))

with util.openany(self.filename, 'w') as pqrfile:
with util.openany(self.filename, 'wt') as pqrfile:
# Header / Remarks
# The *remarknumber* is typically 1 but :program:`pdb2pgr`
# also uses 6 for the total charge and 5 for warnings.
Expand Down
7 changes: 4 additions & 3 deletions package/MDAnalysis/lib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,10 @@ def anyopen(datasource, mode='rt', reset=True):
a file (from :class:`file` or :func:`open`) or a stream (e.g. from
:func:`urllib2.urlopen` or :class:`io.StringIO`)
mode: {'r', 'w', 'a'} (optional)
Open in r(ead), w(rite) or a(ppen) mode. More complicated
modes ('r+', 'w+', ...) are not supported; only the first letter of
`mode` is used and thus any additional modifiers are silently ignored.
Open in r(ead), w(rite) or a(ppen) mode. This string is directly
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
passed to the file opening handler (either Python's openfe, bz2, or
gzip). More complex mode are supported if the file opening handler
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
supports it.
reset: bool (optional)
try to read (`mode` 'r') the stream from the start

Expand Down
13 changes: 8 additions & 5 deletions testsuite/MDAnalysisTests/coordinates/test_crd.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,17 @@ def u(self):
def outfile(self, tmpdir):
return os.path.join(str(tmpdir), 'test.crd')

def test_write_atoms(self, u, outfile):
@pytest.mark.parametrize('testfile',
['test.crd', 'test.crd.bz2', 'test.crd.gz'])
def test_write_atoms(self, u, testfile, tmpdir):
# Test that written file when read gives same coordinates
u.atoms.write(outfile)
with tmpdir.as_cwd():
u.atoms.write(testfile)

u2 = mda.Universe(outfile)
u2 = mda.Universe(testfile)

assert_equal(u.atoms.positions,
u2.atoms.positions)
assert_equal(u.atoms.positions,
u2.atoms.positions)

def test_roundtrip(self, u, outfile):
# Write out a copy of the Universe, and compare this against the original
Expand Down
9 changes: 7 additions & 2 deletions testsuite/MDAnalysisTests/coordinates/test_lammps.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,13 @@ def test_molecule_tag(self, LAMMPSDATAWriter_molecule_tag):
err_msg="resids different after writing",)


def test_datawriter_universe(tmpdir):
fn = str(tmpdir.join('out.data'))
@pytest.mark.parametrize('filename', ['out.data', 'out.data.bz2'])
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
def test_datawriter_universe(filename, tmpdir):
"""
Test roundtrip on datawriter, and also checks compressed files
can be written (see #4159).
"""
fn = str(tmpdir.join(filename))

u = mda.Universe(LAMMPSdata_mini)

Expand Down
16 changes: 10 additions & 6 deletions testsuite/MDAnalysisTests/coordinates/test_pdbqt.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,17 @@ class TestPDBQTWriter(object):
def outfile(self, tmpdir):
return str(tmpdir) + 'out.pdbqt'

def test_roundtrip_writing_coords(self, outfile):
u = mda.Universe(PDBQT_input)
u.atoms.write(outfile)
u2 = mda.Universe(outfile)
@pytest.mark.parametrize('filename',
['test.pdbqt', 'test.pdbqt.bz2', 'test.pdbqt.gz'])
def test_roundtrip_writing_coords(self, filename, tmpdir):

with tmpdir.as_cwd():
u = mda.Universe(PDBQT_input)
u.atoms.write(filename)
u2 = mda.Universe(filename)

assert_equal(u2.atoms.positions, u.atoms.positions,
"Round trip does not preserve coordinates")
assert_equal(u2.atoms.positions, u.atoms.positions,
"Round trip does not preserve coordinates")

def test_roundtrip_formatting(self, outfile):
# Compare formatting of first line
Expand Down
9 changes: 9 additions & 0 deletions testsuite/MDAnalysisTests/coordinates/test_pqr.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ def universe():

prec = 3

@pytest.mark.parametrize('filename',
['test.pqr', 'test.pqr.bz2', 'test.pqr.gz'])
def test_simple_writer_roundtrip(self, universe, filename, tmpdir):
with tmpdir.as_cwd():
universe.atoms.write(filename)
u2 = mda.Universe(filename)
assert_equal(universe.atoms.positions,
u2.atoms.positions)

def test_writer_noChainID(self, universe, tmpdir):
outfile = str(tmpdir.join('pqr-test.pqr'))

Expand Down