Skip to content

Commit

Permalink
Fix DATAWriter, CRD, PQR, and PDBQT compressed file writing (#4163)
Browse files Browse the repository at this point in the history
* fix lammps case

* correct incorrect statement about modes

* O look another t typo

Unpopular opinion; the Foo Fighters have become a more iconic band
than Nirvana.

* Fix CRD case

You know, I don't think I've ever watched Breakfast at Tiffany's.

* Fix PQR case

At 20 miles a day, it would take 50 days for the Proclaimers to
get to fall down at your door. That's some shockingly poor delivery
service...

* Fix PDBQT case

You know Billy, you might not have started the fire, but you _could_
have helped try to put it out... A little bit of water goes a long way.

* Compressed NAMDBIN isn't solvable at this stage.

We built this city on <s>Rock N Roll</s>.. poorly documented ideas of
what file types you can open. anyopen? more like maybeopen

* Add gz tests

Flying like a GZIP, like a GUNZIP?

* Update changelog

The power of a changelog is a curious thing. Make one developer weep,
make another sing. Change existing scripts into new ones.

* Update testsuite/MDAnalysisTests/coordinates/test_lammps.py

Co-authored-by: Rocco Meli <r.meli@bluemail.ch>

* Apply suggestions from code review

Co-authored-by: Rocco Meli <r.meli@bluemail.ch>

---------

Co-authored-by: Rocco Meli <r.meli@bluemail.ch>
  • Loading branch information
IAlibay and RMeli authored Jun 18, 2023
1 parent efc51af commit 4f36e2a
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 25 deletions.
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 written in `wt` mode, and keep extensions, allowing
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 written in `wt` mode, and keep extensions, allowing
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 written in `wt` mode, and keep extensions, allowing
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(ppend) mode. This string is directly
passed to the file opening handler (either Python's openfe, bz2, or
gzip). More complex modes are supported if the file opening handler
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', 'out.data.gz'])
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

0 comments on commit 4f36e2a

Please sign in to comment.