Skip to content

Commit

Permalink
allow pickling of DCDFile
Browse files Browse the repository at this point in the history
  • Loading branch information
kain88-de committed Oct 12, 2017
1 parent 5ccd046 commit ad48254
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Enhancements
the `moltype` selection keyword is added (Issue #1555)
* about 20% speed improvements for GNM analysis. (PR #1579)
* added support for Tinker TXYZ and ARC files
* libmdaxdr classes can now be pickled (PR #1680)
* libmdaxdr and libdcd classes can now be pickled (PR #1680)

Deprecations

Expand Down
30 changes: 24 additions & 6 deletions package/MDAnalysis/lib/formats/libdcd.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,13 @@ cdef class DCDFile:
-----
DCD is not a well defined format. One consequence of this is that different
programs like CHARMM and NAMD are using different convention to store the
unitcell information. The :class:`DCDFile` will read the unitcell information
as is when available. Post processing depending on the program this DCD file
was written with is necessary. Have a look at the MDAnalysis DCD reader for
possible post processing into a common unitcell data structure. You can also
find more information how different programs store unitcell information in DCD
on the `mdawiki`_ .
unitcell information. The :class:`DCDFile` will read the unitcell
information as is when available. Post processing depending on the program
this DCD file was written with is necessary. Have a look at the MDAnalysis
DCD reader for possible post processing into a common unitcell data
structure. You can also find more information how different programs store
unitcell information in DCD on the `mdawiki`_ . This class can be pickled.
The pickle will store filename, mode, current frame
.. _mdawiki: https://github.com/MDAnalysis/mdanalysis/wiki/FileFormats#dcd
"""
Expand Down Expand Up @@ -249,6 +250,23 @@ cdef class DCDFile:
raise IOError('No file currently opened')
return self.n_frames

def __reduce__(self):
return (self.__class__, (self.fname.decode(), self.mode),
self.__getstate__())

def __getstate__(self):
return self.is_open, self.current_frame

def __setstate__(self, state):
is_open = state[0]
if not is_open:
self.close()
return

current_frame = state[1]
self.seek(current_frame)


def tell(self):
"""
Returns
Expand Down
63 changes: 41 additions & 22 deletions testsuite/MDAnalysisTests/formats/test_libdcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#
from __future__ import absolute_import, print_function
from six.moves import zip
from six.moves import cPickle as pickle

from collections import namedtuple
import os
Expand All @@ -25,7 +26,7 @@
import numpy as np

from numpy.testing import (assert_array_almost_equal, assert_equal,
assert_array_equal)
assert_array_equal, assert_almost_equal)

from MDAnalysis.lib.formats.libdcd import DCDFile, DCD_IS_CHARMM, DCD_HAS_EXTRA_BLOCK

Expand All @@ -44,9 +45,9 @@ def test_is_periodic(dcdfile, is_periodic):
assert f.is_periodic == is_periodic


@pytest.mark.parametrize("dcdfile, natoms",
[(DCD, 3341), (DCD_NAMD_TRICLINIC, 5545),
(DCD_TRICLINIC, 375)])
@pytest.mark.parametrize("dcdfile, natoms", [(DCD, 3341), (DCD_NAMD_TRICLINIC,
5545),
(DCD_TRICLINIC, 375)])
def test_read_coordsshape(dcdfile, natoms):
# confirm shape of coordinate data against result from previous
# MDAnalysis implementation of DCD file handling
Expand Down Expand Up @@ -82,6 +83,25 @@ def dcd():
yield dcd


def test_pickle(dcd):
dcd.seek(len(dcd) - 1)
dump = pickle.dumps(dcd)
new_dcd = pickle.loads(dump)

assert dcd.fname == new_dcd.fname
assert dcd.tell() == new_dcd.tell()


def test_pickle_closed(dcd):
dcd.seek(len(dcd) - 1)
dcd.close()
dump = pickle.dumps(dcd)
new_dcd = pickle.loads(dump)

assert dcd.fname == new_dcd.fname
assert dcd.tell() != new_dcd.tell()


@pytest.mark.parametrize("new_frame", (10, 42, 21))
def test_seek_normal(new_frame, dcd):
# frame seek within range is tested
Expand Down Expand Up @@ -115,9 +135,9 @@ def test_zero_based_frames_counting(dcd):
assert dcd.tell() == 0


@pytest.mark.parametrize("dcdfile, natoms",
[(DCD, 3341), (DCD_NAMD_TRICLINIC, 5545),
(DCD_TRICLINIC, 375)])
@pytest.mark.parametrize("dcdfile, natoms", [(DCD, 3341), (DCD_NAMD_TRICLINIC,
5545),
(DCD_TRICLINIC, 375)])
def test_natoms(dcdfile, natoms):
with DCDFile(dcdfile) as dcd:
assert dcd.header['natoms'] == natoms
Expand All @@ -129,9 +149,9 @@ def test_read_closed(dcd):
dcd.read()


@pytest.mark.parametrize("dcdfile, nframes",
[(DCD, 98), (DCD_NAMD_TRICLINIC, 1),
(DCD_TRICLINIC, 10)])
@pytest.mark.parametrize("dcdfile, nframes", [(DCD, 98), (DCD_NAMD_TRICLINIC,
1), (DCD_TRICLINIC,
10)])
def test_length_traj(dcdfile, nframes):
with DCDFile(dcdfile) as dcd:
assert len(dcd) == nframes
Expand All @@ -158,9 +178,10 @@ def test_iterating_twice(dcd):
DCD_TRICLINIC_HEADER = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver '


@pytest.mark.parametrize("dcdfile, remarks", (
(DCD, DCD_HEADER), (DCD_NAMD_TRICLINIC, DCD_NAMD_TRICLINIC_HEADER),
(DCD_TRICLINIC, DCD_TRICLINIC_HEADER)))
@pytest.mark.parametrize("dcdfile, remarks",
((DCD, DCD_HEADER), (DCD_NAMD_TRICLINIC,
DCD_NAMD_TRICLINIC_HEADER),
(DCD_TRICLINIC, DCD_TRICLINIC_HEADER)))
def test_header_remarks(dcdfile, remarks):
# confirm correct header remarks section reading
with DCDFile(dcdfile) as f:
Expand Down Expand Up @@ -346,8 +367,8 @@ def test_written_unit_cell(written_dcd):
assert_array_almost_equal(frame.unitcell, o_frame.unitcell)


@pytest.mark.parametrize(
"dtype", (np.int32, np.int64, np.float32, np.float64, int, float))
@pytest.mark.parametrize("dtype", (np.int32, np.int64, np.float32, np.float64,
int, float))
def test_write_all_dtypes(tmpdir, dtype):
fname = str(tmpdir.join('foo.dcd'))
with DCDFile(fname, 'w') as out:
Expand Down Expand Up @@ -456,9 +477,9 @@ def test_nframessize_int(dcdfile):
[([None, None, None], 98), ([0, None, None], 98), ([None, 98, None], 98),
([None, None, 1], 98), ([None, None, -1], 98), ([2, 6, 2], 2),
([0, 10, None], 10), ([2, 10, None], 8), ([0, 1, 1], 1), ([1, 1, 1], 0),
([1, 2, 1], 1), ([1, 2, 2], 1), ([1, 4, 2], 2), ([1, 4, 4], 1),
([0, 5, 5], 1), ([3, 5, 1], 2), ([4, 0, -1], 4), ([5, 0, -2], 3),
([5, 0, -4], 2)])
([1, 2, 1], 1), ([1, 2, 2], 1), ([1, 4, 2], 2), ([1, 4, 4], 1), ([
0, 5, 5
], 1), ([3, 5, 1], 2), ([4, 0, -1], 4), ([5, 0, -2], 3), ([5, 0, -4], 2)])
def test_readframes_slices(slice, length, dcd):
start, stop, step = slice
allframes = dcd.readframes().xyz
Expand Down Expand Up @@ -493,8 +514,7 @@ def test_readframes_atomindices(indices, dcd):
def test_write_random_unitcell(tmpdir):
testname = str(tmpdir.join('test.dcd'))
rstate = np.random.RandomState(1178083)
random_unitcells = rstate.uniform(
high=80, size=(98, 6)).astype(np.float64)
random_unitcells = rstate.uniform(high=80, size=(98, 6)).astype(np.float64)

with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out:
header = f_in.header
Expand All @@ -505,5 +525,4 @@ def test_write_random_unitcell(tmpdir):

with DCDFile(testname) as test:
for index, frame in enumerate(test):
assert_array_almost_equal(frame.unitcell,
random_unitcells[index])
assert_array_almost_equal(frame.unitcell, random_unitcells[index])

0 comments on commit ad48254

Please sign in to comment.