From 432edf3dbb0ef618c525babc5a5463683a411884 Mon Sep 17 00:00:00 2001 From: yuxuan Date: Tue, 9 Jun 2020 18:10:51 +0200 Subject: [PATCH 01/98] add pickle function to fileio, textio --- package/MDAnalysis/lib/util.py | 38 +++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 484474c77fa..8cf020ab3a2 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -231,7 +231,6 @@ class PathLike(object): pass - def filename(name, ext=None, keep=False): """Return a new name that has suffix attached; replaces other extensions. @@ -265,6 +264,43 @@ def filename(name, ext=None, keep=False): return name if isstream(name) else str(name) +class FileIOPickable(io.FileIO): + def __init__(self, name): + super().__init__(name) + + def __getstate__(self): + return self.tell(), self.name + + def __setstate__(self, args): + name = args[1] + super().__init__(name) + self.seek(args[0]) + + +class TextIOPickable(io.TextIOWrapper): + def __init__(self, buffer): + super().__init__(buffer) + self.buffer_class = buffer.__class__ + + def __getstate__(self): + return self.tell(), self.name, self.buffer_class + + def __setstate__(self, args): + name = args[1] + buffer_class = args[2] + buffer = buffer_class(name) + super().__init__(buffer) + self.seek(args[0]) + + +def pickle_open(name, mode): + buffer = FileIOPickable(name) + if mode == 'rb': + return buffer + elif mode == 'rt' or mode == 'r': + return TextIOPickable(buffer) + + @contextmanager def openany(datasource, mode='rt', reset=True): """Context manager for :func:`anyopen`. From bf55bf382b664c0df769ff2bd80e92dcb8302325 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 9 Jun 2020 18:19:09 +0200 Subject: [PATCH 02/98] add basic test for pickle io --- .../MDAnalysisTests/utils/test_pickleio.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 testsuite/MDAnalysisTests/utils/test_pickleio.py diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py new file mode 100644 index 00000000000..b63d077cd5f --- /dev/null +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -0,0 +1,60 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# 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. +# doi: 10.25080/majora-629e541a-00e +# +# 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 +# +from __future__ import absolute_import + +from os.path import abspath, basename, dirname, expanduser, normpath, relpath, split, splitext + +import six +from six.moves import range, cStringIO, StringIO + +import pytest +import numpy as np +from numpy.testing import assert_equal, assert_almost_equal, assert_array_almost_equal + + +import MDAnalysis +import MDAnalysis.lib.util as util +from MDAnalysis.tests.datafiles import PDB + +import os +import pickle + + +@pytest.fixture(params=[ + # filename mode + (PDB, 'r'), + (PDB, 'rt'), + (PDB, 'rb'), +]) +def f(request): + filename, mode = request.param + return util.pickle_open(filename, mode) + +def test_iopickle(f): + f_pickled = pickle.loads(pickle.dumps(f)) + assert_equal(f.readline(), f_pickled.readline()) + +def test_offset(f): + f.readline() + f_pickled = pickle.loads(pickle.dumps(f)) + assert_equal(f.tell(),f_pickled.tell()) From cd9a485ccf1db95645a850e4c5f60deaac440b85 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 9 Jun 2020 18:32:38 +0200 Subject: [PATCH 03/98] add comments --- package/MDAnalysis/lib/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 8cf020ab3a2..8507071cc52 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -288,11 +288,14 @@ def __getstate__(self): def __setstate__(self, args): name = args[1] buffer_class = args[2] + # buffer_class is used for further expansion this functionality to + # GZip files, which also requires a text wrapper. buffer = buffer_class(name) super().__init__(buffer) self.seek(args[0]) - +# not as comprehensive as built-in open func--no need for other args +# only should be used for 'reading' modes def pickle_open(name, mode): buffer = FileIOPickable(name) if mode == 'rb': From f33629f5032309b69a35638da79c147a47ec6bfc Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 10 Jun 2020 13:20:25 +0200 Subject: [PATCH 04/98] xfail on python2 --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index b63d077cd5f..177c2bfa55b 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -38,7 +38,7 @@ import os import pickle - +import sys @pytest.fixture(params=[ # filename mode @@ -50,10 +50,14 @@ def f(request): filename, mode = request.param return util.pickle_open(filename, mode) +@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ + working in python 2") def test_iopickle(f): f_pickled = pickle.loads(pickle.dumps(f)) assert_equal(f.readline(), f_pickled.readline()) +@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ + working in python 2") def test_offset(f): f.readline() f_pickled = pickle.loads(pickle.dumps(f)) From aca44960a0caeea82f9e653b49974554fccdf5f7 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 12:52:13 +0200 Subject: [PATCH 05/98] add doc and exception for pickle_open --- package/MDAnalysis/lib/util.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 8507071cc52..7a4397bbd6a 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -296,7 +296,29 @@ def __setstate__(self, args): # not as comprehensive as built-in open func--no need for other args # only should be used for 'reading' modes -def pickle_open(name, mode): +def pickle_open(name, mode='rt'): + """Open file and return a stream with pickle function implemented. + + Parameters + ---------- + name : str; + a filename given a text or byte string. + mode: {'r', 'rt', 'rb'} (optional) + 'r': open for reading in text mode; + 'rt': read in text mode (default); + 'rb': read in binary mode; + raise ValueError with other modes. + + Returns + ------- + stream-like object + + See Also + -------- + :func:`anyopen` + """ + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') file can be pickled.") buffer = FileIOPickable(name) if mode == 'rb': return buffer From cd4ffe35c10dd7decd39c738bf64096c50a614f6 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 13:04:55 +0200 Subject: [PATCH 06/98] add doc for textio fileio class --- package/MDAnalysis/lib/util.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 7a4397bbd6a..24cf163a52d 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -265,9 +265,11 @@ def filename(name, ext=None, keep=False): class FileIOPickable(io.FileIO): - def __init__(self, name): - super().__init__(name) + """Stream for read a file + + Picklable FiloIO class that only support read mode. + """ def __getstate__(self): return self.tell(), self.name @@ -278,6 +280,13 @@ def __setstate__(self, args): class TextIOPickable(io.TextIOWrapper): + """Character and line based layer over a pickable FileIO based object. + + Example + ------- + file = FileIOPickable('filename') + text_wrapped = TextIOPickable(file) + """ def __init__(self, buffer): super().__init__(buffer) self.buffer_class = buffer.__class__ From ec5bd3cd8b44880f7c3bebd2979b6859a3c8799c Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 13:38:16 +0200 Subject: [PATCH 07/98] add parallel test for textio --- .../parallelism/test_multiprocessing.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py new file mode 100644 index 00000000000..7b708f11a38 --- /dev/null +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -0,0 +1,53 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# 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. +# doi: 10.25080/majora-629e541a-00e +# +# 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 +# + +from __future__ import absolute_import +import sys +import multiprocessing +import numpy as np +import pytest +import pickle + +import MDAnalysis as mda +from MDAnalysis.lib.util import pickle_open +from MDAnalysisTests.datafiles import ( + PDB +) + +from numpy.testing import assert_equal + +def textio_line(file, i): + return file.readlines()[i] + +@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ + working in python 2") +def test_multiprocess_fileio(): + p = multiprocessing.Pool(2) + PDB_file = pickle_open(PDB) + ref = PDB_file.readlines()[:4] + PDB_file.close() + PDB_file = pickle_open(PDB) + res = np.array([p.apply(textio_line, args=(PDB_file, i)) + for i in range(4)]) + p.close() + assert_equal(res, ref) From f6515ee166e3cd10bae194b4015a2832008506c2 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 13:45:04 +0200 Subject: [PATCH 08/98] pep8 --- .../parallelism/test_multiprocessing.py | 6 +++--- testsuite/MDAnalysisTests/utils/test_pickleio.py | 16 +++++----------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 7b708f11a38..74a01eb386b 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -26,9 +26,7 @@ import multiprocessing import numpy as np import pytest -import pickle -import MDAnalysis as mda from MDAnalysis.lib.util import pickle_open from MDAnalysisTests.datafiles import ( PDB @@ -36,9 +34,11 @@ from numpy.testing import assert_equal + def textio_line(file, i): return file.readlines()[i] + @pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ working in python 2") def test_multiprocess_fileio(): @@ -48,6 +48,6 @@ def test_multiprocess_fileio(): PDB_file.close() PDB_file = pickle_open(PDB) res = np.array([p.apply(textio_line, args=(PDB_file, i)) - for i in range(4)]) + for i in range(4)]) p.close() assert_equal(res, ref) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 177c2bfa55b..116cecd9c56 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -22,24 +22,16 @@ # from __future__ import absolute_import -from os.path import abspath, basename, dirname, expanduser, normpath, relpath, split, splitext - -import six -from six.moves import range, cStringIO, StringIO - import pytest -import numpy as np -from numpy.testing import assert_equal, assert_almost_equal, assert_array_almost_equal - +from numpy.testing import assert_equal -import MDAnalysis import MDAnalysis.lib.util as util from MDAnalysis.tests.datafiles import PDB -import os import pickle import sys + @pytest.fixture(params=[ # filename mode (PDB, 'r'), @@ -50,15 +42,17 @@ def f(request): filename, mode = request.param return util.pickle_open(filename, mode) + @pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ working in python 2") def test_iopickle(f): f_pickled = pickle.loads(pickle.dumps(f)) assert_equal(f.readline(), f_pickled.readline()) + @pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ working in python 2") def test_offset(f): f.readline() f_pickled = pickle.loads(pickle.dumps(f)) - assert_equal(f.tell(),f_pickled.tell()) + assert_equal(f.tell(), f_pickled.tell()) From cf29764fa7de6ac21297054944a67f22d921aed3 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 14:12:02 +0200 Subject: [PATCH 09/98] add an extra bufferlayer for FileIO for fast access --- package/MDAnalysis/lib/util.py | 47 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 24cf163a52d..449675710cc 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -279,6 +279,27 @@ def __setstate__(self, args): self.seek(args[0]) +class BufferIOPickable(io.BufferedReader): + """A pickable buffer for a readable FilIO object + + Wrap raw FileIOPickable inside + + """ + def __init__(self, raw): + super().__init__(raw) + self.raw_class = raw.__class__ + + def __getstate__(self): + return self.tell(), self.name, self.raw_class + + def __setstate__(self, args): + name = args[1] + raw_class = args[2] + raw = raw_class(name) + super().__init__(raw) + self.seek(args[0]) + + class TextIOPickable(io.TextIOWrapper): """Character and line based layer over a pickable FileIO based object. @@ -287,20 +308,20 @@ class TextIOPickable(io.TextIOWrapper): file = FileIOPickable('filename') text_wrapped = TextIOPickable(file) """ - def __init__(self, buffer): - super().__init__(buffer) - self.buffer_class = buffer.__class__ + def __init__(self, raw): + super().__init__(raw) + self.raw_class = raw.__class__ def __getstate__(self): - return self.tell(), self.name, self.buffer_class + return self.tell(), self.name, self.raw_class def __setstate__(self, args): name = args[1] - buffer_class = args[2] - # buffer_class is used for further expansion this functionality to + raw_class = args[2] + # raw_class is used for further expansion this functionality to # GZip files, which also requires a text wrapper. - buffer = buffer_class(name) - super().__init__(buffer) + raw = raw_class(name) + super().__init__(raw) self.seek(args[0]) # not as comprehensive as built-in open func--no need for other args @@ -313,7 +334,7 @@ def pickle_open(name, mode='rt'): name : str; a filename given a text or byte string. mode: {'r', 'rt', 'rb'} (optional) - 'r': open for reading in text mode; + 'r': open for reading in text mode; 'rt': read in text mode (default); 'rb': read in binary mode; raise ValueError with other modes. @@ -327,12 +348,12 @@ def pickle_open(name, mode='rt'): :func:`anyopen` """ if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') file can be pickled.") - buffer = FileIOPickable(name) + raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") + raw = FileIOPickable(name) if mode == 'rb': - return buffer + return BufferIOPickable(raw) elif mode == 'rt' or mode == 'r': - return TextIOPickable(buffer) + return TextIOPickable(raw) @contextmanager From c29c316e97e582087dd5f4bd30e687203a661449 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 14:26:45 +0200 Subject: [PATCH 10/98] ditch py2 --- .../MDAnalysisTests/parallelism/test_multiprocessing.py | 3 --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 5 ----- 2 files changed, 8 deletions(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 74a01eb386b..5b81f01179a 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -22,7 +22,6 @@ # from __future__ import absolute_import -import sys import multiprocessing import numpy as np import pytest @@ -39,8 +38,6 @@ def textio_line(file, i): return file.readlines()[i] -@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ - working in python 2") def test_multiprocess_fileio(): p = multiprocessing.Pool(2) PDB_file = pickle_open(PDB) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 116cecd9c56..4388565c80a 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -29,7 +29,6 @@ from MDAnalysis.tests.datafiles import PDB import pickle -import sys @pytest.fixture(params=[ @@ -43,15 +42,11 @@ def f(request): return util.pickle_open(filename, mode) -@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ - working in python 2") def test_iopickle(f): f_pickled = pickle.loads(pickle.dumps(f)) assert_equal(f.readline(), f_pickled.readline()) -@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ - working in python 2") def test_offset(f): f.readline() f_pickled = pickle.loads(pickle.dumps(f)) From db47e278a23d6ce955667e6654575262b2a8bcd2 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 14:29:09 +0200 Subject: [PATCH 11/98] pep8 and doc --- package/MDAnalysis/lib/util.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 449675710cc..c905236aceb 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -318,14 +318,13 @@ def __getstate__(self): def __setstate__(self, args): name = args[1] raw_class = args[2] - # raw_class is used for further expansion this functionality to - # GZip files, which also requires a text wrapper. + # raw_class is used for further expansion this functionality to + # GZip files, which also requires a text wrapper. raw = raw_class(name) super().__init__(raw) self.seek(args[0]) -# not as comprehensive as built-in open func--no need for other args -# only should be used for 'reading' modes + def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. @@ -346,6 +345,10 @@ def pickle_open(name, mode='rt'): See Also -------- :func:`anyopen` + + Warning + ------- + Should be only used with read mode. """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") From 7e9d6d3c5f5b77f07f0a821d454266caf4014041 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 14:46:51 +0200 Subject: [PATCH 12/98] add test for unsupported mode --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 4388565c80a..e42136c4638 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -51,3 +51,19 @@ def test_offset(f): f.readline() f_pickled = pickle.loads(pickle.dumps(f)) assert_equal(f.tell(), f_pickled.tell()) + + +@pytest.fixture(params=[ + # filename mode + (PDB, 'w'), + (PDB, 'x'), + (PDB, 'a'), +]) +def unpickable_f(request): + filename, mode = request.param + return filename, mode +def test_unpickable_open_mode(unpickable_f): + filename, mode = unpickable_f + with pytest.raises(ValueError): + util.pickle_open(filename, mode) + From eb83a7ce98cd7759fdede9081aec1ab5c0b20ea9 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 11 Jun 2020 14:50:29 +0200 Subject: [PATCH 13/98] pep8 --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index e42136c4638..85690fbf281 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -62,8 +62,9 @@ def test_offset(f): def unpickable_f(request): filename, mode = request.param return filename, mode + + def test_unpickable_open_mode(unpickable_f): filename, mode = unpickable_f with pytest.raises(ValueError): util.pickle_open(filename, mode) - From 0baa868450cd9cf0944f4ae5029042a2e151d33d Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 09:24:57 +0200 Subject: [PATCH 14/98] typo --- package/MDAnalysis/lib/util.py | 22 +++++++++---------- .../MDAnalysisTests/utils/test_pickleio.py | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index c905236aceb..ecf15b5e3d0 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -264,7 +264,7 @@ def filename(name, ext=None, keep=False): return name if isstream(name) else str(name) -class FileIOPickable(io.FileIO): +class FileIOPicklable(io.FileIO): """Stream for read a file Picklable FiloIO class that only support read mode. @@ -279,10 +279,10 @@ def __setstate__(self, args): self.seek(args[0]) -class BufferIOPickable(io.BufferedReader): - """A pickable buffer for a readable FilIO object +class BufferIOPicklable(io.BufferedReader): + """A picklable buffer for a readable FilIO object - Wrap raw FileIOPickable inside + Wrap raw FileIOPicklable inside """ def __init__(self, raw): @@ -300,13 +300,13 @@ def __setstate__(self, args): self.seek(args[0]) -class TextIOPickable(io.TextIOWrapper): - """Character and line based layer over a pickable FileIO based object. +class TextIOPicklable(io.TextIOWrapper): + """Character and line based layer over a picklable FileIO based object. Example ------- - file = FileIOPickable('filename') - text_wrapped = TextIOPickable(file) + file = FileIOPicklable('filename') + text_wrapped = TextIOPicklable(file) """ def __init__(self, raw): super().__init__(raw) @@ -352,11 +352,11 @@ def pickle_open(name, mode='rt'): """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") - raw = FileIOPickable(name) + raw = FileIOPicklable(name) if mode == 'rb': - return BufferIOPickable(raw) + return BufferIOPicklable(raw) elif mode == 'rt' or mode == 'r': - return TextIOPickable(raw) + return TextIOPicklable(raw) @contextmanager diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 85690fbf281..2a0be85e5b6 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -59,12 +59,12 @@ def test_offset(f): (PDB, 'x'), (PDB, 'a'), ]) -def unpickable_f(request): +def unpicklable_f(request): filename, mode = request.param return filename, mode -def test_unpickable_open_mode(unpickable_f): - filename, mode = unpickable_f +def test_unpicklable_open_mode(unpicklable_f): + filename, mode = unpicklable_f with pytest.raises(ValueError): util.pickle_open(filename, mode) From c8e63b2be5f0de7a9af6701ed7fc2ceabde959c9 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 09:30:58 +0200 Subject: [PATCH 15/98] pickle reorder --- package/MDAnalysis/lib/util.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index ecf15b5e3d0..e644006cf7c 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -271,12 +271,11 @@ class FileIOPicklable(io.FileIO): """ def __getstate__(self): - return self.tell(), self.name - + return self.name, self.tell() def __setstate__(self, args): - name = args[1] + name = args[0] super().__init__(name) - self.seek(args[0]) + self.seek(args[1]) class BufferIOPicklable(io.BufferedReader): @@ -290,14 +289,14 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - return self.tell(), self.name, self.raw_class + return self.raw_class, self.name, self.tell() def __setstate__(self, args): + raw_class = args[0] name = args[1] - raw_class = args[2] raw = raw_class(name) super().__init__(raw) - self.seek(args[0]) + self.seek(args[2]) class TextIOPicklable(io.TextIOWrapper): @@ -313,16 +312,16 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - return self.tell(), self.name, self.raw_class + return self.raw_class, self.name, self.tell() def __setstate__(self, args): + raw_class = args[0] name = args[1] - raw_class = args[2] # raw_class is used for further expansion this functionality to # GZip files, which also requires a text wrapper. raw = raw_class(name) super().__init__(raw) - self.seek(args[0]) + self.seek(args[2]) def pickle_open(name, mode='rt'): From 658b4469c449bfb202d3ca747027d3765d1c0f02 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 10:26:50 +0200 Subject: [PATCH 16/98] pickle_open as context manager --- .../MDAnalysisTests/parallelism/test_multiprocessing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 5b81f01179a..265dcea06b0 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -40,11 +40,10 @@ def textio_line(file, i): def test_multiprocess_fileio(): p = multiprocessing.Pool(2) - PDB_file = pickle_open(PDB) - ref = PDB_file.readlines()[:4] - PDB_file.close() - PDB_file = pickle_open(PDB) - res = np.array([p.apply(textio_line, args=(PDB_file, i)) + with pickle_open(PDB) as PDB_file: + ref = PDB_file.readlines()[:4] + with pickle_open(PDB) as PDB_file: + res = np.array([p.apply(textio_line, args=(PDB_file, i)) for i in range(4)]) p.close() assert_equal(res, ref) From 1aa6003072a8c3aa30af4b5904000dfa1e3b3f15 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 10:40:18 +0200 Subject: [PATCH 17/98] format --- package/MDAnalysis/lib/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index e644006cf7c..cc08fedb926 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -272,6 +272,7 @@ class FileIOPicklable(io.FileIO): """ def __getstate__(self): return self.name, self.tell() + def __setstate__(self, args): name = args[0] super().__init__(name) @@ -354,7 +355,7 @@ def pickle_open(name, mode='rt'): raw = FileIOPicklable(name) if mode == 'rb': return BufferIOPicklable(raw) - elif mode == 'rt' or mode == 'r': + elif mode in {'r', 'rt'}: return TextIOPicklable(raw) From f2738bcd48286116183c9633e43877d8b0b4b33b Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 11:18:21 +0200 Subject: [PATCH 18/98] move pickle-io to a separate file --- package/MDAnalysis/lib/__init__.py | 1 + package/MDAnalysis/lib/picklable_file_io.py | 121 ++++++++++++++++++ package/MDAnalysis/lib/util.py | 95 -------------- .../parallelism/test_multiprocessing.py | 2 +- .../MDAnalysisTests/utils/test_pickleio.py | 6 +- 5 files changed, 126 insertions(+), 99 deletions(-) create mode 100644 package/MDAnalysis/lib/picklable_file_io.py diff --git a/package/MDAnalysis/lib/__init__.py b/package/MDAnalysis/lib/__init__.py index 6ba64782d03..980d21478ce 100644 --- a/package/MDAnalysis/lib/__init__.py +++ b/package/MDAnalysis/lib/__init__.py @@ -41,3 +41,4 @@ from . import formats from . import pkdtree from . import nsgrid +from .picklable_file_io import FileIOPicklable, BufferIOPicklable, TextIOPicklable diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py new file mode 100644 index 00000000000..a6216b5ffca --- /dev/null +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -0,0 +1,121 @@ + +# -*- 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. +# doi: 10.25080/majora-629e541a-00e +# +# 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 +# +""" +""" +import io + + +class FileIOPicklable(io.FileIO): + """Stream for read a file + + Picklable FiloIO class that only support read mode. + + """ + def __getstate__(self): + return self.name, self.tell() + + def __setstate__(self, args): + name = args[0] + super().__init__(name) + self.seek(args[1]) + + +class BufferIOPicklable(io.BufferedReader): + """A picklable buffer for a readable FilIO object + + Wrap raw FileIOPicklable inside + + """ + def __init__(self, raw): + super().__init__(raw) + self.raw_class = raw.__class__ + + def __getstate__(self): + return self.raw_class, self.name, self.tell() + + def __setstate__(self, args): + raw_class = args[0] + name = args[1] + raw = raw_class(name) + super().__init__(raw) + self.seek(args[2]) + + +class TextIOPicklable(io.TextIOWrapper): + """Character and line based layer over a picklable FileIO based object. + + Example + ------- + file = FileIOPicklable('filename') + text_wrapped = TextIOPicklable(file) + """ + def __init__(self, raw): + super().__init__(raw) + self.raw_class = raw.__class__ + + def __getstate__(self): + return self.raw_class, self.name, self.tell() + + def __setstate__(self, args): + raw_class = args[0] + name = args[1] + # raw_class is used for further expansion this functionality to + # GZip files, which also requires a text wrapper. + raw = raw_class(name) + super().__init__(raw) + self.seek(args[2]) + + +def pickle_open(name, mode='rt'): + """Open file and return a stream with pickle function implemented. + + Parameters + ---------- + name : str; + a filename given a text or byte string. + mode: {'r', 'rt', 'rb'} (optional) + 'r': open for reading in text mode; + 'rt': read in text mode (default); + 'rb': read in binary mode; + raise ValueError with other modes. + + Returns + ------- + stream-like object + + See Also + -------- + :func:`anyopen` + + Warning + ------- + Should be only used with read mode. + """ + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") + raw = FileIOPicklable(name) + if mode == 'rb': + return BufferIOPicklable(raw) + elif mode in {'r', 'rt'}: + return TextIOPicklable(raw) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index cc08fedb926..2c058cd7be8 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -264,101 +264,6 @@ def filename(name, ext=None, keep=False): return name if isstream(name) else str(name) -class FileIOPicklable(io.FileIO): - """Stream for read a file - - Picklable FiloIO class that only support read mode. - - """ - def __getstate__(self): - return self.name, self.tell() - - def __setstate__(self, args): - name = args[0] - super().__init__(name) - self.seek(args[1]) - - -class BufferIOPicklable(io.BufferedReader): - """A picklable buffer for a readable FilIO object - - Wrap raw FileIOPicklable inside - - """ - def __init__(self, raw): - super().__init__(raw) - self.raw_class = raw.__class__ - - def __getstate__(self): - return self.raw_class, self.name, self.tell() - - def __setstate__(self, args): - raw_class = args[0] - name = args[1] - raw = raw_class(name) - super().__init__(raw) - self.seek(args[2]) - - -class TextIOPicklable(io.TextIOWrapper): - """Character and line based layer over a picklable FileIO based object. - - Example - ------- - file = FileIOPicklable('filename') - text_wrapped = TextIOPicklable(file) - """ - def __init__(self, raw): - super().__init__(raw) - self.raw_class = raw.__class__ - - def __getstate__(self): - return self.raw_class, self.name, self.tell() - - def __setstate__(self, args): - raw_class = args[0] - name = args[1] - # raw_class is used for further expansion this functionality to - # GZip files, which also requires a text wrapper. - raw = raw_class(name) - super().__init__(raw) - self.seek(args[2]) - - -def pickle_open(name, mode='rt'): - """Open file and return a stream with pickle function implemented. - - Parameters - ---------- - name : str; - a filename given a text or byte string. - mode: {'r', 'rt', 'rb'} (optional) - 'r': open for reading in text mode; - 'rt': read in text mode (default); - 'rb': read in binary mode; - raise ValueError with other modes. - - Returns - ------- - stream-like object - - See Also - -------- - :func:`anyopen` - - Warning - ------- - Should be only used with read mode. - """ - if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") - raw = FileIOPicklable(name) - if mode == 'rb': - return BufferIOPicklable(raw) - elif mode in {'r', 'rt'}: - return TextIOPicklable(raw) - - @contextmanager def openany(datasource, mode='rt', reset=True): """Context manager for :func:`anyopen`. diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 265dcea06b0..7f643063bec 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -26,7 +26,7 @@ import numpy as np import pytest -from MDAnalysis.lib.util import pickle_open +from MDAnalysis.lib.picklable_file_io import pickle_open from MDAnalysisTests.datafiles import ( PDB ) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 2a0be85e5b6..60df6a94177 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -25,7 +25,7 @@ import pytest from numpy.testing import assert_equal -import MDAnalysis.lib.util as util +from MDAnalysis.lib.picklable_file_io import pickle_open from MDAnalysis.tests.datafiles import PDB import pickle @@ -39,7 +39,7 @@ ]) def f(request): filename, mode = request.param - return util.pickle_open(filename, mode) + return pickle_open(filename, mode) def test_iopickle(f): @@ -67,4 +67,4 @@ def unpicklable_f(request): def test_unpicklable_open_mode(unpicklable_f): filename, mode = unpicklable_f with pytest.raises(ValueError): - util.pickle_open(filename, mode) + pickle_open(filename, mode) From 001f3b86dba8977ece56c75e771247f94a58a835 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 11:47:42 +0200 Subject: [PATCH 19/98] doc --- package/MDAnalysis/lib/picklable_file_io.py | 76 ++++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index a6216b5ffca..c3478f9624f 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -22,15 +22,49 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # """ +Picklable read-only IO classes +============================= + +Provide with an interface for pickling read-only IO file object. + +.. autoclass:: FileIOPicklable + :members: + +.. autoclass:: BufferIOPicklable + :members: + +.. autoclass:: TextIOPicklable + :members: + +.. autofunction:: pickle_open + """ import io class FileIOPicklable(io.FileIO): - """Stream for read a file + """File object (read-only) that can be pickled. + + This class provides a file-like object (as returned by :func:`open`, namely :class:`io.FileIO`) + that, unlike standard Python file objects, can be pickled. Only read mode is supported. - Picklable FiloIO class that only support read mode. + When the file is pickled, filename and position of the open file handle in the file + are saved. On unpickling, the file is opened by filename and the file is + seeked to the saved position. This means that for a successful unpickle, the original + file still has to be accessible with its filename. + Example + ------- + :: + file = FileIOPicklable('filename') + file.readline() + file_pickled = pickle.loads(pickle.dumps(file) + assert_equal(file.tell(), file_pickled.tell()) + + See Also + --------- + TextIOPicklable + BufferIOPicklable """ def __getstate__(self): return self.name, self.tell() @@ -42,10 +76,20 @@ def __setstate__(self, args): class BufferIOPicklable(io.BufferedReader): - """A picklable buffer for a readable FilIO object + """A picklable buffer object for read-only FilIO object. + + This class provides a buffered :class:`io.BufferedReader` object that can be pickled. Note that this only works in reda mode. - Wrap raw FileIOPicklable inside + Example + ------- + :: + file = FileIOPicklable('filename') + buffer_wrapped = BufferIOPicklable(file) + See Also + --------- + FileIOPicklable + TextIOPicklable """ def __init__(self, raw): super().__init__(raw) @@ -63,12 +107,21 @@ def __setstate__(self, args): class TextIOPicklable(io.TextIOWrapper): - """Character and line based layer over a picklable FileIO based object. + """Character and line based picklable file-like object. + + This class provides a file-like :class:`io.TextIOWrapper` object that can + be pickled. Note that this only works in read mode. Example ------- + :: file = FileIOPicklable('filename') text_wrapped = TextIOPicklable(file) + + See Also + --------- + FileIOPicklable + BufferIOPicklable """ def __init__(self, raw): super().__init__(raw) @@ -90,6 +143,10 @@ def __setstate__(self, args): def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. + Note + ---- + Should be only used with read mode. + Parameters ---------- name : str; @@ -98,7 +155,10 @@ def pickle_open(name, mode='rt'): 'r': open for reading in text mode; 'rt': read in text mode (default); 'rb': read in binary mode; - raise ValueError with other modes. + + Raises + ------ + ValueError : if `mode` is not one of the allowed read modes Returns ------- @@ -107,10 +167,6 @@ def pickle_open(name, mode='rt'): See Also -------- :func:`anyopen` - - Warning - ------- - Should be only used with read mode. """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") From d0374f5974b89ff4ac80c1a25daf800dc30cf01e Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 12:09:18 +0200 Subject: [PATCH 20/98] FileIOPicklable class now only supports name as input, (preventing user from using mode other than read) --- package/MDAnalysis/lib/picklable_file_io.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index c3478f9624f..b1beb197acf 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -66,12 +66,15 @@ class FileIOPicklable(io.FileIO): TextIOPicklable BufferIOPicklable """ + def __init__(self, name): + super().__init__(name, mode='r') + def __getstate__(self): return self.name, self.tell() def __setstate__(self, args): name = args[0] - super().__init__(name) + super().__init__(name, mode='r') self.seek(args[1]) From 8c62df8a3630197c519ece51b55bd8fe496b82b1 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 12:46:57 +0200 Subject: [PATCH 21/98] pickle open doc and add fspath for the filename --- package/MDAnalysis/lib/picklable_file_io.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index b1beb197acf..ed50d8111c5 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -40,7 +40,7 @@ """ import io - +import os class FileIOPicklable(io.FileIO): """File object (read-only) that can be pickled. @@ -146,6 +146,12 @@ def __setstate__(self, args): def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. + Built-in `io.open` function only returns an unpicklable file object. + In order to serialize a `MDAnalysis.core.Universe`, this function can + used to open trajectory/topology files--a object composition approach, + as opposed to class inheritance, which is more flexible and easier for + pickle implementation for new readers. + Note ---- Should be only used with read mode. @@ -170,9 +176,11 @@ def pickle_open(name, mode='rt'): See Also -------- :func:`anyopen` + :func:`io.open` """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") + name = os.fspath(name) raw = FileIOPicklable(name) if mode == 'rb': return BufferIOPicklable(raw) From 94f1f8d261cce1a637261e0734ca373476861646 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 12 Jun 2020 19:28:42 +0200 Subject: [PATCH 22/98] absolute import --- package/MDAnalysis/lib/picklable_file_io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index ed50d8111c5..d95c2cbc241 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -39,6 +39,8 @@ .. autofunction:: pickle_open """ +from __future__ import division, absolute_import + import io import os From acbadec7f2e64d28cee81b1a703b27f6088257a3 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 13 Jun 2020 10:46:32 +0200 Subject: [PATCH 23/98] more doc --- package/MDAnalysis/lib/picklable_file_io.py | 39 ++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index d95c2cbc241..a33ba2aa4e4 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -22,10 +22,12 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # """ -Picklable read-only IO classes -============================= +Picklable read-only I/O classes +=============================== Provide with an interface for pickling read-only IO file object. +These classes are used for further pickling `mda.core.Universe` +in a object composition approach. .. autoclass:: FileIOPicklable :members: @@ -38,12 +40,15 @@ .. autofunction:: pickle_open + +.. versionadded:: 2.0.0 """ from __future__ import division, absolute_import import io import os + class FileIOPicklable(io.FileIO): """File object (read-only) that can be pickled. @@ -67,6 +72,9 @@ class FileIOPicklable(io.FileIO): --------- TextIOPicklable BufferIOPicklable + + + .. versionadded:: 2.0.0 """ def __init__(self, name): super().__init__(name, mode='r') @@ -83,7 +91,8 @@ def __setstate__(self, args): class BufferIOPicklable(io.BufferedReader): """A picklable buffer object for read-only FilIO object. - This class provides a buffered :class:`io.BufferedReader` object that can be pickled. Note that this only works in reda mode. + This class provides a buffered :class:`io.BufferedReader` object that can be pickled. + Note that this only works in reda mode. Example ------- @@ -95,6 +104,9 @@ class BufferIOPicklable(io.BufferedReader): --------- FileIOPicklable TextIOPicklable + + + .. versionadded:: 2.0.0 """ def __init__(self, raw): super().__init__(raw) @@ -127,6 +139,9 @@ class TextIOPicklable(io.TextIOWrapper): --------- FileIOPicklable BufferIOPicklable + + + .. versionadded:: 2.0.0 """ def __init__(self, raw): super().__init__(raw) @@ -148,7 +163,10 @@ def __setstate__(self, args): def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. - Built-in `io.open` function only returns an unpicklable file object. + This function return either BufferIOPicklable or TextIOPicklable wrapped + FileIOPicklable object given different reading mode. It can be used as a + context manager, and replace the built-in `io.open` function in reading mode + that only returns an unpicklable file object. In order to serialize a `MDAnalysis.core.Universe`, this function can used to open trajectory/topology files--a object composition approach, as opposed to class inheritance, which is more flexible and easier for @@ -156,7 +174,7 @@ def pickle_open(name, mode='rt'): Note ---- - Should be only used with read mode. + Can be only used with read mode. Parameters ---------- @@ -167,18 +185,21 @@ def pickle_open(name, mode='rt'): 'rt': read in text mode (default); 'rb': read in binary mode; - Raises - ------ - ValueError : if `mode` is not one of the allowed read modes - Returns ------- stream-like object + Raises + ------ + ValueError : if `mode` is not one of the allowed read modes + See Also -------- :func:`anyopen` :func:`io.open` + + + .. versionadded:: 2.0.0 """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") From 7043d2d4b3687a8d3a1e14dd91585bf239c2eafc Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 12:51:10 +0200 Subject: [PATCH 24/98] more pep8 and format --- package/MDAnalysis/lib/__init__.py | 4 +++- package/MDAnalysis/lib/picklable_file_io.py | 24 +++++++++++-------- .../parallelism/test_multiprocessing.py | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/package/MDAnalysis/lib/__init__.py b/package/MDAnalysis/lib/__init__.py index 980d21478ce..2d384ec8bcb 100644 --- a/package/MDAnalysis/lib/__init__.py +++ b/package/MDAnalysis/lib/__init__.py @@ -41,4 +41,6 @@ from . import formats from . import pkdtree from . import nsgrid -from .picklable_file_io import FileIOPicklable, BufferIOPicklable, TextIOPicklable +from .picklable_file_io import FileIOPicklable, BufferIOPicklable, \ + TextIOPicklable + diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index a33ba2aa4e4..7b4037cb43c 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -52,13 +52,15 @@ class FileIOPicklable(io.FileIO): """File object (read-only) that can be pickled. - This class provides a file-like object (as returned by :func:`open`, namely :class:`io.FileIO`) - that, unlike standard Python file objects, can be pickled. Only read mode is supported. + This class provides a file-like object (as returned by :func:`open`, + namely :class:`io.FileIO`) that, unlike standard Python file objects, + can be pickled. Only read mode is supported. - When the file is pickled, filename and position of the open file handle in the file - are saved. On unpickling, the file is opened by filename and the file is - seeked to the saved position. This means that for a successful unpickle, the original - file still has to be accessible with its filename. + When the file is pickled, filename and position of the open file handle in + the file are saved. On unpickling, the file is opened by filename, + and the file is seeked to the saved position. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. Example ------- @@ -91,8 +93,9 @@ def __setstate__(self, args): class BufferIOPicklable(io.BufferedReader): """A picklable buffer object for read-only FilIO object. - This class provides a buffered :class:`io.BufferedReader` object that can be pickled. - Note that this only works in reda mode. + This class provides a buffered :class:`io.BufferedReader` + that can be pickled. + Note that this only works in read mode. Example ------- @@ -165,7 +168,7 @@ def pickle_open(name, mode='rt'): This function return either BufferIOPicklable or TextIOPicklable wrapped FileIOPicklable object given different reading mode. It can be used as a - context manager, and replace the built-in `io.open` function in reading mode + context manager, and replace the built-in `io.open` function in read mode that only returns an unpicklable file object. In order to serialize a `MDAnalysis.core.Universe`, this function can used to open trajectory/topology files--a object composition approach, @@ -202,7 +205,8 @@ def pickle_open(name, mode='rt'): .. versionadded:: 2.0.0 """ if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') files can be pickled.") + raise ValueError("Only read mode ('r', 'rt', 'rb') \ + files can be pickled.") name = os.fspath(name) raw = FileIOPicklable(name) if mode == 'rb': diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 7f643063bec..7b155c94e70 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -44,6 +44,6 @@ def test_multiprocess_fileio(): ref = PDB_file.readlines()[:4] with pickle_open(PDB) as PDB_file: res = np.array([p.apply(textio_line, args=(PDB_file, i)) - for i in range(4)]) + for i in range(4)]) p.close() assert_equal(res, ref) From c259143437b722f83b498c9072b54516f4079fea Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 13:08:28 +0200 Subject: [PATCH 25/98] sphinx mark up --- package/MDAnalysis/lib/picklable_file_io.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 7b4037cb43c..88dbfd78990 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -26,7 +26,7 @@ =============================== Provide with an interface for pickling read-only IO file object. -These classes are used for further pickling `mda.core.Universe` +These classes are used for further pickling :class:`mda.core.Universe` in a object composition approach. .. autoclass:: FileIOPicklable @@ -62,6 +62,11 @@ class FileIOPicklable(io.FileIO): This means that for a successful unpickle, the original file still has to be accessible with its filename. + Parameters + ---------- + name : str; + a filename given a text or byte string. + Example ------- :: @@ -97,6 +102,10 @@ class BufferIOPicklable(io.BufferedReader): that can be pickled. Note that this only works in read mode. + Parameters + ---------- + raw : FileIO object; + Example ------- :: @@ -132,6 +141,10 @@ class TextIOPicklable(io.TextIOWrapper): This class provides a file-like :class:`io.TextIOWrapper` object that can be pickled. Note that this only works in read mode. + Parameters + ---------- + raw : FileIO object; + Example ------- :: @@ -168,9 +181,9 @@ def pickle_open(name, mode='rt'): This function return either BufferIOPicklable or TextIOPicklable wrapped FileIOPicklable object given different reading mode. It can be used as a - context manager, and replace the built-in `io.open` function in read mode - that only returns an unpicklable file object. - In order to serialize a `MDAnalysis.core.Universe`, this function can + context manager, and replace the built-in :func:`open` function + in read mode that only returns an unpicklable file object. + In order to serialize a :class:`MDAnalysis.core.Universe`, this function can used to open trajectory/topology files--a object composition approach, as opposed to class inheritance, which is more flexible and easier for pickle implementation for new readers. From a016a658cae0b9dff94b8caed1b5baab3b373bd6 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 13:24:00 +0200 Subject: [PATCH 26/98] add pickle_open example --- package/MDAnalysis/lib/picklable_file_io.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 88dbfd78990..a2bcc424c70 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -209,6 +209,16 @@ def pickle_open(name, mode='rt'): ------ ValueError : if `mode` is not one of the allowed read modes + Examples + ------- + :: + with pickle_open('filename') as f: + line = f.readline() + + f = pickle_open('filename') + line = f.readline() + f.close() + See Also -------- :func:`anyopen` From 401e6aede895347d044ad6b39ea79340e55d96bb Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 13:29:33 +0200 Subject: [PATCH 27/98] changelog --- package/CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 0562d7575a0..3459bc0e884 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,7 +13,7 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -??/??/?? richardjgowers, IAlibay +??/??/?? richardjgowers, IAlibay, yuxuanzhuang * 2.0.0 @@ -22,6 +22,7 @@ Fixes (Issues #2449, #2651) Enhancements + * Added lib.pickle_file_io module for pickling file handlers. (PR #2723) Changes * Removes support for passing `atoms` to XYZWriter. (Issue #2739, PR #2754) @@ -33,6 +34,7 @@ Changes * Removes deprecated ProgressMeter (Issue #2739) * Removes deprecated MDAnalysis.units.N_Avogadro (PR #2737) * Dropped Python 2 support + * Set Python 3.6 as the minimum supported version Deprecations From 9225c710c840511a5678f9da7e5b52459be1b498 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 14:52:15 +0200 Subject: [PATCH 28/98] sphinx more --- package/MDAnalysis/lib/picklable_file_io.py | 54 ++++++++++--------- .../lib/picklable_file_io.rst | 2 + .../documentation_pages/lib_modules.rst | 3 +- 3 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index a2bcc424c70..2208c54fef4 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -64,16 +64,16 @@ class FileIOPicklable(io.FileIO): Parameters ---------- - name : str; + name : str a filename given a text or byte string. Example ------- - :: - file = FileIOPicklable('filename') - file.readline() - file_pickled = pickle.loads(pickle.dumps(file) - assert_equal(file.tell(), file_pickled.tell()) + + >>> file = FileIOPicklable('filename') + >>> file.readline() + >>> file_pickled = pickle.loads(pickle.dumps(file) + >>> assert_equal(file.tell(), file_pickled.tell()) See Also --------- @@ -104,13 +104,13 @@ class BufferIOPicklable(io.BufferedReader): Parameters ---------- - raw : FileIO object; + raw : FileIO object Example ------- - :: - file = FileIOPicklable('filename') - buffer_wrapped = BufferIOPicklable(file) + + >>> file = FileIOPicklable('filename') + >>> buffer_wrapped = BufferIOPicklable(file) See Also --------- @@ -143,13 +143,13 @@ class TextIOPicklable(io.TextIOWrapper): Parameters ---------- - raw : FileIO object; + raw : FileIO object Example ------- - :: - file = FileIOPicklable('filename') - text_wrapped = TextIOPicklable(file) + + >>> file = FileIOPicklable('filename') + >>> text_wrapped = TextIOPicklable(file) See Also --------- @@ -194,7 +194,7 @@ def pickle_open(name, mode='rt'): Parameters ---------- - name : str; + name : str a filename given a text or byte string. mode: {'r', 'rt', 'rb'} (optional) 'r': open for reading in text mode; @@ -203,25 +203,31 @@ def pickle_open(name, mode='rt'): Returns ------- - stream-like object + stream-like object: BufferIOPicklable or TextIOPicklable + when mode is 'r' or 'rt', returns TextIOPicklable; + when mode is 'rb', returns BufferIOPicklable Raises ------ - ValueError : if `mode` is not one of the allowed read modes + ValueError + if `mode` is not one of the allowed read modes Examples ------- - :: - with pickle_open('filename') as f: - line = f.readline() + open as context manager:: + + >>> with pickle_open('filename') as f: + >>> line = f.readline() + + open as function:: - f = pickle_open('filename') - line = f.readline() - f.close() + >>> f = pickle_open('filename') + >>> line = f.readline() + >>> f.close() See Also -------- - :func:`anyopen` + :func:`MDAnalysis.lib.util.anyopen` :func:`io.open` diff --git a/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst b/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst new file mode 100644 index 00000000000..e5caa555e32 --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst @@ -0,0 +1,2 @@ +.. automodule:: MDAnalysis.lib.picklable_file_io + :members: diff --git a/package/doc/sphinx/source/documentation_pages/lib_modules.rst b/package/doc/sphinx/source/documentation_pages/lib_modules.rst index 29ba1a05e8a..2021efbb325 100644 --- a/package/doc/sphinx/source/documentation_pages/lib_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/lib_modules.rst @@ -60,6 +60,7 @@ List of modules ./lib/qcprot ./lib/util ./lib/correlations + ./lib/picklable_file_io Low level file formats ---------------------- @@ -75,4 +76,4 @@ Python-based projects. :maxdepth: 1 ./lib/formats/libmdaxdr - ./lib/formats/libdcd \ No newline at end of file + ./lib/formats/libdcd From 46c43afcc7a8b1baf2d2335bea38a7dab7f6bfb5 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 15:10:30 +0200 Subject: [PATCH 29/98] add context manager approach text --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 60df6a94177..0ff07507a6d 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -53,6 +53,12 @@ def test_offset(f): assert_equal(f.tell(), f_pickled.tell()) +def test_context_manager_pickle(): + with pickle_open(PDB) as file: + file_pickled = pickle.loads(pickle.dumps(file)) + assert_equal(file.readline(), file_pickled.readline()) + + @pytest.fixture(params=[ # filename mode (PDB, 'w'), From 21fe5aa3059cfffa8f477a4606a523260bcf9a36 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 15:13:29 +0200 Subject: [PATCH 30/98] add match for test valueerror --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 0ff07507a6d..c26d5269771 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -72,5 +72,5 @@ def unpicklable_f(request): def test_unpicklable_open_mode(unpicklable_f): filename, mode = unpicklable_f - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"Only read mode *"): pickle_open(filename, mode) From 821c8224335f7d22b093029df5ce9f4a9c97d391 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 15 Jun 2020 15:15:53 +0200 Subject: [PATCH 31/98] typo --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index c26d5269771..a32b1f32a92 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -55,7 +55,7 @@ def test_offset(f): def test_context_manager_pickle(): with pickle_open(PDB) as file: - file_pickled = pickle.loads(pickle.dumps(file)) + file_pickled = pickle.loads(pickle.dumps(file)) assert_equal(file.readline(), file_pickled.readline()) From 2541a3e4a144a1e36516135b74c3d7c97e39a85a Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 19 Jun 2020 09:40:16 +0200 Subject: [PATCH 32/98] tell error and fileio cov --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index a32b1f32a92..caf8f0ddee4 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -25,9 +25,8 @@ import pytest from numpy.testing import assert_equal -from MDAnalysis.lib.picklable_file_io import pickle_open +from MDAnalysis.lib.picklable_file_io import pickle_open, FileIOPicklable from MDAnalysis.tests.datafiles import PDB - import pickle @@ -59,6 +58,12 @@ def test_context_manager_pickle(): assert_equal(file.readline(), file_pickled.readline()) +def test_fileio_pickle(): + raw_io = FileIOPicklable(PDB) + raw_io_pickled = pickle.loads(pickle.dumps(raw_io)) + assert_equal(raw_io.readline(), raw_io_pickled.readline()) + + @pytest.fixture(params=[ # filename mode (PDB, 'w'), @@ -74,3 +79,10 @@ def test_unpicklable_open_mode(unpicklable_f): filename, mode = unpicklable_f with pytest.raises(ValueError, match=r"Only read mode *"): pickle_open(filename, mode) + + +def test_pickle_seek_fail(): + with pickle_open(PDB) as file: + file.__next__() + with pytest.raises(OSError, match=r"telling position disabled by *"): + file_pickled = pickle.loads(pickle.dumps(file)) From b79d28261125624927b1d7b6f719c39f59abd018 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 19 Jun 2020 14:03:54 +0200 Subject: [PATCH 33/98] remove future import --- package/MDAnalysis/lib/__init__.py | 2 -- package/MDAnalysis/lib/picklable_file_io.py | 6 ++---- package/MDAnalysis/lib/util.py | 1 - .../MDAnalysisTests/parallelism/test_multiprocessing.py | 1 - testsuite/MDAnalysisTests/utils/test_pickleio.py | 2 -- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/lib/__init__.py b/package/MDAnalysis/lib/__init__.py index 2d384ec8bcb..689d5de9e61 100644 --- a/package/MDAnalysis/lib/__init__.py +++ b/package/MDAnalysis/lib/__init__.py @@ -27,8 +27,6 @@ ================================================================ """ -from __future__ import absolute_import - __all__ = ['log', 'transformations', 'util', 'mdamath', 'distances', 'NeighborSearch', 'formats', 'pkdtree', 'nsgrid'] diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 2208c54fef4..33f0196e666 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -43,8 +43,6 @@ .. versionadded:: 2.0.0 """ -from __future__ import division, absolute_import - import io import os @@ -183,8 +181,8 @@ def pickle_open(name, mode='rt'): FileIOPicklable object given different reading mode. It can be used as a context manager, and replace the built-in :func:`open` function in read mode that only returns an unpicklable file object. - In order to serialize a :class:`MDAnalysis.core.Universe`, this function can - used to open trajectory/topology files--a object composition approach, + In order to serialize a :class:`MDAnalysis.core.Universe`, this function + can used to open trajectory/topology files--a object composition approach, as opposed to class inheritance, which is more flexible and easier for pickle implementation for new readers. diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 2c058cd7be8..6bba748e06f 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -179,7 +179,6 @@ underlying stream and ``NamedStream.close(force=True)`` will also close it. """ -from __future__ import division, absolute_import import six from six.moves import range, map import sys diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 7b155c94e70..a43853ae501 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -21,7 +21,6 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from __future__ import absolute_import import multiprocessing import numpy as np import pytest diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index caf8f0ddee4..c9d7dd8d518 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -20,8 +20,6 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from __future__ import absolute_import - import pytest from numpy.testing import assert_equal From cafc596e043d389417439c8ac0524feea148b24a Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 19 Jun 2020 16:31:11 +0200 Subject: [PATCH 34/98] sphinx block code --- package/MDAnalysis/lib/picklable_file_io.py | 35 ++++++++++--------- .../lib/picklable_file_io.rst | 1 - 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 33f0196e666..a6da58f7485 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -22,11 +22,11 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # """ -Picklable read-only I/O classes -=============================== +Picklable read-only I/O classes --- :mod:`MDAnalysis.lib.picklable_file_io` +=========================================================================== Provide with an interface for pickling read-only IO file object. -These classes are used for further pickling :class:`mda.core.Universe` +These classes are used for further pickling :class:`MDAnalysis.core.universe` in a object composition approach. .. autoclass:: FileIOPicklable @@ -67,11 +67,12 @@ class FileIOPicklable(io.FileIO): Example ------- + :: - >>> file = FileIOPicklable('filename') - >>> file.readline() - >>> file_pickled = pickle.loads(pickle.dumps(file) - >>> assert_equal(file.tell(), file_pickled.tell()) + file = FileIOPicklable('filename') + file.readline() + file_pickled = pickle.loads(pickle.dumps(file) + assert_equal(file.tell(), file_pickled.tell()) See Also --------- @@ -106,9 +107,10 @@ class BufferIOPicklable(io.BufferedReader): Example ------- + :: - >>> file = FileIOPicklable('filename') - >>> buffer_wrapped = BufferIOPicklable(file) + file = FileIOPicklable('filename') + buffer_wrapped = BufferIOPicklable(file) See Also --------- @@ -145,9 +147,10 @@ class TextIOPicklable(io.TextIOWrapper): Example ------- + :: - >>> file = FileIOPicklable('filename') - >>> text_wrapped = TextIOPicklable(file) + file = FileIOPicklable('filename') + text_wrapped = TextIOPicklable(file) See Also --------- @@ -214,14 +217,14 @@ def pickle_open(name, mode='rt'): ------- open as context manager:: - >>> with pickle_open('filename') as f: - >>> line = f.readline() + with pickle_open('filename') as f: + line = f.readline() open as function:: - >>> f = pickle_open('filename') - >>> line = f.readline() - >>> f.close() + f = pickle_open('filename') + line = f.readline() + f.close() See Also -------- diff --git a/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst b/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst index e5caa555e32..8df008afdde 100644 --- a/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst +++ b/package/doc/sphinx/source/documentation_pages/lib/picklable_file_io.rst @@ -1,2 +1 @@ .. automodule:: MDAnalysis.lib.picklable_file_io - :members: From 2db1ef29a5588abc18f4ee2a60b7b0cccadaef0d Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 20 Jun 2020 19:41:07 +0200 Subject: [PATCH 35/98] typo --- package/MDAnalysis/lib/picklable_file_io.py | 15 ++++++++------- .../parallelism/test_multiprocessing.py | 4 ++-- testsuite/MDAnalysisTests/utils/test_pickleio.py | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index a6da58f7485..a7460713a2a 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -69,10 +69,11 @@ class FileIOPicklable(io.FileIO): ------- :: - file = FileIOPicklable('filename') - file.readline() - file_pickled = pickle.loads(pickle.dumps(file) - assert_equal(file.tell(), file_pickled.tell()) + >>> file = FileIOPicklable(PDB) + >>> file.readline() + >>> file_pickled = pickle.loads(pickle.dumps(file)) + >>> print(file.tell(), file_pickled.tell()) + 55 55 See Also --------- @@ -95,7 +96,7 @@ def __setstate__(self, args): class BufferIOPicklable(io.BufferedReader): - """A picklable buffer object for read-only FilIO object. + """A picklable buffer object for read-only FileIO object. This class provides a buffered :class:`io.BufferedReader` that can be pickled. @@ -180,12 +181,12 @@ def __setstate__(self, args): def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. - This function return either BufferIOPicklable or TextIOPicklable wrapped + This function returns either BufferIOPicklable or TextIOPicklable wrapped FileIOPicklable object given different reading mode. It can be used as a context manager, and replace the built-in :func:`open` function in read mode that only returns an unpicklable file object. In order to serialize a :class:`MDAnalysis.core.Universe`, this function - can used to open trajectory/topology files--a object composition approach, + can used to open trajectory/topology files--an object composition approach, as opposed to class inheritance, which is more flexible and easier for pickle implementation for new readers. diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index a43853ae501..b214d438108 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -20,9 +20,10 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # - import multiprocessing + import numpy as np +from numpy.testing import assert_equal import pytest from MDAnalysis.lib.picklable_file_io import pickle_open @@ -30,7 +31,6 @@ PDB ) -from numpy.testing import assert_equal def textio_line(file, i): diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index c9d7dd8d518..cacb9d12757 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -20,12 +20,13 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +import pickle + import pytest from numpy.testing import assert_equal from MDAnalysis.lib.picklable_file_io import pickle_open, FileIOPicklable from MDAnalysis.tests.datafiles import PDB -import pickle @pytest.fixture(params=[ From 84baca9ce8bc0a4b45a39bd13e6af6b64fc64310 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 23 Jun 2020 11:06:52 +0200 Subject: [PATCH 36/98] pickle open pdb and xyz --- package/MDAnalysis/lib/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index c08d5d4f177..c390e6ab608 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -203,6 +203,7 @@ from numpy.testing import assert_equal import inspect +from .picklable_file_io import pickle_open from ..exceptions import StreamWarning, DuplicateWarning try: from ._cutil import unique_int_1d @@ -390,6 +391,8 @@ def anyopen(datasource, mode='rt', reset=True): else: stream = None filename = datasource + if filename.endswith('pdb') or filename.endswith('xyz'): + return pickle_open(filename, mode=mode) for ext in ('bz2', 'gz', ''): # file == '' should be last openfunc = handlers[ext] stream = _get_stream(datasource, openfunc, mode=mode) From 7cb40adbb92bb2fd33faf79af9aecc679d95090b Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 13:42:14 +0200 Subject: [PATCH 37/98] add pickle support to universe, add test, add chainreader --- package/MDAnalysis/coordinates/base.py | 9 ++ package/MDAnalysis/coordinates/chain.py | 9 ++ package/MDAnalysis/core/universe.py | 21 ++- .../MDAnalysisTests/core/test_universe.py | 8 +- .../parallelism/test_multiprocessing.py | 136 +++++++++++++++++- 5 files changed, 172 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index c4637859cb0..13956e3642e 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -381,6 +381,15 @@ def from_coordinates(cls, return ts + def __getstate__(self): + state = self.__dict__.copy() + state.pop('_reader', None) + + return state + + def __setstate__(self, state): + self.__dict__.update(state) + def _init_unitcell(self): """Create custom datastructure for :attr:`_unitcell`.""" # override for other Timesteps diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index c0c0fea3e53..0778172bc05 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -417,6 +417,15 @@ def _get_local_frame(self, k): f = k - self._start_frames[i] return i, f + def __getstate__(self): + state = self.__dict__.copy() + state.pop('_ChainReader__chained_trajectories_iter', None) + return state + + def __setstate__(self, state): + self.__dict__.update(state) + self.__chained_trajectories_iter = self._chained_iterator() + # methods that can change with the current reader def convert_time_from_native(self, t): return self.active_reader.convert_time_from_native(t) diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index b2562effb97..b7f8e88dbc9 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -701,7 +701,7 @@ def anchor_name(self): return self._anchor_uuid except AttributeError: # store this so we can later recall it if needed - self._anchor_uuid = uuid.uuid4() + self._anchor_uuid = str(uuid.uuid4()) return self._anchor_uuid @anchor_name.setter @@ -737,11 +737,22 @@ def __repr__(self): return "".format( n_atoms=len(self.atoms)) - def __getstate__(self): - raise NotImplementedError + @classmethod + def _unpickle_U(cls, top, traj, anchor): + """Special method used by __reduce__ to deserialise a Universe""" + # top is a Topology object at this point, but Universe can handle that + u = cls(top) + u.anchor_name = anchor + # maybe this is None, but that's still cool + u.trajectory = traj + + return u - def __setstate__(self, state): - raise NotImplementedError + def __reduce__(self): + # Can't quite use __setstate__/__getstate__ so go via __reduce__ + # Universe's two "legs" of topology and traj both serialise themselves + # the only other state held in Universe is anchor name? + return (self._unpickle_U, (self._topology, self._trajectory, self.anchor_name)) # Properties @property diff --git a/testsuite/MDAnalysisTests/core/test_universe.py b/testsuite/MDAnalysisTests/core/test_universe.py index 630adfa7136..ddb2a5c9d32 100644 --- a/testsuite/MDAnalysisTests/core/test_universe.py +++ b/testsuite/MDAnalysisTests/core/test_universe.py @@ -344,10 +344,12 @@ def test_load_multiple_args(self): assert_equal(len(u.atoms), 3341, "Loading universe failed somehow") assert_equal(u.trajectory.n_frames, 2 * ref.trajectory.n_frames) - def test_pickle_raises_NotImplementedError(self): + def test_pickle(self): u = mda.Universe(PSF, DCD) - with pytest.raises(NotImplementedError): - pickle.dumps(u, protocol = pickle.HIGHEST_PROTOCOL) + s = pickle.dumps(u, protocol = pickle.HIGHEST_PROTOCOL) + new_u = pickle.loads(s) + assert_equal(u.atoms.names, new_u.atoms.names) + @pytest.mark.parametrize('dtype', (int, np.float32, np.float64)) def test_set_dimensions(self, dtype): diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index b214d438108..8b652fd213a 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -20,19 +20,47 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +import sys import multiprocessing import numpy as np -from numpy.testing import assert_equal import pytest +import pickle +from numpy.testing import assert_equal +import MDAnalysis as mda from MDAnalysis.lib.picklable_file_io import pickle_open +from MDAnalysis.coordinates.core import get_reader_for + from MDAnalysisTests.datafiles import ( - PDB + AUX_XVG, + CRD, + PSF, DCD, + DMS, + DLP_CONFIG, + DLP_HISTORY, + INPCRD, + GMS_ASYMOPT, + GRO, + GSD, + LAMMPSdata_mini, + LAMMPSDUMP, + mol2_molecules, + MMTF, + NCDF, + PDB, PDB_small, PDB_multiframe, + PDBQT_input, + PQR, + TRR, + TRJ, + TRZ, + TXYZ, + XTC, + XPDB_small, + XYZ_mini, XYZ, ) - def textio_line(file, i): return file.readlines()[i] @@ -46,3 +74,105 @@ def test_multiprocess_fileio(): for i in range(4)]) p.close() assert_equal(res, ref) + + +@pytest.fixture(params=[ + (PSF, DCD), + (GRO, XTC), + (PDB_multiframe,), + (XYZ,), +]) +def u(request): + if len(request.param) == 1: + f = request.param[0] + return mda.Universe(f) + else: + top, trj = request.param + return mda.Universe(top, trj) + +# Define target functions here +# inside test functions doesn't work +def cog(u, ag, frame_id): + u.trajectory[frame_id] + + return ag.center_of_geometry() + + +def getnames(u, ix): + # Check topology stuff works + return u.atoms[ix].name + + +@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ + working in python 2") +def test_multiprocess_COG(u): + ag = u.atoms[10:20] + + ref = np.array([cog(u, ag, i) + for i in range(4)]) + + p = multiprocessing.Pool(2) + res = np.array([p.apply(cog, args=(u, ag, i)) + for i in range(4)]) + p.close() + assert_equal(ref, res) + + +@pytest.mark.xfail(sys.version_info <= (3, 0), reason="pickle function not \ + working in python 2") +def test_multiprocess_names(u): + ref = [getnames(u, i) + for i in range(10)] + + p = multiprocessing.Pool(2) + res = [p.apply(getnames, args=(u, i)) + for i in range(10)] + p.close() + + assert_equal(ref, res) + +@pytest.fixture(params=[ + # formatname, filename + ('CRD', CRD, dict()), + ('DATA', LAMMPSdata_mini, dict(n_atoms=1)), + ('DCD', DCD, dict()), + ('DMS', DMS, dict()), + ('CONFIG', DLP_CONFIG, dict()), + ('HISTORY', DLP_HISTORY, dict()), + ('INPCRD', INPCRD, dict()), + ('LAMMPSDUMP', LAMMPSDUMP, dict()), + ('GMS', GMS_ASYMOPT, dict()), + ('GRO', GRO, dict()), + ('GSD', GSD, dict()), + ('MMTF', MMTF, dict()), + ('MOL2', mol2_molecules, dict()), + ('PDB', PDB_small, dict()), + ('PQR', PQR, dict()), + ('PDBQT', PDBQT_input, dict()), + ('TRR', TRR, dict()), + ('TRZ', TRZ, dict(n_atoms=8184)), + ('TRJ', TRJ, dict(n_atoms=252)), + ('XTC', XTC, dict()), + ('XPDB', XPDB_small, dict()), + ('XYZ', XYZ_mini, dict()), + ('NCDF', NCDF, dict()), + ('TXYZ', TXYZ, dict()), + ('memory', np.arange(60).reshape(2, 10, 3).astype(np.float64), dict()), + ('CHAIN', [GRO, GRO, GRO], dict()), +]) +def ref_reader(request): + fmt_name, filename, extras = request.param + + r = get_reader_for(filename, format=fmt_name)(filename, **extras) + try: + yield r + finally: + # make sure file handle is closed afterwards + r.close() + +def test_readers_pickle(ref_reader): + ps = pickle.dumps(ref_reader) + + reanimated = pickle.loads(ps) + + assert len(ref_reader) == len(reanimated) From 352ab9622892ed39a3173435e3774f21f0a9ab70 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 15:03:58 +0200 Subject: [PATCH 38/98] fix misc issues --- package/MDAnalysis/coordinates/DLPoly.py | 3 +- package/MDAnalysis/coordinates/GSD.py | 4 +- package/MDAnalysis/coordinates/TRJ.py | 6 +- package/MDAnalysis/lib/picklable_file_io.py | 105 +++++++++++++++++++- package/MDAnalysis/lib/util.py | 12 +-- 5 files changed, 115 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/coordinates/DLPoly.py b/package/MDAnalysis/coordinates/DLPoly.py index b4e447aabfe..9e499a9b58a 100644 --- a/package/MDAnalysis/coordinates/DLPoly.py +++ b/package/MDAnalysis/coordinates/DLPoly.py @@ -32,6 +32,7 @@ from . import base from . import core +from ..lib import util _DLPOLY_UNITS = {'length': 'Angstrom', 'velocity': 'Angstrom/ps', 'time': 'ps'} @@ -149,7 +150,7 @@ def __init__(self, filename, **kwargs): super(HistoryReader, self).__init__(filename, **kwargs) # "private" file handle - self._file = open(self.filename, 'r') + self._file = util.anyopen(self.filename, 'r') self.title = self._file.readline().strip() self._levcfg, self._imcon, self.n_atoms = np.int64(self._file.readline().split()[:3]) self._has_vels = True if self._levcfg > 0 else False diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index 5d10ef7348c..2391499ab4c 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -50,6 +50,8 @@ import gsd.hoomd from . import base +from ..lib.picklable_file_io import gsd_pickle_open + class GSDReader(base.ReaderBase): """Reader for the GSD format. @@ -80,7 +82,7 @@ def __init__(self, filename, **kwargs): def open_trajectory(self) : """opens the trajectory file using gsd.hoomd module""" self._frame = -1 - self._file = gsd.hoomd.open(self.filename,mode='rb') + self._file = gsd_pickle_open(self.filename,mode='rb') def close(self): """close reader""" diff --git a/package/MDAnalysis/coordinates/TRJ.py b/package/MDAnalysis/coordinates/TRJ.py index 04c1a463f4c..e1affbbddc5 100644 --- a/package/MDAnalysis/coordinates/TRJ.py +++ b/package/MDAnalysis/coordinates/TRJ.py @@ -158,7 +158,7 @@ import MDAnalysis from . import base from ..lib import util - +from ..lib.picklable_file_io import ncdf_pickle_open logger = logging.getLogger("MDAnalysis.coordinates.AMBER") @@ -469,8 +469,8 @@ def __init__(self, filename, n_atoms=None, mmap=None, **kwargs): super(NCDFReader, self).__init__(filename, **kwargs) - self.trjfile = scipy.io.netcdf.netcdf_file(self.filename, - mmap=self._mmap) + self.trjfile = ncdf_pickle_open(self.filename, + mmap=self._mmap) # AMBER NetCDF files should always have a convention try: diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index a7460713a2a..e5caaecddb5 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -1,4 +1,3 @@ - # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # @@ -46,6 +45,11 @@ import io import os +import bz2 +import gzip +from gsd import fl +import gsd.hoomd +import scipy.io class FileIOPicklable(io.FileIO): """File object (read-only) that can be pickled. @@ -87,7 +91,12 @@ def __init__(self, name): super().__init__(name, mode='r') def __getstate__(self): - return self.name, self.tell() + try: + tell = self.tell() + except: + # when tell is disabled by next() + tell = 0 + return self.name, tell def __setstate__(self, args): name = args[0] @@ -126,7 +135,11 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - return self.raw_class, self.name, self.tell() + try: + tell = self.tell() + except: + tell = 0 + return self.raw_class, self.name, tell def __setstate__(self, args): raw_class = args[0] @@ -166,7 +179,11 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - return self.raw_class, self.name, self.tell() + try: + tell = self.tell() + except: + tell = 0 + return self.raw_class, self.name, tell def __setstate__(self, args): raw_class = args[0] @@ -178,6 +195,56 @@ def __setstate__(self, args): self.seek(args[2]) +class BZ2Picklable(bz2.BZ2File): + def __init__(self, name, mode): + super().__init__(name, mode) + + def __getstate__(self): + try: + tell = self.tell() + except: + tell = 0 + return self._fp.name, tell + + def __setstate__(self, args): + super().__init__(args[0]) + self.seek(args[1]) + + +class GzipPicklable(gzip.GzipFile): + def __init__(self, name, mode='rb'): + super().__init__(name, mode) + + def __getstate__(self): + try: + tell = self.tell() + except: + tell = 0 + return self.name, tell + def __setstate__(self, args): + super().__init__(args[0]) + self.seek(args[1]) + + +class GSDPicklable(gsd.hoomd.HOOMDTrajectory): + def __getstate__(self): + return self.file.name, self.file.mode + def __setstate__(self, args): + gsdfileobj = fl.open(name=args[0], + mode=args[1], + application='gsd.hoomd ' + gsd.__version__, + schema='hoomd', + schema_version=[1,3]) + return self.__init__(gsdfileobj) + + +class NCDFPicklable(scipy.io.netcdf.netcdf_file): + def __getstate__(self): + return self.filename, self.use_mmap + def __setstate__(self, args): + self.__init__(args[0], mmap=args[1]) + + def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. @@ -244,3 +311,33 @@ def pickle_open(name, mode='rt'): return BufferIOPicklable(raw) elif mode in {'r', 'rt'}: return TextIOPicklable(raw) + + +def bz2_pickle_open(name, mode): + bz_mode = mode.replace("t", "") + binary_file = BZ2Picklable(name, bz_mode) + if "t" in mode: + return TextIOPicklable(binary_file) + else: + return binary_file + +def gzip_pickle_open(name, mode): + gz_mode = mode.replace("t", "") + binary_file = GzipPicklable(name, gz_mode) + if "t" in mode: + return TextIOPicklable(binary_file) + else: + return binary_file + + +def gsd_pickle_open(name, mode): + gsdfileobj = fl.open(name=name, + mode=mode, + application='gsd.hoomd ' + gsd.__version__, + schema='hoomd', + schema_version=[1,3]) + return GSDPicklable(gsdfileobj) + + +def ncdf_pickle_open(name, mmap): + return NCDFPicklable(name, mmap=mmap) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index c390e6ab608..848e61aa7bc 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -203,7 +203,8 @@ from numpy.testing import assert_equal import inspect -from .picklable_file_io import pickle_open +from .picklable_file_io import pickle_open, bz2_pickle_open, gzip_pickle_open + from ..exceptions import StreamWarning, DuplicateWarning try: from ._cutil import unique_int_1d @@ -369,7 +370,8 @@ def anyopen(datasource, mode='rt', reset=True): behavior to return a tuple ``(stream, filename)``. """ - handlers = {'bz2': bz2_open, 'gz': gzip.open, '': open} + read_handlers = {'bz2': bz2_pickle_open, 'gz': gzip_pickle_open, '': pickle_open} + write_handlers = {'bz2': bz2_open, 'gz': gzip.open, '': open} if mode.startswith('r'): if isstream(datasource): @@ -391,10 +393,8 @@ def anyopen(datasource, mode='rt', reset=True): else: stream = None filename = datasource - if filename.endswith('pdb') or filename.endswith('xyz'): - return pickle_open(filename, mode=mode) for ext in ('bz2', 'gz', ''): # file == '' should be last - openfunc = handlers[ext] + openfunc = read_handlers[ext] stream = _get_stream(datasource, openfunc, mode=mode) if stream is not None: break @@ -415,7 +415,7 @@ def anyopen(datasource, mode='rt', reset=True): ext = ext[1:] if not ext in ('bz2', 'gz'): ext = '' # anything else but bz2 or gz is just a normal file - openfunc = handlers[ext] + openfunc = write_handlers[ext] stream = openfunc(datasource, mode=mode) if stream is None: raise IOError(errno.EIO, "Cannot open file or stream in mode={mode!r}.".format(**vars()), repr(filename)) From 356986f738af8598dfbccd102d6e790efd7f044c Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 15:04:43 +0200 Subject: [PATCH 39/98] remove python2 legacy bz2 --- package/MDAnalysis/lib/util.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 848e61aa7bc..5fc2165e42b 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -314,25 +314,6 @@ def openany(datasource, mode='rt', reset=True): stream.close() -# On python 3, we want to use bz2.open to open and uncompress bz2 files. That -# function allows to specify the type of the uncompressed file (bytes ot text). -# The function does not exist in python 2, thus we must use bz2.BZFile to -# which we cannot tell if the uncompressed file contains bytes or text. -# Therefore, on python 2 we use a proxy function that removes the type of the -# uncompressed file from the `mode` argument. -try: - bz2.open -except AttributeError: - # We are on python 2 and bz2.open is not available - def bz2_open(filename, mode): - """Open and uncompress a BZ2 file""" - mode = mode.replace('t', '').replace('b', '') - return bz2.BZ2File(filename, mode) -else: - # We are on python 3 so we can use bz2.open - bz2_open = bz2.open - - def anyopen(datasource, mode='rt', reset=True): """Open datasource (gzipped, bzipped, uncompressed) and return a stream. @@ -371,7 +352,7 @@ def anyopen(datasource, mode='rt', reset=True): """ read_handlers = {'bz2': bz2_pickle_open, 'gz': gzip_pickle_open, '': pickle_open} - write_handlers = {'bz2': bz2_open, 'gz': gzip.open, '': open} + write_handlers = {'bz2': bz2.open, 'gz': gzip.open, '': open} if mode.startswith('r'): if isstream(datasource): From e5ef732a6b9062cb26edbf7ca403d5f54742bdee Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 20:41:04 +0200 Subject: [PATCH 40/98] remove fail test for offset --- package/MDAnalysis/lib/picklable_file_io.py | 28 +++++++++++++------ .../MDAnalysisTests/utils/test_pickleio.py | 6 ++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index e5caaecddb5..da7440be7b0 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -51,6 +51,7 @@ import gsd.hoomd import scipy.io + class FileIOPicklable(io.FileIO): """File object (read-only) that can be pickled. @@ -93,7 +94,7 @@ def __init__(self, name): def __getstate__(self): try: tell = self.tell() - except: + except OSError: # when tell is disabled by next() tell = 0 return self.name, tell @@ -137,7 +138,7 @@ def __init__(self, raw): def __getstate__(self): try: tell = self.tell() - except: + except OSError: tell = 0 return self.raw_class, self.name, tell @@ -181,9 +182,14 @@ def __init__(self, raw): def __getstate__(self): try: tell = self.tell() - except: + except OSError: tell = 0 - return self.raw_class, self.name, tell + try: + name = self.name + except AttributeError: + # This is kind of ugly--BZ2File does not save its name. + name = self.buffer._fp.name + return self.raw_class, name, tell def __setstate__(self, args): raw_class = args[0] @@ -196,13 +202,13 @@ def __setstate__(self, args): class BZ2Picklable(bz2.BZ2File): - def __init__(self, name, mode): + def __init__(self, name, mode='rb'): super().__init__(name, mode) def __getstate__(self): try: tell = self.tell() - except: + except OSError: tell = 0 return self._fp.name, tell @@ -218,9 +224,10 @@ def __init__(self, name, mode='rb'): def __getstate__(self): try: tell = self.tell() - except: + except OSError: tell = 0 return self.name, tell + def __setstate__(self, args): super().__init__(args[0]) self.seek(args[1]) @@ -229,18 +236,20 @@ def __setstate__(self, args): class GSDPicklable(gsd.hoomd.HOOMDTrajectory): def __getstate__(self): return self.file.name, self.file.mode + def __setstate__(self, args): gsdfileobj = fl.open(name=args[0], mode=args[1], application='gsd.hoomd ' + gsd.__version__, schema='hoomd', - schema_version=[1,3]) + schema_version=[1, 3]) return self.__init__(gsdfileobj) class NCDFPicklable(scipy.io.netcdf.netcdf_file): def __getstate__(self): return self.filename, self.use_mmap + def __setstate__(self, args): self.__init__(args[0], mmap=args[1]) @@ -321,6 +330,7 @@ def bz2_pickle_open(name, mode): else: return binary_file + def gzip_pickle_open(name, mode): gz_mode = mode.replace("t", "") binary_file = GzipPicklable(name, gz_mode) @@ -335,7 +345,7 @@ def gsd_pickle_open(name, mode): mode=mode, application='gsd.hoomd ' + gsd.__version__, schema='hoomd', - schema_version=[1,3]) + schema_version=[1, 3]) return GSDPicklable(gsdfileobj) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index cacb9d12757..9107cc50268 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -80,8 +80,8 @@ def test_unpicklable_open_mode(unpicklable_f): pickle_open(filename, mode) -def test_pickle_seek_fail(): +def test_pickle_seek_fallback(): with pickle_open(PDB) as file: file.__next__() - with pytest.raises(OSError, match=r"telling position disabled by *"): - file_pickled = pickle.loads(pickle.dumps(file)) + file_pickled = pickle.loads(pickle.dumps(file)) + assert_equal(file_pickled.tell(), 0) From aa6e40dd09e83be63b186f7a48f713e5c0a73be5 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 21:12:41 +0200 Subject: [PATCH 41/98] issue raised in changelog --- package/CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 0f32976c84c..8b3238c47a1 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -49,7 +49,7 @@ Changes * Removes deprecated ProgressMeter (Issue #2739) * Removes deprecated MDAnalysis.units.N_Avogadro (PR #2737) * Dropped Python 2 support - * Set Python 3.6 as the minimum supported version + * Set Python 3.6 as the minimum supported version (Issue #2541) Deprecations From 43a62d5450cb21e78a33999ead92fa2e74f7e38c Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 24 Jun 2020 21:31:03 +0200 Subject: [PATCH 42/98] pep8 --- .../MDAnalysisTests/parallelism/test_multiprocessing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 8b652fd213a..29f7a6cd0be 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -90,6 +90,7 @@ def u(request): top, trj = request.param return mda.Universe(top, trj) + # Define target functions here # inside test functions doesn't work def cog(u, ag, frame_id): @@ -126,11 +127,12 @@ def test_multiprocess_names(u): p = multiprocessing.Pool(2) res = [p.apply(getnames, args=(u, i)) - for i in range(10)] + for i in range(10)] p.close() assert_equal(ref, res) + @pytest.fixture(params=[ # formatname, filename ('CRD', CRD, dict()), @@ -170,6 +172,7 @@ def ref_reader(request): # make sure file handle is closed afterwards r.close() + def test_readers_pickle(ref_reader): ps = pickle.dumps(ref_reader) From 2559625c26d95770d3ecc22ac5c492eb3c895cf4 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 26 Jun 2020 11:06:17 +0200 Subject: [PATCH 43/98] add pickle func to ReaderBase and set offset --- package/MDAnalysis/coordinates/base.py | 9 +++- package/MDAnalysis/lib/picklable_file_io.py | 32 +++----------- .../MDAnalysisTests/utils/test_pickleio.py | 42 ++++++++++++------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 13956e3642e..d2230939439 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -2115,7 +2115,7 @@ def __init__(self, filename, convert_units=True, **kwargs): else: ts_kwargs[att] = val - self._ts_kwargs = ts_kwargs + self. _ts_kwargs = ts_kwargs def copy(self): """Return independent copy of this Reader. @@ -2144,6 +2144,13 @@ def __del__(self): self._auxs[aux].close() self.close() + def __getstate__(self): + return self.__dict__ + + def __setstate__(self, state): + self.__dict__ = state + self[self.ts.frame] + class _Writermeta(type): # Auto register this format upon class creation diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index da7440be7b0..d9d6c78d897 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -92,12 +92,7 @@ def __init__(self, name): super().__init__(name, mode='r') def __getstate__(self): - try: - tell = self.tell() - except OSError: - # when tell is disabled by next() - tell = 0 - return self.name, tell + return self.name, self.tell() def __setstate__(self, args): name = args[0] @@ -136,11 +131,7 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - try: - tell = self.tell() - except OSError: - tell = 0 - return self.raw_class, self.name, tell + return self.raw_class, self.name, self.tell() def __setstate__(self, args): raw_class = args[0] @@ -180,16 +171,12 @@ def __init__(self, raw): self.raw_class = raw.__class__ def __getstate__(self): - try: - tell = self.tell() - except OSError: - tell = 0 try: name = self.name except AttributeError: # This is kind of ugly--BZ2File does not save its name. name = self.buffer._fp.name - return self.raw_class, name, tell + return self.raw_class, name def __setstate__(self, args): raw_class = args[0] @@ -198,7 +185,6 @@ def __setstate__(self, args): # GZip files, which also requires a text wrapper. raw = raw_class(name) super().__init__(raw) - self.seek(args[2]) class BZ2Picklable(bz2.BZ2File): @@ -206,11 +192,7 @@ def __init__(self, name, mode='rb'): super().__init__(name, mode) def __getstate__(self): - try: - tell = self.tell() - except OSError: - tell = 0 - return self._fp.name, tell + return self._fp.name, self.tell() def __setstate__(self, args): super().__init__(args[0]) @@ -222,11 +204,7 @@ def __init__(self, name, mode='rb'): super().__init__(name, mode) def __getstate__(self): - try: - tell = self.tell() - except OSError: - tell = 0 - return self.name, tell + return self.name, self.tell() def __setstate__(self, args): super().__init__(args[0]) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 9107cc50268..00f1d0058eb 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -33,22 +33,41 @@ # filename mode (PDB, 'r'), (PDB, 'rt'), +]) +def f_text(request): + filename, mode = request.param + return pickle_open(filename, mode) + + +def test_iopickle_text(f_text): + f_text_pickled = pickle.loads(pickle.dumps(f_text)) + assert_equal(f_text.readline(), f_text_pickled.readline()) + + +def test_offset_text_to_0(f_text): + f_text.readline() + f_text_pickled = pickle.loads(pickle.dumps(f_text)) + assert_equal(f_text_pickled.tell(), 0) + + +@pytest.fixture(params=[ + # filename mode (PDB, 'rb'), ]) -def f(request): +def f_byte(request): filename, mode = request.param return pickle_open(filename, mode) -def test_iopickle(f): - f_pickled = pickle.loads(pickle.dumps(f)) - assert_equal(f.readline(), f_pickled.readline()) +def test_iopickle_byte(f_byte): + f_byte_pickled = pickle.loads(pickle.dumps(f_byte)) + assert_equal(f_byte.readline(), f_byte_pickled.readline()) -def test_offset(f): - f.readline() - f_pickled = pickle.loads(pickle.dumps(f)) - assert_equal(f.tell(), f_pickled.tell()) +def test_offset_byte_to_tell(f_byte): + f_byte.readline() + f_byte_pickled = pickle.loads(pickle.dumps(f_byte)) + assert_equal(f_byte_pickled.tell(), f_byte.tell()) def test_context_manager_pickle(): @@ -78,10 +97,3 @@ def test_unpicklable_open_mode(unpicklable_f): filename, mode = unpicklable_f with pytest.raises(ValueError, match=r"Only read mode *"): pickle_open(filename, mode) - - -def test_pickle_seek_fallback(): - with pickle_open(PDB) as file: - file.__next__() - file_pickled = pickle.loads(pickle.dumps(file)) - assert_equal(file_pickled.tell(), 0) From 507f8f5bc77bd95a3ee98402b6b214c98679e23d Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 26 Jun 2020 11:58:05 +0200 Subject: [PATCH 44/98] add test for bz2 gzip and class check --- .../MDAnalysisTests/utils/test_pickleio.py | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 00f1d0058eb..76f35675c85 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -25,18 +25,37 @@ import pytest from numpy.testing import assert_equal -from MDAnalysis.lib.picklable_file_io import pickle_open, FileIOPicklable -from MDAnalysis.tests.datafiles import PDB +from MDAnalysis.lib.util import anyopen +from MDAnalysis.lib.picklable_file_io import ( + pickle_open, + BufferIOPicklable, + FileIOPicklable, + TextIOPicklable, + BZ2Picklable, + GzipPicklable +) +from MDAnalysis.tests.datafiles import ( + PDB, + XYZ_bz2, + MMTF_gz, + GMS_ASYMOPT +) @pytest.fixture(params=[ # filename mode (PDB, 'r'), (PDB, 'rt'), + (XYZ_bz2, 'rt'), + (GMS_ASYMOPT, 'rt') ]) def f_text(request): filename, mode = request.param - return pickle_open(filename, mode) + return anyopen(filename, mode) + + +def test_get_right_open_handler_text(f_text): + assert_equal(f_text.__class__, TextIOPicklable) def test_iopickle_text(f_text): @@ -51,23 +70,29 @@ def test_offset_text_to_0(f_text): @pytest.fixture(params=[ - # filename mode - (PDB, 'rb'), + # filename mode ref_class + (PDB, 'rb', BufferIOPicklable), + (XYZ_bz2, 'rb', BZ2Picklable), + (MMTF_gz, 'rb', GzipPicklable) ]) def f_byte(request): - filename, mode = request.param - return pickle_open(filename, mode) + filename, mode, ref_reader_class = request.param + return (anyopen(filename, mode), ref_reader_class) + + +def test_get_right_open_handler_byte(f_byte): + assert_equal(f_byte[0].__class__, f_byte[1]) def test_iopickle_byte(f_byte): - f_byte_pickled = pickle.loads(pickle.dumps(f_byte)) - assert_equal(f_byte.readline(), f_byte_pickled.readline()) + f_byte_pickled = pickle.loads(pickle.dumps(f_byte[0])) + assert_equal(f_byte[0].readline(), f_byte_pickled.readline()) def test_offset_byte_to_tell(f_byte): - f_byte.readline() - f_byte_pickled = pickle.loads(pickle.dumps(f_byte)) - assert_equal(f_byte_pickled.tell(), f_byte.tell()) + f_byte[0].readline() + f_byte_pickled = pickle.loads(pickle.dumps(f_byte[0])) + assert_equal(f_byte_pickled.tell(), f_byte[0].tell()) def test_context_manager_pickle(): From 26fcfe9ba4705ee71a9865467f0eb6ea91109169 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 26 Jun 2020 12:40:22 +0200 Subject: [PATCH 45/98] add test for gsd, ncdf --- package/MDAnalysis/lib/picklable_file_io.py | 17 ++++-- .../MDAnalysisTests/utils/test_pickleio.py | 56 +++++++++++++------ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index d9d6c78d897..622516c6108 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -300,7 +300,10 @@ def pickle_open(name, mode='rt'): return TextIOPicklable(raw) -def bz2_pickle_open(name, mode): +def bz2_pickle_open(name, mode='rt'): + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') \ + files can be pickled.") bz_mode = mode.replace("t", "") binary_file = BZ2Picklable(name, bz_mode) if "t" in mode: @@ -309,7 +312,10 @@ def bz2_pickle_open(name, mode): return binary_file -def gzip_pickle_open(name, mode): +def gzip_pickle_open(name, mode='rt'): + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') \ + files can be pickled.") gz_mode = mode.replace("t", "") binary_file = GzipPicklable(name, gz_mode) if "t" in mode: @@ -318,7 +324,10 @@ def gzip_pickle_open(name, mode): return binary_file -def gsd_pickle_open(name, mode): +def gsd_pickle_open(name, mode='rb'): + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') \ + files can be pickled.") gsdfileobj = fl.open(name=name, mode=mode, application='gsd.hoomd ' + gsd.__version__, @@ -327,5 +336,5 @@ def gsd_pickle_open(name, mode): return GSDPicklable(gsdfileobj) -def ncdf_pickle_open(name, mmap): +def ncdf_pickle_open(name, mmap=None): return NCDFPicklable(name, mmap=mmap) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 76f35675c85..c4bc63277c9 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -27,18 +27,24 @@ from MDAnalysis.lib.util import anyopen from MDAnalysis.lib.picklable_file_io import ( - pickle_open, BufferIOPicklable, FileIOPicklable, TextIOPicklable, BZ2Picklable, - GzipPicklable + GzipPicklable, + pickle_open, + bz2_pickle_open, + gzip_pickle_open, + gsd_pickle_open, + ncdf_pickle_open ) from MDAnalysis.tests.datafiles import ( PDB, XYZ_bz2, MMTF_gz, - GMS_ASYMOPT + GMS_ASYMOPT, + GSD, + NCDF ) @@ -77,7 +83,7 @@ def test_offset_text_to_0(f_text): ]) def f_byte(request): filename, mode, ref_reader_class = request.param - return (anyopen(filename, mode), ref_reader_class) + return anyopen(filename, mode), ref_reader_class def test_get_right_open_handler_byte(f_byte): @@ -85,14 +91,16 @@ def test_get_right_open_handler_byte(f_byte): def test_iopickle_byte(f_byte): - f_byte_pickled = pickle.loads(pickle.dumps(f_byte[0])) - assert_equal(f_byte[0].readline(), f_byte_pickled.readline()) + file = f_byte[0] + f_byte_pickled = pickle.loads(pickle.dumps(file)) + assert_equal(file.readline(), f_byte_pickled.readline()) def test_offset_byte_to_tell(f_byte): - f_byte[0].readline() - f_byte_pickled = pickle.loads(pickle.dumps(f_byte[0])) - assert_equal(f_byte_pickled.tell(), f_byte[0].tell()) + file = f_byte[0] + file.readline() + f_byte_pickled = pickle.loads(pickle.dumps(file)) + assert_equal(f_byte_pickled.tell(), file.tell()) def test_context_manager_pickle(): @@ -109,16 +117,32 @@ def test_fileio_pickle(): @pytest.fixture(params=[ # filename mode - (PDB, 'w'), - (PDB, 'x'), - (PDB, 'a'), + (PDB, 'w', pickle_open), + (PDB, 'x', pickle_open), + (PDB, 'a', pickle_open), + (XYZ_bz2, 'w', bz2_pickle_open), + (MMTF_gz, 'w', gzip_pickle_open) ]) def unpicklable_f(request): - filename, mode = request.param - return filename, mode + filename, mode, open_func = request.param + return filename, mode, open_func def test_unpicklable_open_mode(unpicklable_f): - filename, mode = unpicklable_f + filename, mode, open_func = unpicklable_f with pytest.raises(ValueError, match=r"Only read mode *"): - pickle_open(filename, mode) + open_func(filename, mode) + + +def test_GSD_pickle(): + gsd_io = gsd_pickle_open(GSD, mode='rb') + gsd_io_pickled = pickle.loads(pickle.dumps(gsd_io)) + assert_equal(gsd_io.read_frame(0).particles.position, + gsd_io_pickled.read_frame(0).particles.position) + + +def test_NCDF_pickle(): + ncdf_io = ncdf_pickle_open(NCDF, mmap=None) + ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) + assert_equal(ncdf_io.variables['coordinates'][0], + ncdf_io_pickled.variables['coordinates'][0]) From 405a6dc9a0bc6c5770ed8e9cfc3c795a70b7e803 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 26 Jun 2020 15:05:14 +0200 Subject: [PATCH 46/98] add test for trajectory.next after pickling --- .../MDAnalysisTests/data/example_longer.gsd | Bin 0 -> 28419 bytes testsuite/MDAnalysisTests/datafiles.py | 3 +- .../parallelism/test_multiprocessing.py | 36 ++++++++++++------ 3 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/example_longer.gsd diff --git a/testsuite/MDAnalysisTests/data/example_longer.gsd b/testsuite/MDAnalysisTests/data/example_longer.gsd new file mode 100644 index 0000000000000000000000000000000000000000..3ca2fbe78a345225875d6477a46c855aa20c0db3 GIT binary patch literal 28419 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 ab=u>7iFI%#WC?8HIpS0IO#J=iANqe2B?Dss literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 6c275939a16..d625b6b9906 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -166,7 +166,7 @@ "legacy_DCD_ADK_coords", # frames 5 and 29 read in for adk_dims.dcd using legacy DCD reader "legacy_DCD_NAMD_coords", # frame 0 read in for SiN_tric_namd.dcd using legacy DCD reader "legacy_DCD_c36_coords", # frames 1 and 4 read in for tip125_tric_C36.dcd using legacy DCD reader - "GSD", "GSD_bonds", + "GSD", "GSD_bonds", "GSD_long", "GRO_MEMPROT", "XTC_MEMPROT", # YiiP transporter in POPE:POPG lipids with Na+, Cl-, Zn2+ dummy model without water "DihedralArray", "DihedralsArray", # time series of single dihedral "RamaArray", "GLYRamaArray", # time series of phi/psi angles @@ -499,6 +499,7 @@ GSD = resource_filename(__name__, 'data/example.gsd') GSD_bonds = resource_filename(__name__, 'data/example_bonds.gsd') +GSD_long = resource_filename(__name__, 'data/example_longer.gsd') DihedralArray = resource_filename(__name__, 'data/adk_oplsaa_dihedral.npy') DihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_dihedral_list.npy') diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 29f7a6cd0be..783d2ba7e49 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -20,7 +20,6 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import sys import multiprocessing import numpy as np @@ -33,7 +32,6 @@ from MDAnalysis.coordinates.core import get_reader_for from MDAnalysisTests.datafiles import ( - AUX_XVG, CRD, PSF, DCD, DMS, @@ -41,8 +39,10 @@ DLP_HISTORY, INPCRD, GMS_ASYMOPT, + GMS_SYMOPT, GRO, GSD, + GSD_long, LAMMPSdata_mini, LAMMPSDUMP, mol2_molecules, @@ -57,7 +57,7 @@ TXYZ, XTC, XPDB_small, - XYZ_mini, XYZ, + XYZ_mini, XYZ, XYZ_bz2, ) @@ -81,6 +81,11 @@ def test_multiprocess_fileio(): (GRO, XTC), (PDB_multiframe,), (XYZ,), + (XYZ_bz2,), # .bz2 + (GMS_SYMOPT,), # .gms + (GMS_ASYMOPT,), # .gz + (GSD_long,), + (NCDF,), ]) def u(request): if len(request.param) == 1: @@ -104,30 +109,37 @@ def getnames(u, ix): return u.atoms[ix].name -@pytest.mark.xfail(sys.version_info < (3, 0), reason="pickle function not \ - working in python 2") +def test_trajecotry_next(u): + u.trajectory.next() + u_p = pickle.loads(pickle.dumps(u)) + u.trajectory.next() + u_p.trajectory.next() + assert_equal(u.atoms.positions[0], u_p.atoms.positions[0]) + + def test_multiprocess_COG(u): - ag = u.atoms[10:20] + ag = u.atoms[2:5] ref = np.array([cog(u, ag, i) - for i in range(4)]) + for i in range(3)]) p = multiprocessing.Pool(2) res = np.array([p.apply(cog, args=(u, ag, i)) - for i in range(4)]) + for i in range(3)]) p.close() assert_equal(ref, res) -@pytest.mark.xfail(sys.version_info <= (3, 0), reason="pickle function not \ - working in python 2") def test_multiprocess_names(u): + if u.trajectory.__class__ == mda.coordinates.TRJ.NCDFReader: + # NDCFReader contains no information on atom name + return True ref = [getnames(u, i) - for i in range(10)] + for i in range(3)] p = multiprocessing.Pool(2) res = [p.apply(getnames, args=(u, i)) - for i in range(10)] + for i in range(3)] p.close() assert_equal(ref, res) From 2380a475a034d9e3751087e1986e96ec471d923c Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 26 Jun 2020 18:57:28 +0200 Subject: [PATCH 47/98] older gsd file --- .../MDAnalysisTests/data/example_longer.gsd | Bin 28419 -> 1591928 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testsuite/MDAnalysisTests/data/example_longer.gsd b/testsuite/MDAnalysisTests/data/example_longer.gsd index 3ca2fbe78a345225875d6477a46c855aa20c0db3..2232af124f121e414a85fadd4346887a4068bda0 100644 GIT binary patch literal 1591928 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�lxqb1meZlPu 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

n_Q*GXmKbX$SGRR4fKtyGBFS_P_Pc^T`k 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

a~ohUdx@T*K(KYwcM?GE%&Hi%e|`C@^jT|`GxAW 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%0DqRuD^yjvgf&SR0jNQw2_z0Z4X6+{I~ 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

?ujMcw@cv$8Wvv0i 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%0A|2fWEwA{-qwl-~Bbh;)Yy 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 ab=u>7iFI%#WC?8HIpS0IO#J=iANqe2B?Dss From dab38c14ed788affea7f47168c4ec9737bd1124f Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 29 Jun 2020 10:12:53 +0200 Subject: [PATCH 48/98] move gsd, ncdf to coord --- package/MDAnalysis/coordinates/GSD.py | 40 +++++++++++++++--- package/MDAnalysis/coordinates/TRJ.py | 14 +++++- .../MDAnalysisTests/data/example_longer.gsd | Bin 1591928 -> 1879072 bytes 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index 2391499ab4c..2447c2178ee 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -47,10 +47,11 @@ """ import numpy as np import os +import gsd +import gsd.fl as fl import gsd.hoomd from . import base -from ..lib.picklable_file_io import gsd_pickle_open class GSDReader(base.ReaderBase): @@ -79,10 +80,10 @@ def __init__(self, filename, **kwargs): self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) self._read_next_timestep() - def open_trajectory(self) : + def open_trajectory(self): """opens the trajectory file using gsd.hoomd module""" self._frame = -1 - self._file = gsd_pickle_open(self.filename,mode='rb') + self._file = gsd_pickle_open(self.filename, mode='rb') def close(self): """close reader""" @@ -99,7 +100,7 @@ def _reopen(self): self.open_trajectory() def _read_frame(self, frame): - try : + try: myframe = self._file[frame] except IndexError: raise IOError from None @@ -113,13 +114,13 @@ def _read_frame(self, frame): # set frame box dimensions self.ts.dimensions = myframe.configuration.box - for i in range(3,6) : + for i in range(3, 6): self.ts.dimensions[i] = np.arccos(self.ts.dimensions[i]) * 180.0 / np.pi # set particle positions frame_positions = myframe.particles.position n_atoms_now = frame_positions.shape[0] - if n_atoms_now != self.n_atoms : + if n_atoms_now != self.n_atoms: raise ValueError("Frame %d has %d atoms but the initial frame has %d" " atoms. MDAnalysis in unable to deal with variable" " topology!"%(frame, n_atoms_now, self.n_atoms)) @@ -130,3 +131,30 @@ def _read_frame(self, frame): def _read_next_timestep(self) : """read next frame in trajectory""" return self._read_frame(self._frame + 1) + + +class GSDPicklable(gsd.hoomd.HOOMDTrajectory): + def __getstate__(self): + return self.file.name, self.file.mode + + def __setstate__(self, args): + gsdfileobj = fl.open(name=args[0], + mode=args[1], + application='gsd.hoomd ' + gsd.__version__, + schema='hoomd', + schema_version=[1, 3]) + return self.__init__(gsdfileobj) + + +def gsd_pickle_open(name, mode='rb'): + gsd_version = gsd.__version__ + schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] + if mode not in {'r', 'rt', 'rb'}: + raise ValueError("Only read mode ('r', 'rt', 'rb') \ + files can be pickled.") + gsdfileobj = fl.open(name=name, + mode=mode, + application='gsd.hoomd ' + gsd_version, + schema='hoomd', + schema_version=schema_version) + return GSDPicklable(gsdfileobj) diff --git a/package/MDAnalysis/coordinates/TRJ.py b/package/MDAnalysis/coordinates/TRJ.py index e1affbbddc5..3ede9f21d3d 100644 --- a/package/MDAnalysis/coordinates/TRJ.py +++ b/package/MDAnalysis/coordinates/TRJ.py @@ -158,7 +158,6 @@ import MDAnalysis from . import base from ..lib import util -from ..lib.picklable_file_io import ncdf_pickle_open logger = logging.getLogger("MDAnalysis.coordinates.AMBER") @@ -1075,3 +1074,16 @@ def close(self): if self.trjfile is not None: self.trjfile.close() self.trjfile = None + + +class NCDFPicklable(scipy.io.netcdf.netcdf_file): + def __getstate__(self): + return self.filename, self.use_mmap + + def __setstate__(self, args): + self.__init__(args[0], mmap=args[1]) + + +def ncdf_pickle_open(name, mmap=None): + return NCDFPicklable(name, mmap=mmap) + diff --git a/testsuite/MDAnalysisTests/data/example_longer.gsd b/testsuite/MDAnalysisTests/data/example_longer.gsd index 2232af124f121e414a85fadd4346887a4068bda0..cccad7cbcc8be7cd0b136b363c1598f5d4bcf92e 100644 GIT binary patch delta 2343 zcmZ{mOK1~89L6W_rteg@FN^L$u&0<7d>msD6oh&bdQeC;!B*6Srk8?{#zU;oLbg>9 z4^q!w3N5wAdW*M;mwFR=u!58xqX;_R%znF*6$6vqncx5U?LUv^i|O_Kxw?I3QRr7h zgyc;~+%M=?4 zywR_3ZNooe_>)(%%i0hLqayK7fZR8Igw!#kKnB=t>QceulCyAL?RH%>_6eDR=W!jj zB1R1ZC_n>Vvj&F(Jf{LzT}}naS>ScU9}2YciV!mezd|U`Y9PeYuI6}$0zrQGDXMw_ zHx=*-IM9y;R&COsg{wF+zi8alvf}^}(ZH2R>=_&ik+YCPgF_*5UVpe8d&~&WQ6Tb* zVjb(0-!*U@7?HBhB(KJ1bOC8m&Y+9 zdSYY)ZhldJ+^q~a12muluMCbEAg2Nw28ROVRA9^HRDis~Dcvywpnn>W?|<|QgaSd{ zJlo@S2nB+C>SC|Q4FxKK33jfflmQ&^G2wR^JG{Krr#KZNr$SkSLm_hdPZ=BvX)g7r zM7Lc7O2hyxFlPjSLP5URbHXbS3I+N41I;-jisSh+fp~IK1%Lr03I@onSc+32cFyb9 zpZZH*cbxP-Jyf7+a3}yjHGfp#w`)KJ7}zWDm!AL%1i5`{Koy9DP$0;&8=6xA$@Tm* z!TN_m6@Uux=ZJyC=OHfvo-sK)95gsOBxi#oE@y)YH^27?Y$E_VWB?YJO%Hnopu-@~ zJ=dHa2KnymQ!NdO&4a|B#A>R~nMik(rsw8o?>#J(&rdql3qAgJexXz-%l`|#tw*zY zr<&>Z&+ZoU3r=->Z8A0FOq?@7saT#}dy^VpO?5}h^M#UgCB3`)lu>IvP8kE%* delta 2024 zcmb7_KWGzC9LHb&r#5Mm_%9UEbPx+d>eWdc?BJk_6eq#O*u*AKduMV&I~_Wl-E7r@ zqk@BjLZGM}1qTBv8ROkTvrLZ@DB+V;(QWu|IXXV&jn>&{c}Ad5&M#0XF1H_wGA8$2cwA&;x8 zBZ|{JE>a=y9pcN15uY&P!M$0Bg|uN(Wd2ET?E^2-)+wVvBzXIoOC_F`9EGP(K$P^Ht`oSCyMfvLQEXibQJ_GbiADn^R+`eFr2EpY#*k5GxT0k8_fQ@pB z)0|E@I65pC@f029rCxEeHx1-JQ|kg4XGjgX0zrj1K7xfQJQs z&js-~1AL-nF15c{yF3;MU=ak^;JLxcA#gbKLUA|*jsm(yJoy9eL9qQ&7eE$40KS_Y z4;>(fqI_4tEqshu|o%Z^ToFQNHKv0;oae z(tji$lp&M2;#{(dJlxpKPajnqO>mrY{M7?XEbDPGsJy8w=O%`pUy|rHSJ@#<4psw`DJhd3)(bYvFdg zMziUT+i*7jl-$H!%eP{_75A-#ZzX*z Date: Mon, 29 Jun 2020 11:49:31 +0200 Subject: [PATCH 49/98] add chainreader state --- package/MDAnalysis/coordinates/base.py | 2 +- package/MDAnalysis/coordinates/chain.py | 3 +++ package/MDAnalysis/lib/picklable_file_io.py | 27 ------------------- .../parallelism/test_multiprocessing.py | 8 ++++-- .../MDAnalysisTests/utils/test_pickleio.py | 16 +++++++---- 5 files changed, 21 insertions(+), 35 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index d2230939439..ec83cd04ded 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -2115,7 +2115,7 @@ def __init__(self, filename, convert_units=True, **kwargs): else: ts_kwargs[att] = val - self. _ts_kwargs = ts_kwargs + self._ts_kwargs = ts_kwargs def copy(self): """Return independent copy of this Reader. diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index 0778172bc05..5cbe4be344f 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -422,9 +422,12 @@ def __getstate__(self): state.pop('_ChainReader__chained_trajectories_iter', None) return state + def __setstate__(self, state): self.__dict__.update(state) self.__chained_trajectories_iter = self._chained_iterator() + self[state['_ChainReader__active_reader_index']] + # methods that can change with the current reader def convert_time_from_native(self, t): diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 622516c6108..d1fd1748971 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -47,8 +47,6 @@ import bz2 import gzip -from gsd import fl -import gsd.hoomd import scipy.io @@ -211,19 +209,6 @@ def __setstate__(self, args): self.seek(args[1]) -class GSDPicklable(gsd.hoomd.HOOMDTrajectory): - def __getstate__(self): - return self.file.name, self.file.mode - - def __setstate__(self, args): - gsdfileobj = fl.open(name=args[0], - mode=args[1], - application='gsd.hoomd ' + gsd.__version__, - schema='hoomd', - schema_version=[1, 3]) - return self.__init__(gsdfileobj) - - class NCDFPicklable(scipy.io.netcdf.netcdf_file): def __getstate__(self): return self.filename, self.use_mmap @@ -324,17 +309,5 @@ def gzip_pickle_open(name, mode='rt'): return binary_file -def gsd_pickle_open(name, mode='rb'): - if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') \ - files can be pickled.") - gsdfileobj = fl.open(name=name, - mode=mode, - application='gsd.hoomd ' + gsd.__version__, - schema='hoomd', - schema_version=[1, 3]) - return GSDPicklable(gsdfileobj) - - def ncdf_pickle_open(name, mmap=None): return NCDFPicklable(name, mmap=mmap) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 783d2ba7e49..2617311a42b 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -86,6 +86,8 @@ def test_multiprocess_fileio(): (GMS_ASYMOPT,), # .gz (GSD_long,), (NCDF,), + (np.arange(150).reshape(5, 10, 3).astype(np.float64),), + (GRO, [GRO, GRO, GRO, GRO, GRO]), ]) def u(request): if len(request.param) == 1: @@ -131,8 +133,10 @@ def test_multiprocess_COG(u): def test_multiprocess_names(u): - if u.trajectory.__class__ == mda.coordinates.TRJ.NCDFReader: - # NDCFReader contains no information on atom name + if u.trajectory.__class__ in (mda.coordinates.TRJ.NCDFReader, + mda.coordinates.memory.MemoryReader, + mda.coordinates.chain.ChainReader): + # These Readers contain no information on atom name return True ref = [getnames(u, i) for i in range(3)] diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index c4bc63277c9..9466e9d59bf 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -35,7 +35,13 @@ pickle_open, bz2_pickle_open, gzip_pickle_open, - gsd_pickle_open, +) +from MDAnalysis.coordinates.GSD import ( + GSDPicklable, + gsd_pickle_open +) +from MDAnalysis.coordinates.TRJ import ( + NCDFPicklable, ncdf_pickle_open ) from MDAnalysis.tests.datafiles import ( @@ -66,7 +72,7 @@ def test_get_right_open_handler_text(f_text): def test_iopickle_text(f_text): f_text_pickled = pickle.loads(pickle.dumps(f_text)) - assert_equal(f_text.readline(), f_text_pickled.readline()) + assert_equal(f_text.readlines(), f_text_pickled.readlines()) def test_offset_text_to_0(f_text): @@ -93,7 +99,7 @@ def test_get_right_open_handler_byte(f_byte): def test_iopickle_byte(f_byte): file = f_byte[0] f_byte_pickled = pickle.loads(pickle.dumps(file)) - assert_equal(file.readline(), f_byte_pickled.readline()) + assert_equal(file.readlines(), f_byte_pickled.readlines()) def test_offset_byte_to_tell(f_byte): @@ -106,13 +112,13 @@ def test_offset_byte_to_tell(f_byte): def test_context_manager_pickle(): with pickle_open(PDB) as file: file_pickled = pickle.loads(pickle.dumps(file)) - assert_equal(file.readline(), file_pickled.readline()) + assert_equal(file.readlines(), file_pickled.readlines()) def test_fileio_pickle(): raw_io = FileIOPicklable(PDB) raw_io_pickled = pickle.loads(pickle.dumps(raw_io)) - assert_equal(raw_io.readline(), raw_io_pickled.readline()) + assert_equal(raw_io.readlines(), raw_io_pickled.readlines()) @pytest.fixture(params=[ From b324791c58c202c8da5944349c7c8fec6785f943 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 29 Jun 2020 12:33:22 +0200 Subject: [PATCH 50/98] test timestep --- .../parallelism/test_multiprocessing.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 2617311a42b..45bcffde515 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -111,7 +111,7 @@ def getnames(u, ix): return u.atoms[ix].name -def test_trajecotry_next(u): +def test_trajectory_next(u): u.trajectory.next() u_p = pickle.loads(pickle.dumps(u)) u.trajectory.next() @@ -180,7 +180,6 @@ def test_multiprocess_names(u): ]) def ref_reader(request): fmt_name, filename, extras = request.param - r = get_reader_for(filename, format=fmt_name)(filename, **extras) try: yield r @@ -191,7 +190,16 @@ def ref_reader(request): def test_readers_pickle(ref_reader): ps = pickle.dumps(ref_reader) - reanimated = pickle.loads(ps) - assert len(ref_reader) == len(reanimated) + + +def test_timestep_pickle(ref_reader): + try: + ref_reader[2] + except IndexError: + # single frame files + pass + ps = pickle.dumps(ref_reader) + reanimated = pickle.loads(ps) + assert_equal(reanimated.ts, ref_reader.ts) From 49f959d7da6090e67d5fadf673653f90cdfbaa2b Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 29 Jun 2020 18:12:20 +0200 Subject: [PATCH 51/98] doc --- package/MDAnalysis/lib/picklable_file_io.py | 229 ++++++++++++++++++-- 1 file changed, 213 insertions(+), 16 deletions(-) diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index d1fd1748971..4e442ebceb8 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -37,8 +37,18 @@ .. autoclass:: TextIOPicklable :members: +.. autoclass:: BZ2Picklable + :members: + +.. autoclass:: GzipPicklable + :members: + .. autofunction:: pickle_open +.. autofunction:: bz2_pickle_open + +.. autofunction:: gzip_pickle_open + .. versionadded:: 2.0.0 """ @@ -47,7 +57,6 @@ import bz2 import gzip -import scipy.io class FileIOPicklable(io.FileIO): @@ -180,12 +189,56 @@ def __setstate__(self, args): raw_class = args[0] name = args[1] # raw_class is used for further expansion this functionality to - # GZip files, which also requires a text wrapper. + # Gzip files, which also requires a text wrapper. raw = raw_class(name) super().__init__(raw) class BZ2Picklable(bz2.BZ2File): + """File object (read-only) for bzip2 (de)compression that can be pickled. + + This class provides a file-like object (as returned by :func:`bz2.open`, + namely :class:`bz2.BZ2File`) that, unlike standard Python file objects, + can be pickled. Only read mode is supported. + + When the file is pickled, filename and position of the open file handle in + the file are saved. On unpickling, the file is opened by filename, + and the file is seeked to the saved position. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. + + Note + ---- + This class only supprots opening files in binary mode. If you need to open + to open a compressed file in text mode, use the :func:`bz2_pickle_open`. + + Parameters + ---------- + name : str + a filename given a text or byte string. + mode : str + can only be 'r', 'rb' to make pickle work. + + Example + ------- + :: + + >>> file = BZ2Picklable(XYZ_bz2) + >>> file.readline() + >>> file_pickled = pickle.loads(pickle.dumps(file)) + >>> print(file.tell(), file_pickled.tell()) + 5 5 + + See Also + --------- + FileIOPicklable + BufferIOPicklable + TextIOPicklable + GzipPicklable + + + .. versionadded:: 2.0.0 + """ def __init__(self, name, mode='rb'): super().__init__(name, mode) @@ -198,6 +251,50 @@ def __setstate__(self, args): class GzipPicklable(gzip.GzipFile): + """File object (read-only) that can be pickled. + + This class provides a file-like object (as returned by :func:`gzip.open`, + namely :class:`gzip.GzipFile`) that, unlike standard Python file objects, + can be pickled. Only read mode is supported. + + When the file is pickled, filename and position of the open file handle in + the file are saved. On unpickling, the file is opened by filename, + and the file is seeked to the saved position. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. + + Note + ---- + This class only supprots opening files in binary mode. If you need to open + to open a compressed file in text mode, use the :func:`gzip_pickle_open`. + + Parameters + ---------- + name : str + a filename given a text or byte string. + mode : str + can only be 'r', 'rb' to make pickle work. + + Example + ------- + :: + + >>> file = GzipPicklable(MMTF_gz) + >>> file.readline() + >>> file_pickled = pickle.loads(pickle.dumps(file)) + >>> print(file.tell(), file_pickled.tell()) + 1218 1218 + + See Also + --------- + FileIOPicklable + BufferIOPicklable + TextIOPicklable + BZ2Picklable + + + .. versionadded:: 2.0.0 + """ def __init__(self, name, mode='rb'): super().__init__(name, mode) @@ -209,14 +306,6 @@ def __setstate__(self, args): self.seek(args[1]) -class NCDFPicklable(scipy.io.netcdf.netcdf_file): - def __getstate__(self): - return self.filename, self.use_mmap - - def __setstate__(self, args): - self.__init__(args[0], mmap=args[1]) - - def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. @@ -285,7 +374,63 @@ def pickle_open(name, mode='rt'): return TextIOPicklable(raw) -def bz2_pickle_open(name, mode='rt'): +def bz2_pickle_open(name, mode='rb'): + """Open a bzip2-compressed file in binary or text mode + with pickle function implemented. + + This function returns either BZ2Picklable or TextIOPicklable wrapped + BZ2Picklable object given different reading mode. It can be used as a + context manager, and replace the built-in :func:`bz2.open` function + in read mode that only returns an unpicklable file object. + + Note + ---- + Can be only used with read mode. + + Parameters + ---------- + name : str + a filename given a text or byte string. + mode: {'r', 'rt', 'rb'} (optional) + 'r': open for reading in binary mode; + 'rt': read in text mode; + 'rb': read in binary mode; (default) + + Returns + ------- + stream-like object: BZ2Picklable or TextIOPicklable + when mode is 'rt', returns TextIOPicklable; + when mode is 'r' or 'rb', returns BZ2Picklable + + Raises + ------ + ValueError + if `mode` is not one of the allowed read modes + + Examples + ------- + open as context manager:: + + with bz2_pickle_open('filename') as f: + line = f.readline() + + open as function:: + + f = bz2_pickle_open('filename') + line = f.readline() + f.close() + + See Also + -------- + :func:`io.open` + :func:`bz2.open` + :func:`MDAnalysis.lib.util.anyopen` + :func:`MDAnalysis.lib.picklable_file_io.pickle_open` + :func:`MDAnalysis.lib.picklable_file_io.gzip_pickle_open` + + + .. versionadded:: 2.0.0 + """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') \ files can be pickled.") @@ -297,7 +442,63 @@ def bz2_pickle_open(name, mode='rt'): return binary_file -def gzip_pickle_open(name, mode='rt'): +def gzip_pickle_open(name, mode='rb'): + """Open a gzip-compressed file in binary or text mode + with pickle function implemented. + + This function returns either GzipPicklable or TextIOPicklable wrapped + GzipPicklable object given different reading mode. It can be used as a + context manager, and replace the built-in :func:`gzip.open` function + in read mode that only returns an unpicklable file object. + + Note + ---- + Can be only used with read mode. + + Parameters + ---------- + name : str + a filename given a text or byte string. + mode: {'r', 'rt', 'rb'} (optional) + 'r': open for reading in binary mode; + 'rt': read in text mode; + 'rb': read in binary mode; (default) + + Returns + ------- + stream-like object: GzipPicklable or TextIOPicklable + when mode is 'rt', returns TextIOPicklable; + when mode is 'r' or 'rb', returns GzipPicklable + + Raises + ------ + ValueError + if `mode` is not one of the allowed read modes + + Examples + ------- + open as context manager:: + + with gzip_pickle_open('filename') as f: + line = f.readline() + + open as function:: + + f = gzip_pickle_open('filename') + line = f.readline() + f.close() + + See Also + -------- + :func:`io.open` + :func:`gzip.open` + :func:`MDAnalysis.lib.util.anyopen` + :func:`MDAnalysis.lib.picklable_file_io.pickle_open` + :func:`MDAnalysis.lib.picklable_file_io.bz2_pickle_open` + + + .. versionadded:: 2.0.0 + """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') \ files can be pickled.") @@ -307,7 +508,3 @@ def gzip_pickle_open(name, mode='rt'): return TextIOPicklable(binary_file) else: return binary_file - - -def ncdf_pickle_open(name, mmap=None): - return NCDFPicklable(name, mmap=mmap) From 773524d82ca34f9a0bbfeba9de406280a4eae2cd Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 30 Jun 2020 11:31:22 +0200 Subject: [PATCH 52/98] add doc version change --- package/MDAnalysis/coordinates/GSD.py | 122 ++++++++++++++++-- package/MDAnalysis/coordinates/TRJ.py | 59 ++++++++- package/MDAnalysis/coordinates/base.py | 8 +- package/MDAnalysis/coordinates/chain.py | 3 + package/MDAnalysis/core/universe.py | 5 + package/MDAnalysis/lib/picklable_file_io.py | 2 +- package/MDAnalysis/lib/util.py | 14 +- .../MDAnalysisTests/utils/test_pickleio.py | 9 +- 8 files changed, 201 insertions(+), 21 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index 2447c2178ee..d5bea94d9c3 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -44,9 +44,13 @@ .. autoclass:: GSDReader :inherited-members: +.. autoclass:: GSDPicklable + :members: + +.. autofunction:: gsd_pickle_open + """ import numpy as np -import os import gsd import gsd.fl as fl import gsd.hoomd @@ -72,6 +76,11 @@ def __init__(self, filename, **kwargs): .. versionadded:: 0.17.0 + + .. versionchanged:: 2.0.0 + Now use a picklable :class:`gsd.hoomd.HOOMDTrajectory`-- + :class:`GSDPicklable` + """ super(GSDReader, self).__init__(filename, **kwargs) self.filename = filename @@ -124,33 +133,130 @@ def _read_frame(self, frame): raise ValueError("Frame %d has %d atoms but the initial frame has %d" " atoms. MDAnalysis in unable to deal with variable" " topology!"%(frame, n_atoms_now, self.n_atoms)) - else : + else: self.ts.positions = frame_positions return self.ts - def _read_next_timestep(self) : + def _read_next_timestep(self): """read next frame in trajectory""" return self._read_frame(self._frame + 1) class GSDPicklable(gsd.hoomd.HOOMDTrajectory): + """Hoomd GSD file object (read-only) that can be pickled. + + This class provides a file-like object (as by :func:`gsd.hoomd.open`, + namely :class:`gsd.hoodm.HOOMDTrajectory`) that, unlike file objects, + can be pickled. Only read mode is supported. + + When the file is pickled, filename and mode of :class:`gsd.fl.GSDFile` in + the file are saved. On unpickling, the file is opened by filename. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. + + Note + ---- + Open hoomd GSD files with `gsd_pickle_open`. + + Parameters + ---------- + file: :class:`gsd.fl.GSDFile` + File to access. + + Example + ------- + :: + + gsdfileobj = gsd.fl.open(name=filename, + mode='rb', + application='gsd.hoomd '+gsd.__version__, + schema='hoomd', + schema_version=[1, 3] + file = GSDPicklable(gsdfileobj) + file_pickled = pickle.loads(pickle.dumps(file)) + + See Also + --------- + :func:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + :func:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + :func:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + :func:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + :func:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + + + .. versionadded:: 2.0.0 + """ def __getstate__(self): return self.file.name, self.file.mode def __setstate__(self, args): + gsd_version = gsd.__version__ + schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] gsdfileobj = fl.open(name=args[0], mode=args[1], - application='gsd.hoomd ' + gsd.__version__, + application='gsd.hoomd ' + gsd_version, schema='hoomd', - schema_version=[1, 3]) - return self.__init__(gsdfileobj) + schema_version=schema_version) + self.__init__(gsdfileobj) def gsd_pickle_open(name, mode='rb'): + """Open hoomd schema GSD file with pickle function implemented. + + This function returns either GSDPicklable object. It can be used as a + context manager, and replace the built-in :func:`gsd.hoomd.open` function + in read mode that only returns an unpicklable file object. + + Schema version will depend on the version of gsd module. + + Note + ---- + Can be only used with read mode. + + Parameters + ---------- + name : str + a filename given a text or byte string. + mode: {'r', 'rb'} (optional) + 'r', 'rb': open for reading; + + Returns + ------- + stream-like object: GSDPicklable + + Raises + ------ + ValueError + if `mode` is not one of the allowed read modes + + Examples + ------- + open as context manager:: + + with gsd_pickle_open('filename') as f: + line = f.readline() + + open as function:: + + f = gsd_pickle_open('filename') + line = f.readline() + f.close() + + See Also + -------- + :func:`MDAnalysis.lib.util.anyopen` + :func:`MDAnalysis.lib.picklable_file_io.pickle_open` + :func:`MDAnalysis.lib.picklable_file_io.bz2_pickle_open` + :func:`MDAnalysis.lib.picklable_file_io.gzip_pickle_open` + :func:`gsd.hoomd.open` + + + .. versionadded:: 2.0.0 + """ gsd_version = gsd.__version__ schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] - if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') \ + if mode not in {'r', 'rb'}: + raise ValueError("Only read mode ('r', 'rb') \ files can be pickled.") gsdfileobj = fl.open(name=name, mode=mode, diff --git a/package/MDAnalysis/coordinates/TRJ.py b/package/MDAnalysis/coordinates/TRJ.py index 3ede9f21d3d..375f9d53a84 100644 --- a/package/MDAnalysis/coordinates/TRJ.py +++ b/package/MDAnalysis/coordinates/TRJ.py @@ -85,6 +85,8 @@ .. autoclass:: NCDFWriter :members: +.. autoclass:: NCDFPicklable + :members: .. _ascii-trajectories: @@ -449,6 +451,9 @@ class NCDFReader(base.ReaderBase): .. versionchanged:: 1.0.0 Support for reading `degrees` units for `cell_angles` has now been removed (Issue #2327) + .. versionchanged:: 2.0.0 + Now use a picklable :class:`scipy.io.netcdf.netcdf_file`-- + :class:`NCDFPicklable`. """ @@ -468,8 +473,8 @@ def __init__(self, filename, n_atoms=None, mmap=None, **kwargs): super(NCDFReader, self).__init__(filename, **kwargs) - self.trjfile = ncdf_pickle_open(self.filename, - mmap=self._mmap) + self.trjfile = NCDFPicklable(self.filename, + mmap=self._mmap) # AMBER NetCDF files should always have a convention try: @@ -1077,13 +1082,53 @@ def close(self): class NCDFPicklable(scipy.io.netcdf.netcdf_file): + """NetCDF file object (read-only) that can be pickled. + + This class provides a file-like object (as returned by + :class:`scipy.io.netcdf.netcdf_file`) that, + unlike standard Python file objects, + can be pickled. Only read mode is supported. + + When the file is pickled, filename and mmap of the open file handle in + the file are saved. On unpickling, the file is opened by filename, + and the mmap file is loaded. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. + + Parameters + ---------- + filename : str or file-like + a filename given a text or byte string. + mmap : None or bool, optional + Whether to mmap `filename` when reading. True when `filename` + is a file name, False when `filename` is a file-like object. + + Example + ------- + :: + + f = NCDFPicklable(NCDF) + print(f.variables['coordinates'].data) + f.close() + + can also be used as context manager:: + + with NCDFPicklable(NCDF) as f: + print(f.variables['coordinates'].data) + + See Also + --------- + :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + + + .. versionadded:: 2.0.0 + """ def __getstate__(self): return self.filename, self.use_mmap def __setstate__(self, args): self.__init__(args[0], mmap=args[1]) - - -def ncdf_pickle_open(name, mmap=None): - return NCDFPicklable(name, mmap=mmap) - diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index ec83cd04ded..11226e6b0d7 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -226,6 +226,7 @@ class Timestep(object): create a timestep object with space for n_atoms + .. versionchanged:: 0.11.0 Added :meth:`from_timestep` and :meth:`from_coordinates` constructor methods. @@ -233,6 +234,9 @@ class Timestep(object): :attr:`n_atoms` now a read only property. :attr:`frame` now 0-based instead of 1-based. Attributes `status` and `step` removed. + .. versionchanged:: 2.0.0 + Timestep now can be (un)pickled. Weakref for Reader + will be dropped. """ order = 'F' @@ -2070,7 +2074,6 @@ def _apply_transformations(self, ts): return ts - class ReaderBase(ProtoReader): """Base class for trajectory readers that extends :class:`ProtoReader` with a :meth:`__del__` method. @@ -2098,6 +2101,9 @@ class ReaderBase(ProtoReader): Provides kwargs to be passed to :class:`Timestep` .. versionchanged:: 1.0 Removed deprecated flags functionality, use convert_units kwarg instead + .. versionchanged:: 2.0.0 + Now supports (un)pickle. Upon unpickling, + the current timestep is retained. """ def __init__(self, filename, convert_units=True, **kwargs): diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index 5cbe4be344f..cf042610930 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -213,6 +213,9 @@ class ChainReader(base.ProtoReader): added ``continuous`` trajectory option .. versionchanged:: 0.19.0 limit output of __repr__ + .. versionchanged:: 2.0.0 + Now ChainReader can be (un)pickled. Upon unpickling, + current timestep is retained. """ format = 'CHAIN' diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index b7f8e88dbc9..5abcdd6391a 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -314,6 +314,11 @@ class Universe(object): .. versionchanged:: 1.0.0 Universe() now raises an error. Use Universe(None) or :func:`Universe.empty()` instead. Removed instant selectors. + + .. versionchanged:: 2.0.0 + Universe now can be (un)pickled. + Topology, trajectory and anchor_name are reserved upon unpickle. + """ # Py3 TODO # def __init__(self, topology=None, *coordinates, all_coordinates=False, diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 4e442ebceb8..403f3a92664 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -251,7 +251,7 @@ def __setstate__(self, args): class GzipPicklable(gzip.GzipFile): - """File object (read-only) that can be pickled. + """Gzip file object (read-only) that can be pickled. This class provides a file-like object (as returned by :func:`gzip.open`, namely :class:`gzip.GzipFile`) that, unlike standard Python file objects, diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index 5fc2165e42b..53e2bf1cdb6 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -350,9 +350,19 @@ def anyopen(datasource, mode='rt', reset=True): Only returns the ``stream`` and tries to set ``stream.name = filename`` instead of the previous behavior to return a tuple ``(stream, filename)``. + .. versionchanged:: 2.0.0 + New read handlers support pickle functionality + if `datasource` is a filename. + They return a custom picklable file stream in + :class:`MDAnalysis.lib.picklable_file_io`. + """ - read_handlers = {'bz2': bz2_pickle_open, 'gz': gzip_pickle_open, '': pickle_open} - write_handlers = {'bz2': bz2.open, 'gz': gzip.open, '': open} + read_handlers = {'bz2': bz2_pickle_open, + 'gz': gzip_pickle_open, + '': pickle_open} + write_handlers = {'bz2': bz2.open, + 'gz': gzip.open, + '': open} if mode.startswith('r'): if isstream(datasource): diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 9466e9d59bf..fa3cb45d6bb 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -42,7 +42,6 @@ ) from MDAnalysis.coordinates.TRJ import ( NCDFPicklable, - ncdf_pickle_open ) from MDAnalysis.tests.datafiles import ( PDB, @@ -148,7 +147,13 @@ def test_GSD_pickle(): def test_NCDF_pickle(): - ncdf_io = ncdf_pickle_open(NCDF, mmap=None) + ncdf_io = NCDFPicklable(NCDF, mmap=None) ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) assert_equal(ncdf_io.variables['coordinates'][0], ncdf_io_pickled.variables['coordinates'][0]) + + +def test_NCDF_mmap_pickle(): + ncdf_io = NCDFPicklable(NCDF, mmap=False) + ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) + assert_equal(ncdf_io_pickled.use_mmap, False) From b7e4ef01987f72d9942b39088b306f899b8fa7d2 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 30 Jun 2020 13:47:38 +0200 Subject: [PATCH 53/98] chainreader fix --- package/MDAnalysis/coordinates/base.py | 2 +- package/MDAnalysis/coordinates/chain.py | 15 +++++++++++---- .../parallelism/test_multiprocessing.py | 4 ++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 11226e6b0d7..7437e6929fc 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -2103,7 +2103,7 @@ class ReaderBase(ProtoReader): Removed deprecated flags functionality, use convert_units kwarg instead .. versionchanged:: 2.0.0 Now supports (un)pickle. Upon unpickling, - the current timestep is retained. + the current timestep is retained by reconstrunction. """ def __init__(self, filename, convert_units=True, **kwargs): diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index cf042610930..896b7796a6f 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -422,15 +422,22 @@ def _get_local_frame(self, k): def __getstate__(self): state = self.__dict__.copy() + state['ts'] = self.ts.__deepcopy__() + index = self.ts.frame state.pop('_ChainReader__chained_trajectories_iter', None) - return state + for reader in state['readers'][:self.__active_reader_index + 1]: + reader.rewind() + self.ts = state['ts'] + return state, index - def __setstate__(self, state): + def __setstate__(self, args): + state = args[0] + index = args[1] self.__dict__.update(state) self.__chained_trajectories_iter = self._chained_iterator() - self[state['_ChainReader__active_reader_index']] - + for i in range(index + 1): + self._read_next_timestep() # methods that can change with the current reader def convert_time_from_native(self, t): diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 45bcffde515..31599ef1aec 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -88,6 +88,8 @@ def test_multiprocess_fileio(): (NCDF,), (np.arange(150).reshape(5, 10, 3).astype(np.float64),), (GRO, [GRO, GRO, GRO, GRO, GRO]), + (PDB, [PDB, PDB, PDB, PDB, PDB]), + (GRO, [XTC, XTC]), ]) def u(request): if len(request.param) == 1: @@ -177,6 +179,8 @@ def test_multiprocess_names(u): ('TXYZ', TXYZ, dict()), ('memory', np.arange(60).reshape(2, 10, 3).astype(np.float64), dict()), ('CHAIN', [GRO, GRO, GRO], dict()), + ('CHAIN', [PDB, PDB, PDB], dict()), + ('CHAIN', [XTC, XTC, XTC], dict()), ]) def ref_reader(request): fmt_name, filename, extras = request.param From 9d376b7d88c250670bb68fcf7576d6debc29e904 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 30 Jun 2020 14:09:40 +0200 Subject: [PATCH 54/98] docstring error --- package/MDAnalysis/coordinates/GSD.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index d5bea94d9c3..fa0acb7052c 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -76,10 +76,9 @@ def __init__(self, filename, **kwargs): .. versionadded:: 0.17.0 - .. versionchanged:: 2.0.0 - Now use a picklable :class:`gsd.hoomd.HOOMDTrajectory`-- - :class:`GSDPicklable` + Now use a picklable :class:`gsd.hoomd.HOOMDTrajectory`-- + :class:`GSDPicklable` """ super(GSDReader, self).__init__(filename, **kwargs) From 11cceb4d2a7b6f780df86297976b86b006d3cada Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 1 Jul 2020 22:06:05 +0200 Subject: [PATCH 55/98] check dt before pickle --- package/MDAnalysis/coordinates/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 7437e6929fc..1ed8c4b46d2 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -385,15 +385,19 @@ def from_coordinates(cls, return ts + def __getstate__(self): + self.dt state = self.__dict__.copy() state.pop('_reader', None) return state + def __setstate__(self, state): self.__dict__.update(state) + def _init_unitcell(self): """Create custom datastructure for :attr:`_unitcell`.""" # override for other Timesteps @@ -2150,8 +2154,6 @@ def __del__(self): self._auxs[aux].close() self.close() - def __getstate__(self): - return self.__dict__ def __setstate__(self, state): self.__dict__ = state From a3130f51fdb04c6672b2f1c3b32bc5e2aad5cd86 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 3 Jul 2020 09:24:35 +0200 Subject: [PATCH 56/98] add pickle test to base --- testsuite/MDAnalysisTests/coordinates/base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index d874a3d24b6..a386f2164f7 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -21,6 +21,8 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # import itertools +import pickle + import numpy as np import pytest from unittest import TestCase @@ -423,6 +425,14 @@ def test_add_another_transformations_raises_ValueError(self, transformed): with pytest.raises(ValueError): transformed.add_transformations(translate([2,2,2])) + + def test_pickle_reader(self, reader): + reader_p = pickle.loads(pickle.dumps(reader)) + assert_equal(len(reader), len(reader_p)) + assert_equal(reader.ts, reader_p.ts, + "Timestep is changed after pickling") + + class MultiframeReaderTest(BaseReaderTest): def test_last_frame(self, ref, reader): ts = reader[-1] @@ -489,6 +499,11 @@ def test_iter_as_aux_lowf(self, ref, reader): decimal=ref.prec) + def test_pickle_next_ts_reader(self, reader): + reader_p = pickle.loads(pickle.dumps(reader)) + assert_equal(next(reader), next(reader_p), + "Next timestep is changed after pickling") + class BaseWriterTest(object): @staticmethod @pytest.fixture() From df7eb86687e8f1d3004681fee23b3f1a0e09fc45 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 4 Jul 2020 23:24:27 +0200 Subject: [PATCH 57/98] add chemfiles pickle --- package/MDAnalysis/coordinates/chemfiles.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index 9d01f7d6595..b149c06856c 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -132,7 +132,7 @@ def _open(self): if isinstance(self.filename, chemfiles.Trajectory): self._file = self.filename else: - self._file = chemfiles.Trajectory(self.filename, 'r', self._format) + self._file = ChemfilesPicklable(self.filename, 'r', self._format) def close(self): """close reader""" @@ -381,3 +381,10 @@ def _topology_to_chemfiles(self, obj, n_atoms): topology.add_bond(bond.atoms[0].ix, bond.atoms[1].ix) return topology + +class ChemfilesPicklable(chemfiles.Trajectory): + def __getstate__(self): + return self.path, self._Trajectory__mode, self._Trajectory__format + + def __setstate__(self, args): + self.__init__(*args) From 72ba276f3b9d95cfd4de20bc57983a3730d4e6ef Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 4 Jul 2020 23:53:59 +0200 Subject: [PATCH 58/98] doc --- package/MDAnalysis/coordinates/chemfiles.py | 58 +++++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index b149c06856c..05f4749e32d 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -37,6 +37,7 @@ .. autoclass:: ChemfilesWriter +.. autoclass:: ChemfilesPicklable """ import numpy as np from distutils.version import LooseVersion @@ -382,9 +383,56 @@ def _topology_to_chemfiles(self, obj, n_atoms): return topology -class ChemfilesPicklable(chemfiles.Trajectory): - def __getstate__(self): - return self.path, self._Trajectory__mode, self._Trajectory__format +if HAS_CHEMFILES: + class ChemfilesPicklable(chemfiles.Trajectory): + """Chemfiles file object (read-only) that can be pickled. - def __setstate__(self, args): - self.__init__(*args) + This class provides a file-like object (as returned by + :class:`chemfiles.Trajectory`) that, + unlike standard Python file objects, + can be pickled. Only read mode is supported. + + When the file is pickled, path, mode, and format of the open file handle in + the file are saved. On unpickling, the file is opened by path with mode, + and saved format. + This means that for a successful unpickle, the original file still has to + be accessible with its filename. + + Parameters + ---------- + filename : str + a filename given a text or byte string. + mode : 'r' , optional + can only be 'r' for pickling. + format : '', optional + guessed from the file extension if empty. + + Example + ------- + :: + + f = ChemfilesPicklable(XYZ, 'r', '') + print(f.read()) + f.close() + + can also be used as context manager:: + + with ChemfilesPicklable(XYZ) as f: + print(f.read()) + + See Also + --------- + :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + + + .. versionadded:: 2.0.0 + """ + def __getstate__(self): + return self.path, self._Trajectory__mode, self._Trajectory__format + + def __setstate__(self, args): + self.__init__(*args) From aa62ff0e381f76300be515734d15ae55cfb69344 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 5 Jul 2020 15:01:20 +0200 Subject: [PATCH 59/98] doc add note --- package/MDAnalysis/coordinates/GSD.py | 2 ++ package/MDAnalysis/coordinates/chemfiles.py | 13 +++++++++---- package/MDAnalysis/lib/picklable_file_io.py | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index fa0acb7052c..b852d8ce970 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -156,6 +156,8 @@ class GSDPicklable(gsd.hoomd.HOOMDTrajectory): Note ---- Open hoomd GSD files with `gsd_pickle_open`. + After pickling, the current frame is reset. `universe.trajectory[i]` has + to be used to return to its original frame. Parameters ---------- diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index 05f4749e32d..ae844b860c0 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -392,11 +392,16 @@ class ChemfilesPicklable(chemfiles.Trajectory): unlike standard Python file objects, can be pickled. Only read mode is supported. - When the file is pickled, path, mode, and format of the open file handle in - the file are saved. On unpickling, the file is opened by path with mode, + When the file is pickled, path, mode, and format of the open file handle + are saved. On unpickling, the file is opened by path with mode, and saved format. - This means that for a successful unpickle, the original file still has to - be accessible with its filename. + This means that for a successful unpickle, the original file still has + to be accessible with its filename. + + Note + ---- + After pickling, the current frame is reset. `universe.trajectory[i]` has + to be used to return to its original frame. Parameters ---------- diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 403f3a92664..e704b5e3c22 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -154,6 +154,12 @@ class TextIOPicklable(io.TextIOWrapper): This class provides a file-like :class:`io.TextIOWrapper` object that can be pickled. Note that this only works in read mode. + Note + ---- + After pickling, the current position is reset. `universe.trajectory[i]` has + to be used to return to its original frame. + + Parameters ---------- raw : FileIO object From 5a2b28dd031db35e164273fdb1488844090f8413 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 5 Jul 2020 15:09:17 +0200 Subject: [PATCH 60/98] change chain getstate --- package/MDAnalysis/coordinates/chain.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index efe901f3e40..0512f5a15e2 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -419,22 +419,17 @@ def _get_local_frame(self, k): def __getstate__(self): state = self.__dict__.copy() + # save ts temporarily otherwise it will be changed during rewinding. state['ts'] = self.ts.__deepcopy__() - index = self.ts.frame - state.pop('_ChainReader__chained_trajectories_iter', None) for reader in state['readers'][:self.__active_reader_index + 1]: reader.rewind() + # retrieve the current ts self.ts = state['ts'] - return state, index + return state - - def __setstate__(self, args): - state = args[0] - index = args[1] + def __setstate__(self, state): self.__dict__.update(state) - self.__chained_trajectories_iter = self._chained_iterator() - for i in range(index + 1): - self._read_next_timestep() + self.ts.frame = self.__current_frame # methods that can change with the current reader def convert_time_from_native(self, t): From b5f527034bacba63ab5159c4a4d80b6b9636e552 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 5 Jul 2020 15:17:47 +0200 Subject: [PATCH 61/98] add in-line comments --- package/MDAnalysis/coordinates/base.py | 3 +++ testsuite/MDAnalysisTests/coordinates/base.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 1ed8c4b46d2..bceec46409c 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -387,7 +387,10 @@ def from_coordinates(cls, def __getstate__(self): + # save `dt` info so we no longer + # need `_reader` after pickling. self.dt + state = self.__dict__.copy() state.pop('_reader', None) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index a386f2164f7..28875dfcc62 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -498,12 +498,14 @@ def test_iter_as_aux_lowf(self, ref, reader): ref.iter_ts(ref.aux_lowf_frames_with_steps[i]), decimal=ref.prec) - + # To make sure we not only save the current timestep information, + # but also maintain its relative position. def test_pickle_next_ts_reader(self, reader): reader_p = pickle.loads(pickle.dumps(reader)) assert_equal(next(reader), next(reader_p), "Next timestep is changed after pickling") + class BaseWriterTest(object): @staticmethod @pytest.fixture() From e1facfbcb85b49889d95804cd6ef9e503451dfd3 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 5 Jul 2020 15:25:16 +0200 Subject: [PATCH 62/98] pep8 --- package/MDAnalysis/coordinates/TRJ.py | 2 +- package/MDAnalysis/coordinates/base.py | 6 +----- package/MDAnalysis/coordinates/chemfiles.py | 7 ++++--- package/MDAnalysis/core/universe.py | 4 +++- package/MDAnalysis/lib/__init__.py | 6 +++--- testsuite/MDAnalysisTests/coordinates/base.py | 4 +--- testsuite/MDAnalysisTests/core/test_universe.py | 2 +- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/package/MDAnalysis/coordinates/TRJ.py b/package/MDAnalysis/coordinates/TRJ.py index 375f9d53a84..09810736cb1 100644 --- a/package/MDAnalysis/coordinates/TRJ.py +++ b/package/MDAnalysis/coordinates/TRJ.py @@ -474,7 +474,7 @@ def __init__(self, filename, n_atoms=None, mmap=None, **kwargs): super(NCDFReader, self).__init__(filename, **kwargs) self.trjfile = NCDFPicklable(self.filename, - mmap=self._mmap) + mmap=self._mmap) # AMBER NetCDF files should always have a convention try: diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index bceec46409c..ce291abd7ca 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -304,7 +304,6 @@ def __init__(self, n_atoms, **kwargs): # set up aux namespace for adding auxiliary data self.aux = Namespace() - @classmethod def from_timestep(cls, other, **kwargs): """Create a copy of another Timestep, in the format of this Timestep @@ -385,7 +384,6 @@ def from_coordinates(cls, return ts - def __getstate__(self): # save `dt` info so we no longer # need `_reader` after pickling. @@ -396,11 +394,9 @@ def __getstate__(self): return state - def __setstate__(self, state): self.__dict__.update(state) - def _init_unitcell(self): """Create custom datastructure for :attr:`_unitcell`.""" # override for other Timesteps @@ -462,7 +458,7 @@ def __getitem__(self, atoms): return self._pos[atoms] else: raise TypeError - + def __getattr__(self, attr): # special-case timestep info if attr in ('velocities', 'forces', 'positions'): diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index ae844b860c0..2ffc0051aae 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -383,6 +383,7 @@ def _topology_to_chemfiles(self, obj, n_atoms): return topology + if HAS_CHEMFILES: class ChemfilesPicklable(chemfiles.Trajectory): """Chemfiles file object (read-only) that can be pickled. @@ -392,7 +393,7 @@ class ChemfilesPicklable(chemfiles.Trajectory): unlike standard Python file objects, can be pickled. Only read mode is supported. - When the file is pickled, path, mode, and format of the open file handle + When the file is pickled, path, mode, and format of the file handle are saved. On unpickling, the file is opened by path with mode, and saved format. This means that for a successful unpickle, the original file still has @@ -400,7 +401,7 @@ class ChemfilesPicklable(chemfiles.Trajectory): Note ---- - After pickling, the current frame is reset. `universe.trajectory[i]` has + Upon pickling, the current frame is reset. `universe.trajectory[i]` has to be used to return to its original frame. Parameters @@ -440,4 +441,4 @@ def __getstate__(self): return self.path, self._Trajectory__mode, self._Trajectory__format def __setstate__(self, args): - self.__init__(*args) + self.__init__(*args) diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 5abcdd6391a..67f6bef0e34 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -757,7 +757,9 @@ def __reduce__(self): # Can't quite use __setstate__/__getstate__ so go via __reduce__ # Universe's two "legs" of topology and traj both serialise themselves # the only other state held in Universe is anchor name? - return (self._unpickle_U, (self._topology, self._trajectory, self.anchor_name)) + return (self._unpickle_U, (self._topology, + self._trajectory, + self.anchor_name)) # Properties @property diff --git a/package/MDAnalysis/lib/__init__.py b/package/MDAnalysis/lib/__init__.py index 689d5de9e61..2ba03b03274 100644 --- a/package/MDAnalysis/lib/__init__.py +++ b/package/MDAnalysis/lib/__init__.py @@ -39,6 +39,6 @@ from . import formats from . import pkdtree from . import nsgrid -from .picklable_file_io import FileIOPicklable, BufferIOPicklable, \ - TextIOPicklable - +from .picklable_file_io import (FileIOPicklable, + BufferIOPicklable, + TextIOPicklable) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 28875dfcc62..525a5108a45 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -419,13 +419,11 @@ def test_transformations_copy(self,ref,transformed): ideal_coords = ref.iter_ts(i).positions + v1 + v2 assert_array_almost_equal(ts.positions, ideal_coords, decimal = ref.prec) - def test_add_another_transformations_raises_ValueError(self, transformed): # After defining the transformations, the workflow cannot be changed with pytest.raises(ValueError): transformed.add_transformations(translate([2,2,2])) - def test_pickle_reader(self, reader): reader_p = pickle.loads(pickle.dumps(reader)) assert_equal(len(reader), len(reader_p)) @@ -503,7 +501,7 @@ def test_iter_as_aux_lowf(self, ref, reader): def test_pickle_next_ts_reader(self, reader): reader_p = pickle.loads(pickle.dumps(reader)) assert_equal(next(reader), next(reader_p), - "Next timestep is changed after pickling") + "Next timestep is changed after pickling") class BaseWriterTest(object): diff --git a/testsuite/MDAnalysisTests/core/test_universe.py b/testsuite/MDAnalysisTests/core/test_universe.py index ddb2a5c9d32..2cf06a06dce 100644 --- a/testsuite/MDAnalysisTests/core/test_universe.py +++ b/testsuite/MDAnalysisTests/core/test_universe.py @@ -346,7 +346,7 @@ def test_load_multiple_args(self): def test_pickle(self): u = mda.Universe(PSF, DCD) - s = pickle.dumps(u, protocol = pickle.HIGHEST_PROTOCOL) + s = pickle.dumps(u, protocol=pickle.HIGHEST_PROTOCOL) new_u = pickle.loads(s) assert_equal(u.atoms.names, new_u.atoms.names) From cba4456e0ef046794a6f3a9866f57bf71689da16 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 6 Jul 2020 14:16:32 +0200 Subject: [PATCH 63/98] add chemfile test --- .../MDAnalysisTests/utils/test_pickleio.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index fa3cb45d6bb..64faaeb3456 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -43,8 +43,17 @@ from MDAnalysis.coordinates.TRJ import ( NCDFPicklable, ) +from MDAnalysis.coordinates.chemfiles import ( + check_chemfiles_version +) +if check_chemfiles_version(): + from MDAnalysis.coordinates.chemfiles import ( + ChemfilesPicklable + ) + from MDAnalysis.tests.datafiles import ( PDB, + XYZ, XYZ_bz2, MMTF_gz, GMS_ASYMOPT, @@ -157,3 +166,12 @@ def test_NCDF_mmap_pickle(): ncdf_io = NCDFPicklable(NCDF, mmap=False) ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) assert_equal(ncdf_io_pickled.use_mmap, False) + + +@pytest.mark.skipif(not check_chemfiles_version(), + reason="Wrong version of chemfiles") +def test_Chemfiles_pickle(): + chemfiles_io = ChemfilesPicklable(XYZ) + chemfiles_io_pickled = pickle.loads(pickle.dumps(chemfiles_io)) + assert_equal(chemfiles_io.read().positions, + chemfiles_io_pickled.read().positions) From 5622b51f15a6fadcd24be8ea566bd10ca90c04d8 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 6 Jul 2020 14:18:11 +0200 Subject: [PATCH 64/98] pep8 --- package/MDAnalysis/coordinates/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index ce291abd7ca..f2225ea3cce 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -2153,7 +2153,6 @@ def __del__(self): self._auxs[aux].close() self.close() - def __setstate__(self, state): self.__dict__ = state self[self.ts.frame] From 46cda48f7302afc5ae83758f4f90046b88778cc4 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 7 Jul 2020 22:01:23 +0200 Subject: [PATCH 65/98] raise error with mode --- package/MDAnalysis/coordinates/chemfiles.py | 11 ++++++++- package/MDAnalysis/lib/picklable_file_io.py | 27 ++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index 2ffc0051aae..187ee3f3945 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -401,6 +401,7 @@ class ChemfilesPicklable(chemfiles.Trajectory): Note ---- + Can only be used with readling ('r') mode. Upon pickling, the current frame is reset. `universe.trajectory[i]` has to be used to return to its original frame. @@ -409,7 +410,7 @@ class ChemfilesPicklable(chemfiles.Trajectory): filename : str a filename given a text or byte string. mode : 'r' , optional - can only be 'r' for pickling. + only 'r' can be used for pickling. format : '', optional guessed from the file extension if empty. @@ -437,6 +438,14 @@ class ChemfilesPicklable(chemfiles.Trajectory): .. versionadded:: 2.0.0 """ + def __init__(self, path, mode="r", format=""): + if mode != 'r': + raise ValueError("Only read mode ('r') " + "files can be pickled.") + super().__init__(path=path, + mode=mode, + format=format) + def __getstate__(self): return self.path, self._Trajectory__mode, self._Trajectory__format diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index e704b5e3c22..1db25c17922 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -72,10 +72,17 @@ class FileIOPicklable(io.FileIO): This means that for a successful unpickle, the original file still has to be accessible with its filename. + Note + ---- + This class only supports reading files in binary mode. If you need to open + to open a file in text mode, use the :func:`pickle_open`. + Parameters ---------- name : str a filename given a text or byte string. + mode : str + only reading ('r') mode works. Example ------- @@ -95,10 +102,14 @@ class FileIOPicklable(io.FileIO): .. versionadded:: 2.0.0 """ - def __init__(self, name): - super().__init__(name, mode='r') + def __init__(self, name, mode='r'): + self._mode = mode + super().__init__(name, mode) def __getstate__(self): + if self._mode != 'r': + raise RuntimeError("Can only pickle files that were opened " + "in read mode, not {}".format(self._mode)) return self.name, self.tell() def __setstate__(self, args): @@ -215,7 +226,7 @@ class BZ2Picklable(bz2.BZ2File): Note ---- - This class only supprots opening files in binary mode. If you need to open + This class only supports reading files in binary mode. If you need to open to open a compressed file in text mode, use the :func:`bz2_pickle_open`. Parameters @@ -246,9 +257,13 @@ class BZ2Picklable(bz2.BZ2File): .. versionadded:: 2.0.0 """ def __init__(self, name, mode='rb'): + self._bz_mode = mode super().__init__(name, mode) def __getstate__(self): + if not self._bz_mode.startswith('r'): + raise RuntimeError("Can only pickle files that were opened " + "in read mode, not {}".format(self._bz_mode)) return self._fp.name, self.tell() def __setstate__(self, args): @@ -271,7 +286,7 @@ class GzipPicklable(gzip.GzipFile): Note ---- - This class only supprots opening files in binary mode. If you need to open + This class only supports reading files in binary mode. If you need to open to open a compressed file in text mode, use the :func:`gzip_pickle_open`. Parameters @@ -302,9 +317,13 @@ class GzipPicklable(gzip.GzipFile): .. versionadded:: 2.0.0 """ def __init__(self, name, mode='rb'): + self._gz_mode = mode super().__init__(name, mode) def __getstate__(self): + if not self._gz_mode.startswith('r'): + raise RuntimeError("Can only pickle files that were opened " + "in read mode, not {}".format(self._gz_mode)) return self.name, self.tell() def __setstate__(self, args): From 5e2ee7992134f38456c2eb788585b10100652295 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 10:53:17 +0200 Subject: [PATCH 66/98] change to read_step --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 64faaeb3456..b4f34308a72 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -173,5 +173,5 @@ def test_NCDF_mmap_pickle(): def test_Chemfiles_pickle(): chemfiles_io = ChemfilesPicklable(XYZ) chemfiles_io_pickled = pickle.loads(pickle.dumps(chemfiles_io)) - assert_equal(chemfiles_io.read().positions, - chemfiles_io_pickled.read().positions) + assert_equal(chemfiles_io.read_step(0).positions, + chemfiles_io_pickled.read_step(0).positions) From b23b2fbf2ceb450106f0bb62a73fd8670ede9df6 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 11:55:50 +0200 Subject: [PATCH 67/98] change to almost_equal --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index b4f34308a72..a33cdde55bc 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -23,7 +23,7 @@ import pickle import pytest -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_almost_equal from MDAnalysis.lib.util import anyopen from MDAnalysis.lib.picklable_file_io import ( @@ -173,5 +173,6 @@ def test_NCDF_mmap_pickle(): def test_Chemfiles_pickle(): chemfiles_io = ChemfilesPicklable(XYZ) chemfiles_io_pickled = pickle.loads(pickle.dumps(chemfiles_io)) - assert_equal(chemfiles_io.read_step(0).positions, - chemfiles_io_pickled.read_step(0).positions) + assert_almost_equal(chemfiles_io.read().positions[:], + chemfiles_io_pickled.read().positions[:], + decimal=4) From cd030588e7cb514db8d4fa6daec4b34dc81022b8 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 13:34:16 +0200 Subject: [PATCH 68/98] save frame --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index a33cdde55bc..1fd9b23acef 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -23,7 +23,7 @@ import pickle import pytest -from numpy.testing import assert_equal, assert_almost_equal +from numpy.testing import assert_equal from MDAnalysis.lib.util import anyopen from MDAnalysis.lib.picklable_file_io import ( @@ -173,6 +173,9 @@ def test_NCDF_mmap_pickle(): def test_Chemfiles_pickle(): chemfiles_io = ChemfilesPicklable(XYZ) chemfiles_io_pickled = pickle.loads(pickle.dumps(chemfiles_io)) - assert_almost_equal(chemfiles_io.read().positions[:], - chemfiles_io_pickled.read().positions[:], - decimal=4) + # frame has to be first saved to get the right position value. + # As opposed to `chemfiles_io.read().positions) + frame = chemfiles_io.read() + frame_pickled = chemfiles_io_pickled.read() + assert_equal(frame.positions[:], + frame_pickled.positions[:]) From 3ce8ba73bada74186e8e13def3acda83a6e313f7 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 13:35:43 +0200 Subject: [PATCH 69/98] save frame pep --- testsuite/MDAnalysisTests/utils/test_pickleio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 1fd9b23acef..1d7003ce952 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -178,4 +178,4 @@ def test_Chemfiles_pickle(): frame = chemfiles_io.read() frame_pickled = chemfiles_io_pickled.read() assert_equal(frame.positions[:], - frame_pickled.positions[:]) + frame_pickled.positions[:]) From a5da2f7ec43c5371863f4896f367075500d8ea9d Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 19:00:11 +0200 Subject: [PATCH 70/98] add doc for pickle --- .../documentation_pages/pickle_universe.rst | 39 +++++++++++++++++++ package/doc/sphinx/source/index.rst | 1 + 2 files changed, 40 insertions(+) create mode 100644 package/doc/sphinx/source/documentation_pages/pickle_universe.rst diff --git a/package/doc/sphinx/source/documentation_pages/pickle_universe.rst b/package/doc/sphinx/source/documentation_pages/pickle_universe.rst new file mode 100644 index 00000000000..7695ef7f85e --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/pickle_universe.rst @@ -0,0 +1,39 @@ +.. Contains the formatted docstrings for the serialization of universe located +.. mainly in 'MDAnalysis/libs/pickle_file_io.py' +.. _serialization: + +********************************************************* +Serialization of Universes +********************************************************* + +.. module:: MDAnalysis.libs.picklable_file_io + + +.. code-block:: python + + u = MDAnalysis.Universe(topology, trajectory) + u.trajectory[5] + u_pickled = pickle.loads(pickle.dumps(u) + u_pickled.ts.frame == u.trajectory.ts.frame + +.. _how_to_serialize_a_new_reader: + +How to serialize a new reader +----------------------------- + +TODO + + +.. _implemented-fileio: + +Currently implemented picklable FileIO Formats +---------------------------------------------- + + :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + :class:`MDAnalysis.coordinates.GSD.GSDPicklable` + :class:`MDAnalysis.coordinates.TRJ.NCDFPicklable` + :class:`MDAnalysis.coordinates.chemfiles.ChemfilesPicklable` diff --git a/package/doc/sphinx/source/index.rst b/package/doc/sphinx/source/index.rst index 8b18f687ef8..728dd459fc3 100644 --- a/package/doc/sphinx/source/index.rst +++ b/package/doc/sphinx/source/index.rst @@ -177,6 +177,7 @@ Thank you! ./documentation_pages/core_modules ./documentation_pages/visualization_modules ./documentation_pages/lib_modules + ./documentation_pages/pickle_universe ./documentation_pages/version ./documentation_pages/units ./documentation_pages/exceptions From 5a9ad4d27d9cbc2d60f5829c0758261d87974970 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 8 Jul 2020 22:26:14 +0200 Subject: [PATCH 71/98] timestep pickle doc --- package/MDAnalysis/coordinates/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index f2225ea3cce..bc995ee63ae 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -385,8 +385,11 @@ def from_coordinates(cls, return ts def __getstate__(self): - # save `dt` info so we no longer - # need `_reader` after pickling. + # The `dt` property is lazy loaded. + # We need to load it once from the `_reader` (if exists) + # attached to this timestep to get the dt value. + # This is helpful to (un)pickle a `Timestep` without pickling `_reader` + # and retain its dt value. self.dt state = self.__dict__.copy() From bc60aa739ee53dfac67e5d723f80b9bf4e943544 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 9 Jul 2020 14:09:22 +0200 Subject: [PATCH 72/98] doc serialize --- .../documentation_pages/pickle_universe.rst | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/package/doc/sphinx/source/documentation_pages/pickle_universe.rst b/package/doc/sphinx/source/documentation_pages/pickle_universe.rst index 7695ef7f85e..d6a579171fe 100644 --- a/package/doc/sphinx/source/documentation_pages/pickle_universe.rst +++ b/package/doc/sphinx/source/documentation_pages/pickle_universe.rst @@ -8,6 +8,29 @@ Serialization of Universes .. module:: MDAnalysis.libs.picklable_file_io +As we approach the exascale barrier, researchers are handling increasingly +large volumes of molecular dynamics (MD) data. Whilst MDAnalysis is a flexible +and relatively fast framework for complex analysis tasks in MD simulations, +implementing a parallel computing framework would play a pivotal role in +accelerating the time to solution for such large datasets. + +To achieve a flawless implementation of parallelism, this document illustrates +the basic idea of how current :class:`Universe` is being serialized. And what people +should do to serialize a new reader. + +:class:`Universe` is serialized by serializing each necessary compartment--- +:attr:`_topology`, :attr:`_trajectory`, :attr:`anchor_name`; a new :class:`Universe` is +reconstructed with these elements. + +To make sure every Trajectory reader can be successfully +serialized, we implement picklable I/O classes (see :ref:`implemented-fileio`). +When the file is pickled, filename and other necessary attributes of the open +file handle are saved. On unpickling, the file is opened by filename. +This means that for a successful unpickle, the original file still has to +be accessible with its filename. To retain the current frame of the trajectory, +:func:`_read_frame(previous frame)` will be called during unpickling. + +Here is an example of how a :class:`Universe` is (un)pickled: .. code-block:: python @@ -16,13 +39,63 @@ Serialization of Universes u_pickled = pickle.loads(pickle.dumps(u) u_pickled.ts.frame == u.trajectory.ts.frame +.. _simple_parallel_analysis: + +Simple analysis case +-------------------- + +.. code-block:: python + def cog(u, ag, frame_id): + u.trajectory[frame_id] + return ag.center_of_geometry() + + u = MDAnalysis.Universe(GRO, XTC) + ag = u.atoms[2:5] + + p = multiprocessing.Pool(2) + result = np.array([p.apply(cog, args=(u, ag, i)) + for i in range(u.n_frames)]) + p.close() + +Note to make sure :class:`Atomgroup` finds its anchored :class:`Universe` after pickling, +the :class:`Universe` has to be first pickled. + .. _how_to_serialize_a_new_reader: How to serialize a new reader ----------------------------- +- File Access +If the new reader uses :func:`util.anyopen()` (e.g. PDB), the reading handler +can be pickled without modification. +If the new reader uses I/O classes from other package (e.g. GSD), and cannot +be pickled natively, create a new picklable class inherited from +the file class in that package (e.g. GSDPicklable), adding :func:`__getstate__`, +:func:`__setstate__` functions to allow file handler serialization. + +- To seek or not to seek +Some I/O class supports :func:`seek` and :func:`tell` functions to allow the file +to be pickled with an offset. It is normally not needed for MDAnalysis with +random access. But if error occurs, find a way to make the offset work. + +- Miscellaneous +If pickle still fails due to some unpicklable attributes, try to find a way +to pickle those, or write custom :func:`__get/setstate__` methods for the reader. + +If the new reader is written in Cython, read :class:`lib.formats.libmdaxdr` and +:class:`lib.formats.libdcd` files as references. + +- Tests +If the test for the new reader uses :class:`BaseReaderTest`, whether +the current timestep information is saved, and whether its relative +position is maintained, i.e. next() read the right next timestep, +are already tested. -TODO +If the new reader accesses the file with :func:`util.anyopen`, add necessary +testes inside `parallelism/test_multiprocessing.py` for the reader. +If the new reader accessed the file with new picklable I/O class, +add necessary tests inside `utils/test_pickleio.py` for I/O class, +`parallelism/test_multiprocessing.py` for the reader. .. _implemented-fileio: From 01fc64402a5b04225acd6ab7b2a02b3a7077fe01 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 10 Jul 2020 22:02:31 +0200 Subject: [PATCH 73/98] doc sphinx --- package/MDAnalysis/coordinates/base.py | 2 +- package/MDAnalysis/coordinates/chain.py | 4 ++ .../{ => coordinates}/pickle_universe.rst | 49 ++++++++++--------- .../coordinates_modules.rst | 6 +++ package/doc/sphinx/source/index.rst | 1 - package/setup.cfg | 2 +- 6 files changed, 39 insertions(+), 25 deletions(-) rename package/doc/sphinx/source/documentation_pages/{ => coordinates}/pickle_universe.rst (77%) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index bc995ee63ae..0f26e28d919 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -388,7 +388,7 @@ def __getstate__(self): # The `dt` property is lazy loaded. # We need to load it once from the `_reader` (if exists) # attached to this timestep to get the dt value. - # This is helpful to (un)pickle a `Timestep` without pickling `_reader` + # This will help to (un)pickle a `Timestep` without pickling `_reader` # and retain its dt value. self.dt diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index 0512f5a15e2..ec81f330ae2 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -421,8 +421,12 @@ def __getstate__(self): state = self.__dict__.copy() # save ts temporarily otherwise it will be changed during rewinding. state['ts'] = self.ts.__deepcopy__() + + # the ts.frame of each reader is set to the chained frame during + # iteration, thus we need to rewind the readers that have been used. for reader in state['readers'][:self.__active_reader_index + 1]: reader.rewind() + # retrieve the current ts self.ts = state['ts'] return state diff --git a/package/doc/sphinx/source/documentation_pages/pickle_universe.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst similarity index 77% rename from package/doc/sphinx/source/documentation_pages/pickle_universe.rst rename to package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst index d6a579171fe..79f2fe6b472 100644 --- a/package/doc/sphinx/source/documentation_pages/pickle_universe.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst @@ -6,8 +6,6 @@ Serialization of Universes ********************************************************* -.. module:: MDAnalysis.libs.picklable_file_io - As we approach the exascale barrier, researchers are handling increasingly large volumes of molecular dynamics (MD) data. Whilst MDAnalysis is a flexible and relatively fast framework for complex analysis tasks in MD simulations, @@ -45,6 +43,7 @@ Simple analysis case -------------------- .. code-block:: python + def cog(u, ag, frame_id): u.trajectory[frame_id] return ag.center_of_geometry() @@ -58,13 +57,15 @@ Simple analysis case p.close() Note to make sure :class:`Atomgroup` finds its anchored :class:`Universe` after pickling, -the :class:`Universe` has to be first pickled. +the :class:`Universe` has to be pickled first. .. _how_to_serialize_a_new_reader: How to serialize a new reader ----------------------------- -- File Access + +File Access +^^^^^^^^^^^ If the new reader uses :func:`util.anyopen()` (e.g. PDB), the reading handler can be pickled without modification. If the new reader uses I/O classes from other package (e.g. GSD), and cannot @@ -72,41 +73,45 @@ be pickled natively, create a new picklable class inherited from the file class in that package (e.g. GSDPicklable), adding :func:`__getstate__`, :func:`__setstate__` functions to allow file handler serialization. -- To seek or not to seek +To seek or not to seek +^^^^^^^^^^^^^^^^^^^^^^ Some I/O class supports :func:`seek` and :func:`tell` functions to allow the file to be pickled with an offset. It is normally not needed for MDAnalysis with random access. But if error occurs, find a way to make the offset work. -- Miscellaneous +Miscellaneous +^^^^^^^^^^^^^ If pickle still fails due to some unpicklable attributes, try to find a way -to pickle those, or write custom :func:`__get/setstate__` methods for the reader. +to pickle those, or write custom :func:`__getstate__` and :func:`__setstate__` +methods for the reader. If the new reader is written in Cython, read :class:`lib.formats.libmdaxdr` and :class:`lib.formats.libdcd` files as references. -- Tests +Tests +^^^^^ If the test for the new reader uses :class:`BaseReaderTest`, whether the current timestep information is saved, and whether its relative position is maintained, i.e. next() read the right next timestep, are already tested. If the new reader accesses the file with :func:`util.anyopen`, add necessary -testes inside `parallelism/test_multiprocessing.py` for the reader. +testes inside ``parallelism/test_multiprocessing.py`` for the reader. If the new reader accessed the file with new picklable I/O class, -add necessary tests inside `utils/test_pickleio.py` for I/O class, -`parallelism/test_multiprocessing.py` for the reader. +add necessary tests inside ``utils/test_pickleio.py`` for I/O class, +``parallelism/test_multiprocessing.py`` for the reader. .. _implemented-fileio: -Currently implemented picklable FileIO Formats ----------------------------------------------- - - :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` - :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` - :class:`MDAnalysis.coordinates.GSD.GSDPicklable` - :class:`MDAnalysis.coordinates.TRJ.NCDFPicklable` - :class:`MDAnalysis.coordinates.chemfiles.ChemfilesPicklable` +Currently implemented picklable IO Formats +------------------------------------------ + + * :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + * :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + * :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + * :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + * :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + * :class:`MDAnalysis.coordinates.GSD.GSDPicklable` + * :class:`MDAnalysis.coordinates.TRJ.NCDFPicklable` + * :class:`MDAnalysis.coordinates.chemfiles.ChemfilesPicklable` diff --git a/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst b/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst index c0e7c3f3467..d515d6a0cdf 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst @@ -57,5 +57,11 @@ functionality should first read the :ref:`Trajectory API`. coordinates/base coordinates/core + coordinates/pickle_universe coordinates/chain coordinates/XDR + +In particular, all trajectory readers have to be +:ref:`serializable` and they should pass all tests +available in the ``MDAnalysisTests.coordinates.base.MultiframeReaderTest`` +or ``MDAnalysisTests.coordinates.base.BaseReaderTest``. diff --git a/package/doc/sphinx/source/index.rst b/package/doc/sphinx/source/index.rst index 728dd459fc3..8b18f687ef8 100644 --- a/package/doc/sphinx/source/index.rst +++ b/package/doc/sphinx/source/index.rst @@ -177,7 +177,6 @@ Thank you! ./documentation_pages/core_modules ./documentation_pages/visualization_modules ./documentation_pages/lib_modules - ./documentation_pages/pickle_universe ./documentation_pages/version ./documentation_pages/units ./documentation_pages/exceptions diff --git a/package/setup.cfg b/package/setup.cfg index b033807c3c4..83f81580a92 100644 --- a/package/setup.cfg +++ b/package/setup.cfg @@ -15,4 +15,4 @@ universal = 1 all-files = 1 source-dir = doc/sphinx/source build-dir = doc/html -warning-is-error = 1 +warning-is-error = 0 From 84eb61f38128ff3049c13364bb46a5b2eeb1251b Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 10 Jul 2020 22:17:38 +0200 Subject: [PATCH 74/98] pickle u with getsetstate --- package/MDAnalysis/coordinates/chain.py | 3 ++- package/MDAnalysis/core/universe.py | 27 ++++++++++--------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index ec81f330ae2..f2c4e456cfa 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -422,8 +422,9 @@ def __getstate__(self): # save ts temporarily otherwise it will be changed during rewinding. state['ts'] = self.ts.__deepcopy__() - # the ts.frame of each reader is set to the chained frame during + # the ts.frame of each reader is set to the chained frame index during # iteration, thus we need to rewind the readers that have been used. + # PR #2723 for reader in state['readers'][:self.__active_reader_index + 1]: reader.rewind() diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 67f6bef0e34..26f5dfb15a9 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -742,24 +742,19 @@ def __repr__(self): return "".format( n_atoms=len(self.atoms)) - @classmethod - def _unpickle_U(cls, top, traj, anchor): - """Special method used by __reduce__ to deserialise a Universe""" - # top is a Topology object at this point, but Universe can handle that - u = cls(top) - u.anchor_name = anchor - # maybe this is None, but that's still cool - u.trajectory = traj - - return u - - def __reduce__(self): - # Can't quite use __setstate__/__getstate__ so go via __reduce__ + def __getstate__(self): # Universe's two "legs" of topology and traj both serialise themselves # the only other state held in Universe is anchor name? - return (self._unpickle_U, (self._topology, - self._trajectory, - self.anchor_name)) + return self.anchor_name, self._topology, self._trajectory + + def __setstate__(self, args): + self._anchor_name = args[0] + self.make_anchor() + + self._topology = args[1] + _generate_from_topology(self) + + self._trajectory = args[2] # Properties @property From 9f18ccd321caa5ec02c119ea513578ed5b9375eb Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Fri, 10 Jul 2020 22:20:40 +0200 Subject: [PATCH 75/98] pep --- package/MDAnalysis/coordinates/chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/coordinates/chain.py b/package/MDAnalysis/coordinates/chain.py index f2c4e456cfa..9454397e794 100644 --- a/package/MDAnalysis/coordinates/chain.py +++ b/package/MDAnalysis/coordinates/chain.py @@ -424,7 +424,7 @@ def __getstate__(self): # the ts.frame of each reader is set to the chained frame index during # iteration, thus we need to rewind the readers that have been used. - # PR #2723 + # PR #2723 for reader in state['readers'][:self.__active_reader_index + 1]: reader.rewind() From e37c84a05be270681343ea5d5d3b76c42a40e0f9 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 11 Jul 2020 00:13:50 +0200 Subject: [PATCH 76/98] warning on cfg --- package/setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/setup.cfg b/package/setup.cfg index 83f81580a92..b033807c3c4 100644 --- a/package/setup.cfg +++ b/package/setup.cfg @@ -15,4 +15,4 @@ universal = 1 all-files = 1 source-dir = doc/sphinx/source build-dir = doc/html -warning-is-error = 0 +warning-is-error = 1 From 2d3de9963c66135e85644ae486a364b0d91f5e25 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 13 Jul 2020 12:35:46 +0200 Subject: [PATCH 77/98] sep files --- ...pickle_universe.rst => pickle_readers.rst} | 63 ++++--------------- .../coordinates_modules.rst | 2 +- 2 files changed, 12 insertions(+), 53 deletions(-) rename package/doc/sphinx/source/documentation_pages/coordinates/{pickle_universe.rst => pickle_readers.rst} (57%) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst similarity index 57% rename from package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst rename to package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index 79f2fe6b472..ac90ee87752 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_universe.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -3,22 +3,12 @@ .. _serialization: ********************************************************* -Serialization of Universes +Serialization of Coordinate Readers ********************************************************* -As we approach the exascale barrier, researchers are handling increasingly -large volumes of molecular dynamics (MD) data. Whilst MDAnalysis is a flexible -and relatively fast framework for complex analysis tasks in MD simulations, -implementing a parallel computing framework would play a pivotal role in -accelerating the time to solution for such large datasets. - To achieve a flawless implementation of parallelism, this document illustrates -the basic idea of how current :class:`Universe` is being serialized. And what people -should do to serialize a new reader. - -:class:`Universe` is serialized by serializing each necessary compartment--- -:attr:`_topology`, :attr:`_trajectory`, :attr:`anchor_name`; a new :class:`Universe` is -reconstructed with these elements. +the basic idea of how different coordinate readers are being serialized in MDAnalysis, +and what developers should do to serialize a new reader. To make sure every Trajectory reader can be successfully serialized, we implement picklable I/O classes (see :ref:`implemented-fileio`). @@ -28,37 +18,6 @@ This means that for a successful unpickle, the original file still has to be accessible with its filename. To retain the current frame of the trajectory, :func:`_read_frame(previous frame)` will be called during unpickling. -Here is an example of how a :class:`Universe` is (un)pickled: - -.. code-block:: python - - u = MDAnalysis.Universe(topology, trajectory) - u.trajectory[5] - u_pickled = pickle.loads(pickle.dumps(u) - u_pickled.ts.frame == u.trajectory.ts.frame - -.. _simple_parallel_analysis: - -Simple analysis case --------------------- - -.. code-block:: python - - def cog(u, ag, frame_id): - u.trajectory[frame_id] - return ag.center_of_geometry() - - u = MDAnalysis.Universe(GRO, XTC) - ag = u.atoms[2:5] - - p = multiprocessing.Pool(2) - result = np.array([p.apply(cog, args=(u, ag, i)) - for i in range(u.n_frames)]) - p.close() - -Note to make sure :class:`Atomgroup` finds its anchored :class:`Universe` after pickling, -the :class:`Universe` has to be pickled first. - .. _how_to_serialize_a_new_reader: How to serialize a new reader @@ -107,11 +66,11 @@ add necessary tests inside ``utils/test_pickleio.py`` for I/O class, Currently implemented picklable IO Formats ------------------------------------------ - * :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` - * :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` - * :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` - * :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` - * :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` - * :class:`MDAnalysis.coordinates.GSD.GSDPicklable` - * :class:`MDAnalysis.coordinates.TRJ.NCDFPicklable` - * :class:`MDAnalysis.coordinates.chemfiles.ChemfilesPicklable` +* :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` +* :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` +* :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` +* :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` +* :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` +* :class:`MDAnalysis.coordinates.GSD.GSDPicklable` +* :class:`MDAnalysis.coordinates.TRJ.NCDFPicklable` +* :class:`MDAnalysis.coordinates.chemfiles.ChemfilesPicklable` diff --git a/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst b/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst index d515d6a0cdf..c4f73198ac1 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates_modules.rst @@ -57,7 +57,7 @@ functionality should first read the :ref:`Trajectory API`. coordinates/base coordinates/core - coordinates/pickle_universe + coordinates/pickle_readers coordinates/chain coordinates/XDR From 18d146b84874e644f7e2962751e5182cbe16a71c Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 13 Jul 2020 12:39:44 +0200 Subject: [PATCH 78/98] sep to two files --- .../documentation_pages/coordinates/pickle_readers.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index ac90ee87752..dafd6635d86 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -30,13 +30,17 @@ can be pickled without modification. If the new reader uses I/O classes from other package (e.g. GSD), and cannot be pickled natively, create a new picklable class inherited from the file class in that package (e.g. GSDPicklable), adding :func:`__getstate__`, -:func:`__setstate__` functions to allow file handler serialization. +:func:`__setstate__` functions (or :func:`__reduce__` if needed. Consult the +pickle [documentation](https://docs.python.org/3/library/pickle.html) of python) +to allow file handler serialization. To seek or not to seek ^^^^^^^^^^^^^^^^^^^^^^ Some I/O class supports :func:`seek` and :func:`tell` functions to allow the file to be pickled with an offset. It is normally not needed for MDAnalysis with -random access. But if error occurs, find a way to make the offset work. +random access. But if error occurs during testing, find a way to make the offset work. +Maybe this I/O class supports frame indexing? Maybe the file handler inside this I/O +class supports offset? Miscellaneous ^^^^^^^^^^^^^ From 8679e50a1d792664814f4169ab896f809ca8c5a9 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Tue, 14 Jul 2020 14:13:22 -0700 Subject: [PATCH 79/98] fixed failed merge in CHANGELOG --- package/CHANGELOG | 3 --- 1 file changed, 3 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index ed94e4c87dc..11e749eb8c2 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -71,12 +71,9 @@ Changes * Removes deprecated ProgressMeter (Issue #2739) * Removes deprecated MDAnalysis.units.N_Avogadro (PR #2737) * Dropped Python 2 support -<<<<<<< HEAD * Set Python 3.6 as the minimum supported version (Issue #2541) -======= * Changes the minimal NumPy version to 1.16.0 (Issue #2827, PR #2831) * Sets the minimal RDKit version for CI to 2020.03.1 (Issue #2827, PR #2831) ->>>>>>> mda_origin/develop Deprecations From 0ceffe5371d36f673011d2d27348f9a29521484d Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Tue, 14 Jul 2020 14:14:21 -0700 Subject: [PATCH 80/98] removed superfluous blank lines from CHANGELOG --- package/CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 11e749eb8c2..69445e2dd28 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,11 +13,9 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ - ??/??/?? tylerjereddy, richardjgowers, IAlibay, hmacdope, orbeckst, cbouy, lilyminium, daveminh, jbarnoud, yuxuanzhuang, VOD555 - * 2.0.0 Fixes From 688041c54ebbd8d61b060e1757f05250730f8591 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 16 Jul 2020 11:51:57 +0200 Subject: [PATCH 81/98] xdr dcd seek error --- package/MDAnalysis/lib/formats/libdcd.pyx | 2 +- package/MDAnalysis/lib/formats/libmdaxdr.pyx | 2 +- testsuite/MDAnalysisTests/coordinates/base.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 53719bc8bc2..ed2564b1411 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -260,7 +260,7 @@ cdef class DCDFile: self.close() return - current_frame = state[1] + current_frame = state[1] - 1 self.seek(current_frame) diff --git a/package/MDAnalysis/lib/formats/libmdaxdr.pyx b/package/MDAnalysis/lib/formats/libmdaxdr.pyx index c4a423d7141..c24e7085346 100644 --- a/package/MDAnalysis/lib/formats/libmdaxdr.pyx +++ b/package/MDAnalysis/lib/formats/libmdaxdr.pyx @@ -305,7 +305,7 @@ cdef class _XDRFile: self._has_offsets = state[3] # where was I - current_frame = state[1] + current_frame = state[1] - 1 self.seek(current_frame) def seek(self, frame): diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 525a5108a45..11187c4de37 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -503,6 +503,16 @@ def test_pickle_next_ts_reader(self, reader): assert_equal(next(reader), next(reader_p), "Next timestep is changed after pickling") + # To make sure pickle works for last frame. + def test_pickle_last_ts_reader(self, reader): + # move current ts to last frame. + reader[-1] + reader_p = pickle.loads(pickle.dumps(reader)) + assert_equal(len(reader), len(reader_p), + "Last timestep is changed after pickling") + assert_equal(reader.ts, reader_p.ts, + "Last timestep is changed after pickling") + class BaseWriterTest(object): @staticmethod From f2239bb7620152a473c1f93348ade67e17132b71 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 16 Jul 2020 16:55:33 +0200 Subject: [PATCH 82/98] current frame xdr/dcd --- package/MDAnalysis/lib/formats/libdcd.pyx | 6 +++--- package/MDAnalysis/lib/formats/libmdaxdr.pyx | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index ed2564b1411..2229b02f9bd 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -260,9 +260,9 @@ cdef class DCDFile: self.close() return - current_frame = state[1] - 1 - self.seek(current_frame) - + current_frame = state[1] + self.seek(current_frame - 1) + self.current_frame = current_frame def tell(self): """ diff --git a/package/MDAnalysis/lib/formats/libmdaxdr.pyx b/package/MDAnalysis/lib/formats/libmdaxdr.pyx index c24e7085346..4bcd763a42e 100644 --- a/package/MDAnalysis/lib/formats/libmdaxdr.pyx +++ b/package/MDAnalysis/lib/formats/libmdaxdr.pyx @@ -305,8 +305,9 @@ cdef class _XDRFile: self._has_offsets = state[3] # where was I - current_frame = state[1] - 1 - self.seek(current_frame) + current_frame = state[1] + self.seek(current_frame - 1) + self.current_frame = current_frame def seek(self, frame): """Seek to Frame. From c0d241ed051b80ea34227dde64cf0f55bc3f76fb Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 19 Jul 2020 12:38:17 +0200 Subject: [PATCH 83/98] remove tests not needed --- .../coordinates/pickle_readers.rst | 4 ++ .../parallelism/test_multiprocessing.py | 48 ++++--------------- 2 files changed, 12 insertions(+), 40 deletions(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index dafd6635d86..e9b1697c80f 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -42,6 +42,10 @@ random access. But if error occurs during testing, find a way to make the offset Maybe this I/O class supports frame indexing? Maybe the file handler inside this I/O class supports offset? +For example, in `TRZReader`, `_read_frame` is implemented by `_seek`ing the file into +its previous frame and `_read_next_timestep`, so the offset of the file is crucial +for such machinery to work. + Miscellaneous ^^^^^^^^^^^^^ If pickle still fails due to some unpicklable attributes, try to find a way diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 31599ef1aec..00ad7903c3d 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -28,7 +28,6 @@ from numpy.testing import assert_equal import MDAnalysis as mda -from MDAnalysis.lib.picklable_file_io import pickle_open from MDAnalysis.coordinates.core import get_reader_for from MDAnalysisTests.datafiles import ( @@ -61,21 +60,6 @@ ) -def textio_line(file, i): - return file.readlines()[i] - - -def test_multiprocess_fileio(): - p = multiprocessing.Pool(2) - with pickle_open(PDB) as PDB_file: - ref = PDB_file.readlines()[:4] - with pickle_open(PDB) as PDB_file: - res = np.array([p.apply(textio_line, args=(PDB_file, i)) - for i in range(4)]) - p.close() - assert_equal(res, ref) - - @pytest.fixture(params=[ (PSF, DCD), (GRO, XTC), @@ -108,19 +92,6 @@ def cog(u, ag, frame_id): return ag.center_of_geometry() -def getnames(u, ix): - # Check topology stuff works - return u.atoms[ix].name - - -def test_trajectory_next(u): - u.trajectory.next() - u_p = pickle.loads(pickle.dumps(u)) - u.trajectory.next() - u_p.trajectory.next() - assert_equal(u.atoms.positions[0], u_p.atoms.positions[0]) - - def test_multiprocess_COG(u): ag = u.atoms[2:5] @@ -134,12 +105,13 @@ def test_multiprocess_COG(u): assert_equal(ref, res) -def test_multiprocess_names(u): - if u.trajectory.__class__ in (mda.coordinates.TRJ.NCDFReader, - mda.coordinates.memory.MemoryReader, - mda.coordinates.chain.ChainReader): - # These Readers contain no information on atom name - return True +def getnames(u, ix): + # Check topology stuff works + return u.atoms[ix].name + + +def test_multiprocess_names(): + u = mda.Universe(GRO, XTC) ref = [getnames(u, i) for i in range(3)] @@ -196,14 +168,10 @@ def test_readers_pickle(ref_reader): ps = pickle.dumps(ref_reader) reanimated = pickle.loads(ps) assert len(ref_reader) == len(reanimated) - - -def test_timestep_pickle(ref_reader): try: ref_reader[2] + ref_reader[0] except IndexError: # single frame files pass - ps = pickle.dumps(ref_reader) - reanimated = pickle.loads(ps) assert_equal(reanimated.ts, ref_reader.ts) From 68b1c2a63b289e5bc35fdda251aca22010cefc26 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sun, 19 Jul 2020 12:39:58 +0200 Subject: [PATCH 84/98] pep --- testsuite/MDAnalysisTests/coordinates/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 11187c4de37..9ddff4241a8 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -503,7 +503,7 @@ def test_pickle_next_ts_reader(self, reader): assert_equal(next(reader), next(reader_p), "Next timestep is changed after pickling") - # To make sure pickle works for last frame. + # To make sure pickle works for last frame. def test_pickle_last_ts_reader(self, reader): # move current ts to last frame. reader[-1] From d4574910fee3c0d8acdf8be2245a3287c8551634 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 20 Jul 2020 20:36:12 +0200 Subject: [PATCH 85/98] test title more accurate --- testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 00ad7903c3d..81bc52b4e7d 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -110,7 +110,7 @@ def getnames(u, ix): return u.atoms[ix].name -def test_multiprocess_names(): +def test_universe_unpickle_in_new_process(): u = mda.Universe(GRO, XTC) ref = [getnames(u, i) for i in range(3)] From 0496ca110d8600733f9ac647ad0c12df7c143ec7 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 27 Jul 2020 11:51:57 +0200 Subject: [PATCH 86/98] misc --- package/MDAnalysis/coordinates/GSD.py | 4 ++-- package/MDAnalysis/coordinates/chemfiles.py | 2 +- package/MDAnalysis/lib/picklable_file_io.py | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index b852d8ce970..426d2583664 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -257,8 +257,8 @@ def gsd_pickle_open(name, mode='rb'): gsd_version = gsd.__version__ schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] if mode not in {'r', 'rb'}: - raise ValueError("Only read mode ('r', 'rb') \ - files can be pickled.") + raise ValueError("Only read mode ('r', 'rb') " + "files can be pickled.") gsdfileobj = fl.open(name=name, mode=mode, application='gsd.hoomd ' + gsd_version, diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index 187ee3f3945..98abb79b528 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -401,7 +401,7 @@ class ChemfilesPicklable(chemfiles.Trajectory): Note ---- - Can only be used with readling ('r') mode. + Can only be used with reading ('r') mode. Upon pickling, the current frame is reset. `universe.trajectory[i]` has to be used to return to its original frame. diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 1db25c17922..2fb59544fb3 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -75,7 +75,7 @@ class FileIOPicklable(io.FileIO): Note ---- This class only supports reading files in binary mode. If you need to open - to open a file in text mode, use the :func:`pickle_open`. + a file in text mode, use the :func:`pickle_open`. Parameters ---------- @@ -389,8 +389,8 @@ def pickle_open(name, mode='rt'): .. versionadded:: 2.0.0 """ if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') \ - files can be pickled.") + raise ValueError("Only read mode ('r', 'rt', 'rb') " + "iles can be pickled.") name = os.fspath(name) raw = FileIOPicklable(name) if mode == 'rb': @@ -457,8 +457,8 @@ def bz2_pickle_open(name, mode='rb'): .. versionadded:: 2.0.0 """ if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') \ - files can be pickled.") + raise ValueError("Only read mode ('r', 'rt', 'rb') " + "files can be pickled.") bz_mode = mode.replace("t", "") binary_file = BZ2Picklable(name, bz_mode) if "t" in mode: @@ -525,8 +525,8 @@ def gzip_pickle_open(name, mode='rb'): .. versionadded:: 2.0.0 """ if mode not in {'r', 'rt', 'rb'}: - raise ValueError("Only read mode ('r', 'rt', 'rb') \ - files can be pickled.") + raise ValueError("Only read mode ('r', 'rt', 'rb') " + "files can be pickled.") gz_mode = mode.replace("t", "") binary_file = GzipPicklable(name, gz_mode) if "t" in mode: From df061fc07f6728d2c2d68e37cfcbd565bf327db1 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Tue, 28 Jul 2020 13:47:45 +0200 Subject: [PATCH 87/98] gsd dim --- package/MDAnalysis/coordinates/GSD.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index 426d2583664..a412bbacac3 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -122,8 +122,8 @@ def _read_frame(self, frame): # set frame box dimensions self.ts.dimensions = myframe.configuration.box - for i in range(3, 6): - self.ts.dimensions[i] = np.arccos(self.ts.dimensions[i]) * 180.0 / np.pi + self.ts.dimensions[3:] = np.rad2deg(np.arccos(self.ts.dimensions[3:])) + # set particle positions frame_positions = myframe.particles.position From abe92da659e770137b1dfbf7168337a43b3f0461 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 29 Jul 2020 13:57:06 +0200 Subject: [PATCH 88/98] add test for runtimee pickle --- package/MDAnalysis/coordinates/GSD.py | 10 ++--- package/MDAnalysis/core/universe.py | 1 + .../coordinates/pickle_readers.rst | 26 ++++++----- .../MDAnalysisTests/utils/test_pickleio.py | 45 ++++++++++++++----- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index a412bbacac3..bc010a7920f 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -52,7 +52,7 @@ """ import numpy as np import gsd -import gsd.fl as fl +import gsd.fl import gsd.hoomd from . import base @@ -172,7 +172,7 @@ class GSDPicklable(gsd.hoomd.HOOMDTrajectory): mode='rb', application='gsd.hoomd '+gsd.__version__, schema='hoomd', - schema_version=[1, 3] + schema_version=[1, 3]) file = GSDPicklable(gsdfileobj) file_pickled = pickle.loads(pickle.dumps(file)) @@ -193,7 +193,7 @@ def __getstate__(self): def __setstate__(self, args): gsd_version = gsd.__version__ schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] - gsdfileobj = fl.open(name=args[0], + gsdfileobj = gsd.fl.open(name=args[0], mode=args[1], application='gsd.hoomd ' + gsd_version, schema='hoomd', @@ -204,7 +204,7 @@ def __setstate__(self, args): def gsd_pickle_open(name, mode='rb'): """Open hoomd schema GSD file with pickle function implemented. - This function returns either GSDPicklable object. It can be used as a + This function returns a GSDPicklable object. It can be used as a context manager, and replace the built-in :func:`gsd.hoomd.open` function in read mode that only returns an unpicklable file object. @@ -259,7 +259,7 @@ def gsd_pickle_open(name, mode='rb'): if mode not in {'r', 'rb'}: raise ValueError("Only read mode ('r', 'rb') " "files can be pickled.") - gsdfileobj = fl.open(name=name, + gsdfileobj = gsd.fl.open(name=name, mode=mode, application='gsd.hoomd ' + gsd_version, schema='hoomd', diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index bde468bb082..3276672f2c1 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -311,6 +311,7 @@ class Universe(object): bonds, angles, dihedrals principal ConnectivityGroups for each connectivity type + .. versionchanged:: 1.0.0 Universe() now raises an error. Use Universe(None) or :func:`Universe.empty()` instead. Removed instant selectors. diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index e9b1697c80f..36c0bdbddcf 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -6,7 +6,7 @@ Serialization of Coordinate Readers ********************************************************* -To achieve a flawless implementation of parallelism, this document illustrates +To achieve a working implementation of parallelism, this document illustrates the basic idea of how different coordinate readers are being serialized in MDAnalysis, and what developers should do to serialize a new reader. @@ -25,18 +25,22 @@ How to serialize a new reader File Access ^^^^^^^^^^^ -If the new reader uses :func:`util.anyopen()` (e.g. PDB), the reading handler -can be pickled without modification. -If the new reader uses I/O classes from other package (e.g. GSD), and cannot -be pickled natively, create a new picklable class inherited from -the file class in that package (e.g. GSDPicklable), adding :func:`__getstate__`, +If the new reader uses :func:`util.anyopen()` +(e.g. :class:MDAnalysis.coordinates.PDB.PDBReader), +the reading handler can be pickled without modification. +If the new reader uses I/O classes from other package +(e.g. :class:MDAnalysis.coordinates.GSD.GSDReader)), +and cannot be pickled natively, create a new picklable class inherited from +the file class in that package +(e.g. :class:MDAnalysis.coordinates.GSD.GSDPicklable), +adding :func:`__getstate__`, :func:`__setstate__` functions (or :func:`__reduce__` if needed. Consult the pickle [documentation](https://docs.python.org/3/library/pickle.html) of python) to allow file handler serialization. To seek or not to seek ^^^^^^^^^^^^^^^^^^^^^^ -Some I/O class supports :func:`seek` and :func:`tell` functions to allow the file +Some I/O classes support :func:`seek` and :func:`tell` functions to allow the file to be pickled with an offset. It is normally not needed for MDAnalysis with random access. But if error occurs during testing, find a way to make the offset work. Maybe this I/O class supports frame indexing? Maybe the file handler inside this I/O @@ -59,14 +63,14 @@ Tests ^^^^^ If the test for the new reader uses :class:`BaseReaderTest`, whether the current timestep information is saved, and whether its relative -position is maintained, i.e. next() read the right next timestep, +position is maintained, i.e. next() reads the right next timestep, are already tested. If the new reader accesses the file with :func:`util.anyopen`, add necessary -testes inside ``parallelism/test_multiprocessing.py`` for the reader. +tests inside ``parallelism/test_multiprocessing.py`` for the reader. -If the new reader accessed the file with new picklable I/O class, -add necessary tests inside ``utils/test_pickleio.py`` for I/O class, +If the new reader accessed the file with a new picklable I/O class, +add necessary tests inside ``utils/test_pickleio.py`` for the I/O class, ``parallelism/test_multiprocessing.py`` for the reader. .. _implemented-fileio: diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 1d7003ce952..b005f478c12 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -130,22 +130,29 @@ def test_fileio_pickle(): @pytest.fixture(params=[ - # filename mode - (PDB, 'w', pickle_open), - (PDB, 'x', pickle_open), - (PDB, 'a', pickle_open), - (XYZ_bz2, 'w', bz2_pickle_open), - (MMTF_gz, 'w', gzip_pickle_open) + # filename mode open_func open_class + ('test.pdb', 'w', pickle_open, FileIOPicklable), + ('test.pdb', 'x', pickle_open, FileIOPicklable), + ('test.pdb', 'a', pickle_open, FileIOPicklable), + ('test.bz2', 'w', bz2_pickle_open, BZ2Picklable), + ('test.gz', 'w', gzip_pickle_open, GzipPicklable), ]) def unpicklable_f(request): - filename, mode, open_func = request.param - return filename, mode, open_func + filename, mode, open_func, open_class = request.param + return filename, mode, open_func, open_class + + +def test_unpicklable_open_mode(unpicklable_f, tmpdir): + filename, mode, open_func, open_class = unpicklable_f + with pytest.raises(ValueError, match=r"Only read mode"): + open_func(tmpdir.mkdir("pickle").join(filename), mode) -def test_unpicklable_open_mode(unpicklable_f): - filename, mode, open_func = unpicklable_f - with pytest.raises(ValueError, match=r"Only read mode *"): - open_func(filename, mode) +def test_pickle_with_write_mode(unpicklable_f, tmpdir): + filename, mode, open_func, open_class = unpicklable_f + f_open_by_class = open_class(tmpdir.mkdir("pickle").join(filename), mode) + with pytest.raises(RuntimeError, match=r"Can only pickle"): + f_pickled = pickle.loads(pickle.dumps(f_open_by_class)) def test_GSD_pickle(): @@ -155,6 +162,12 @@ def test_GSD_pickle(): gsd_io_pickled.read_frame(0).particles.position) +def test_GSD_with_write_mode(tmpdir): + with pytest.raises(ValueError, match=r"Only read mode"): + gsd_io = gsd_pickle_open(tmpdir.mkdir("gsd").join('t.gsd'), + mode='w') + + def test_NCDF_pickle(): ncdf_io = NCDFPicklable(NCDF, mmap=None) ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) @@ -179,3 +192,11 @@ def test_Chemfiles_pickle(): frame_pickled = chemfiles_io_pickled.read() assert_equal(frame.positions[:], frame_pickled.positions[:]) + + +@pytest.mark.skipif(not check_chemfiles_version(), + reason="Wrong version of chemfiles") +def test_Chemfiles_with_write_mode(tmpdir): + with pytest.raises(ValueError, match=r"Only read mode"): + chemfiles_io = ChemfilesPicklable(tmpdir.mkdir("xyz").join('t.xyz'), + mode='w') From b3469fe168123950d6ba866c76c6da65a3736970 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 29 Jul 2020 13:57:06 +0200 Subject: [PATCH 89/98] add test for runtimee pickle --- package/MDAnalysis/coordinates/GSD.py | 27 ++++++----- package/MDAnalysis/coordinates/base.py | 14 +++--- package/MDAnalysis/core/universe.py | 1 + .../coordinates/pickle_readers.rst | 26 ++++++----- .../MDAnalysisTests/utils/test_pickleio.py | 45 ++++++++++++++----- 5 files changed, 69 insertions(+), 44 deletions(-) diff --git a/package/MDAnalysis/coordinates/GSD.py b/package/MDAnalysis/coordinates/GSD.py index a412bbacac3..c2bdbe7a523 100644 --- a/package/MDAnalysis/coordinates/GSD.py +++ b/package/MDAnalysis/coordinates/GSD.py @@ -52,7 +52,7 @@ """ import numpy as np import gsd -import gsd.fl as fl +import gsd.fl import gsd.hoomd from . import base @@ -124,7 +124,6 @@ def _read_frame(self, frame): self.ts.dimensions = myframe.configuration.box self.ts.dimensions[3:] = np.rad2deg(np.arccos(self.ts.dimensions[3:])) - # set particle positions frame_positions = myframe.particles.position n_atoms_now = frame_positions.shape[0] @@ -172,7 +171,7 @@ class GSDPicklable(gsd.hoomd.HOOMDTrajectory): mode='rb', application='gsd.hoomd '+gsd.__version__, schema='hoomd', - schema_version=[1, 3] + schema_version=[1, 3]) file = GSDPicklable(gsdfileobj) file_pickled = pickle.loads(pickle.dumps(file)) @@ -193,18 +192,18 @@ def __getstate__(self): def __setstate__(self, args): gsd_version = gsd.__version__ schema_version = [1, 4] if gsd_version >= '1.9.0' else [1, 3] - gsdfileobj = fl.open(name=args[0], - mode=args[1], - application='gsd.hoomd ' + gsd_version, - schema='hoomd', - schema_version=schema_version) + gsdfileobj = gsd.fl.open(name=args[0], + mode=args[1], + application='gsd.hoomd ' + gsd_version, + schema='hoomd', + schema_version=schema_version) self.__init__(gsdfileobj) def gsd_pickle_open(name, mode='rb'): """Open hoomd schema GSD file with pickle function implemented. - This function returns either GSDPicklable object. It can be used as a + This function returns a GSDPicklable object. It can be used as a context manager, and replace the built-in :func:`gsd.hoomd.open` function in read mode that only returns an unpicklable file object. @@ -259,9 +258,9 @@ def gsd_pickle_open(name, mode='rb'): if mode not in {'r', 'rb'}: raise ValueError("Only read mode ('r', 'rb') " "files can be pickled.") - gsdfileobj = fl.open(name=name, - mode=mode, - application='gsd.hoomd ' + gsd_version, - schema='hoomd', - schema_version=schema_version) + gsdfileobj = gsd.fl.open(name=name, + mode=mode, + application='gsd.hoomd ' + gsd_version, + schema='hoomd', + schema_version=schema_version) return GSDPicklable(gsdfileobj) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 64733301ac4..30cf1e89f03 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -1419,6 +1419,9 @@ class ProtoReader(IOBase, metaclass=_Readermeta): .. versionchanged:: 0.11.0 Frames now 0-based instead of 1-based + .. versionchanged:: 2.0.0 + Now supports (un)pickle. Upon unpickling, + the current timestep is retained by reconstrunction. """ #: The appropriate Timestep class, e.g. @@ -2079,6 +2082,10 @@ def _apply_transformations(self, ts): return ts + def __setstate__(self, state): + self.__dict__ = state + self[self.ts.frame] + class ReaderBase(ProtoReader): """Base class for trajectory readers that extends :class:`ProtoReader` with a @@ -2107,9 +2114,6 @@ class ReaderBase(ProtoReader): Provides kwargs to be passed to :class:`Timestep` .. versionchanged:: 1.0 Removed deprecated flags functionality, use convert_units kwarg instead - .. versionchanged:: 2.0.0 - Now supports (un)pickle. Upon unpickling, - the current timestep is retained by reconstrunction. """ def __init__(self, filename, convert_units=True, **kwargs): @@ -2156,10 +2160,6 @@ def __del__(self): self._auxs[aux].close() self.close() - def __setstate__(self, state): - self.__dict__ = state - self[self.ts.frame] - class _Writermeta(type): # Auto register this format upon class creation diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index bde468bb082..3276672f2c1 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -311,6 +311,7 @@ class Universe(object): bonds, angles, dihedrals principal ConnectivityGroups for each connectivity type + .. versionchanged:: 1.0.0 Universe() now raises an error. Use Universe(None) or :func:`Universe.empty()` instead. Removed instant selectors. diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index e9b1697c80f..36c0bdbddcf 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -6,7 +6,7 @@ Serialization of Coordinate Readers ********************************************************* -To achieve a flawless implementation of parallelism, this document illustrates +To achieve a working implementation of parallelism, this document illustrates the basic idea of how different coordinate readers are being serialized in MDAnalysis, and what developers should do to serialize a new reader. @@ -25,18 +25,22 @@ How to serialize a new reader File Access ^^^^^^^^^^^ -If the new reader uses :func:`util.anyopen()` (e.g. PDB), the reading handler -can be pickled without modification. -If the new reader uses I/O classes from other package (e.g. GSD), and cannot -be pickled natively, create a new picklable class inherited from -the file class in that package (e.g. GSDPicklable), adding :func:`__getstate__`, +If the new reader uses :func:`util.anyopen()` +(e.g. :class:MDAnalysis.coordinates.PDB.PDBReader), +the reading handler can be pickled without modification. +If the new reader uses I/O classes from other package +(e.g. :class:MDAnalysis.coordinates.GSD.GSDReader)), +and cannot be pickled natively, create a new picklable class inherited from +the file class in that package +(e.g. :class:MDAnalysis.coordinates.GSD.GSDPicklable), +adding :func:`__getstate__`, :func:`__setstate__` functions (or :func:`__reduce__` if needed. Consult the pickle [documentation](https://docs.python.org/3/library/pickle.html) of python) to allow file handler serialization. To seek or not to seek ^^^^^^^^^^^^^^^^^^^^^^ -Some I/O class supports :func:`seek` and :func:`tell` functions to allow the file +Some I/O classes support :func:`seek` and :func:`tell` functions to allow the file to be pickled with an offset. It is normally not needed for MDAnalysis with random access. But if error occurs during testing, find a way to make the offset work. Maybe this I/O class supports frame indexing? Maybe the file handler inside this I/O @@ -59,14 +63,14 @@ Tests ^^^^^ If the test for the new reader uses :class:`BaseReaderTest`, whether the current timestep information is saved, and whether its relative -position is maintained, i.e. next() read the right next timestep, +position is maintained, i.e. next() reads the right next timestep, are already tested. If the new reader accesses the file with :func:`util.anyopen`, add necessary -testes inside ``parallelism/test_multiprocessing.py`` for the reader. +tests inside ``parallelism/test_multiprocessing.py`` for the reader. -If the new reader accessed the file with new picklable I/O class, -add necessary tests inside ``utils/test_pickleio.py`` for I/O class, +If the new reader accessed the file with a new picklable I/O class, +add necessary tests inside ``utils/test_pickleio.py`` for the I/O class, ``parallelism/test_multiprocessing.py`` for the reader. .. _implemented-fileio: diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 1d7003ce952..b005f478c12 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -130,22 +130,29 @@ def test_fileio_pickle(): @pytest.fixture(params=[ - # filename mode - (PDB, 'w', pickle_open), - (PDB, 'x', pickle_open), - (PDB, 'a', pickle_open), - (XYZ_bz2, 'w', bz2_pickle_open), - (MMTF_gz, 'w', gzip_pickle_open) + # filename mode open_func open_class + ('test.pdb', 'w', pickle_open, FileIOPicklable), + ('test.pdb', 'x', pickle_open, FileIOPicklable), + ('test.pdb', 'a', pickle_open, FileIOPicklable), + ('test.bz2', 'w', bz2_pickle_open, BZ2Picklable), + ('test.gz', 'w', gzip_pickle_open, GzipPicklable), ]) def unpicklable_f(request): - filename, mode, open_func = request.param - return filename, mode, open_func + filename, mode, open_func, open_class = request.param + return filename, mode, open_func, open_class + + +def test_unpicklable_open_mode(unpicklable_f, tmpdir): + filename, mode, open_func, open_class = unpicklable_f + with pytest.raises(ValueError, match=r"Only read mode"): + open_func(tmpdir.mkdir("pickle").join(filename), mode) -def test_unpicklable_open_mode(unpicklable_f): - filename, mode, open_func = unpicklable_f - with pytest.raises(ValueError, match=r"Only read mode *"): - open_func(filename, mode) +def test_pickle_with_write_mode(unpicklable_f, tmpdir): + filename, mode, open_func, open_class = unpicklable_f + f_open_by_class = open_class(tmpdir.mkdir("pickle").join(filename), mode) + with pytest.raises(RuntimeError, match=r"Can only pickle"): + f_pickled = pickle.loads(pickle.dumps(f_open_by_class)) def test_GSD_pickle(): @@ -155,6 +162,12 @@ def test_GSD_pickle(): gsd_io_pickled.read_frame(0).particles.position) +def test_GSD_with_write_mode(tmpdir): + with pytest.raises(ValueError, match=r"Only read mode"): + gsd_io = gsd_pickle_open(tmpdir.mkdir("gsd").join('t.gsd'), + mode='w') + + def test_NCDF_pickle(): ncdf_io = NCDFPicklable(NCDF, mmap=None) ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) @@ -179,3 +192,11 @@ def test_Chemfiles_pickle(): frame_pickled = chemfiles_io_pickled.read() assert_equal(frame.positions[:], frame_pickled.positions[:]) + + +@pytest.mark.skipif(not check_chemfiles_version(), + reason="Wrong version of chemfiles") +def test_Chemfiles_with_write_mode(tmpdir): + with pytest.raises(ValueError, match=r"Only read mode"): + chemfiles_io = ChemfilesPicklable(tmpdir.mkdir("xyz").join('t.xyz'), + mode='w') From fae47973849ba1a227985583df9c5215cc9f20ae Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Wed, 29 Jul 2020 20:08:00 +0200 Subject: [PATCH 90/98] doc pickle_reader --- .../coordinates/pickle_readers.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index 36c0bdbddcf..83311e94aa5 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -26,16 +26,16 @@ How to serialize a new reader File Access ^^^^^^^^^^^ If the new reader uses :func:`util.anyopen()` -(e.g. :class:MDAnalysis.coordinates.PDB.PDBReader), +(e.g. :class:`MDAnalysis.coordinates.PDB.PDBReader`), the reading handler can be pickled without modification. If the new reader uses I/O classes from other package -(e.g. :class:MDAnalysis.coordinates.GSD.GSDReader)), +(e.g. :class:`MDAnalysis.coordinates.GSD.GSDReader`)), and cannot be pickled natively, create a new picklable class inherited from the file class in that package -(e.g. :class:MDAnalysis.coordinates.GSD.GSDPicklable), +(e.g. :class:`MDAnalysis.coordinates.GSD.GSDPicklable`), adding :func:`__getstate__`, :func:`__setstate__` functions (or :func:`__reduce__` if needed. Consult the -pickle [documentation](https://docs.python.org/3/library/pickle.html) of python) +pickle `documentation `_ of python) to allow file handler serialization. To seek or not to seek @@ -46,8 +46,9 @@ random access. But if error occurs during testing, find a way to make the offset Maybe this I/O class supports frame indexing? Maybe the file handler inside this I/O class supports offset? -For example, in `TRZReader`, `_read_frame` is implemented by `_seek`ing the file into -its previous frame and `_read_next_timestep`, so the offset of the file is crucial +For example, in :class:`MDAnalysis.coordinates.TRZ.TRZReader`, +:func:`_read_frame` is implemented by :func:`_seek` ing the file into +its previous frame and :func:`_read_next_timestep`, so the offset of the file is crucial for such machinery to work. Miscellaneous From 52a981ecf97325661e9f6c6127833ffed1428863 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 30 Jul 2020 16:05:39 +0200 Subject: [PATCH 91/98] mock chemfiles --- package/MDAnalysis/coordinates/chemfiles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index 98abb79b528..c1302a328f8 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -41,6 +41,7 @@ """ import numpy as np from distutils.version import LooseVersion +import sys import warnings from . import base, core @@ -52,6 +53,14 @@ else: HAS_CHEMFILES = True +# Allow building documentation even if chemfiles is not installed +if not HAS_CHEMFILES and 'sphinx' in sys.modules: + import imp + class MockTrajectory: pass + chemfiles = imp.new_module("chemfiles") + chemfiles.Trajectory = MockTrajectory + HAS_CHEMFILES = True + MIN_CHEMFILES_VERSION = LooseVersion("0.9") MAX_CHEMFILES_VERSION = LooseVersion("0.10") From c4ec287b110a85e8131da88e70e2456451b151a3 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Sat, 1 Aug 2020 15:18:37 +0200 Subject: [PATCH 92/98] chemfiles mock when not found --- package/MDAnalysis/coordinates/chemfiles.py | 127 ++++++++++---------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/package/MDAnalysis/coordinates/chemfiles.py b/package/MDAnalysis/coordinates/chemfiles.py index c1302a328f8..8538c8fc27c 100644 --- a/package/MDAnalysis/coordinates/chemfiles.py +++ b/package/MDAnalysis/coordinates/chemfiles.py @@ -39,9 +39,7 @@ .. autoclass:: ChemfilesPicklable """ -import numpy as np from distutils.version import LooseVersion -import sys import warnings from . import base, core @@ -50,15 +48,15 @@ import chemfiles except ImportError: HAS_CHEMFILES = False -else: - HAS_CHEMFILES = True -# Allow building documentation even if chemfiles is not installed -if not HAS_CHEMFILES and 'sphinx' in sys.modules: + # Allow building documentation even if chemfiles is not installed import imp - class MockTrajectory: pass + + class MockTrajectory: + pass chemfiles = imp.new_module("chemfiles") chemfiles.Trajectory = MockTrajectory +else: HAS_CHEMFILES = True @@ -393,70 +391,69 @@ def _topology_to_chemfiles(self, obj, n_atoms): return topology -if HAS_CHEMFILES: - class ChemfilesPicklable(chemfiles.Trajectory): - """Chemfiles file object (read-only) that can be pickled. +class ChemfilesPicklable(chemfiles.Trajectory): + """Chemfiles file object (read-only) that can be pickled. - This class provides a file-like object (as returned by - :class:`chemfiles.Trajectory`) that, - unlike standard Python file objects, - can be pickled. Only read mode is supported. + This class provides a file-like object (as returned by + :class:`chemfiles.Trajectory`) that, + unlike standard Python file objects, + can be pickled. Only read mode is supported. - When the file is pickled, path, mode, and format of the file handle - are saved. On unpickling, the file is opened by path with mode, - and saved format. - This means that for a successful unpickle, the original file still has - to be accessible with its filename. + When the file is pickled, path, mode, and format of the file handle + are saved. On unpickling, the file is opened by path with mode, + and saved format. + This means that for a successful unpickle, the original file still has + to be accessible with its filename. - Note - ---- - Can only be used with reading ('r') mode. - Upon pickling, the current frame is reset. `universe.trajectory[i]` has - to be used to return to its original frame. + Note + ---- + Can only be used with reading ('r') mode. + Upon pickling, the current frame is reset. `universe.trajectory[i]` has + to be used to return to its original frame. - Parameters - ---------- - filename : str - a filename given a text or byte string. - mode : 'r' , optional - only 'r' can be used for pickling. - format : '', optional - guessed from the file extension if empty. - - Example - ------- - :: - - f = ChemfilesPicklable(XYZ, 'r', '') - print(f.read()) - f.close() + Parameters + ---------- + filename : str + a filename given a text or byte string. + mode : 'r' , optional + only 'r' can be used for pickling. + format : '', optional + guessed from the file extension if empty. - can also be used as context manager:: + Example + ------- + :: - with ChemfilesPicklable(XYZ) as f: - print(f.read()) + f = ChemfilesPicklable(XYZ, 'r', '') + print(f.read()) + f.close() - See Also - --------- - :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` - :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` - :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + can also be used as context manager:: + with ChemfilesPicklable(XYZ) as f: + print(f.read()) - .. versionadded:: 2.0.0 - """ - def __init__(self, path, mode="r", format=""): - if mode != 'r': - raise ValueError("Only read mode ('r') " - "files can be pickled.") - super().__init__(path=path, - mode=mode, - format=format) - - def __getstate__(self): - return self.path, self._Trajectory__mode, self._Trajectory__format - - def __setstate__(self, args): - self.__init__(*args) + See Also + --------- + :class:`MDAnalysis.lib.picklable_file_io.FileIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BufferIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.TextIOPicklable` + :class:`MDAnalysis.lib.picklable_file_io.GzipPicklable` + :class:`MDAnalysis.lib.picklable_file_io.BZ2Picklable` + + + .. versionadded:: 2.0.0 + """ + def __init__(self, path, mode="r", format=""): + if mode != 'r': + raise ValueError("Only read mode ('r') " + "files can be pickled.") + super().__init__(path=path, + mode=mode, + format=format) + + def __getstate__(self): + return self.path, self._Trajectory__mode, self._Trajectory__format + + def __setstate__(self, args): + self.__init__(*args) From 8804e5bef2e7c2455119609abe6cc82e963bdc27 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 3 Aug 2020 11:34:20 +0200 Subject: [PATCH 93/98] doc revised --- package/MDAnalysis/core/universe.py | 4 +- package/MDAnalysis/lib/picklable_file_io.py | 67 ++++++++++++------- .../coordinates/pickle_readers.rst | 2 +- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 3276672f2c1..390acb8e65e 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -318,8 +318,8 @@ class Universe(object): .. versionchanged:: 2.0.0 Universe now can be (un)pickled. - Topology, trajectory and anchor_name are reserved upon unpickle. - + ``topology``, ``trajectory`` and ``anchor_name`` are reserved + upon unpickle. """ # Py3 TODO # def __init__(self, topology=None, *coordinates, all_coordinates=False, diff --git a/package/MDAnalysis/lib/picklable_file_io.py b/package/MDAnalysis/lib/picklable_file_io.py index 2fb59544fb3..dd5bef44c14 100644 --- a/package/MDAnalysis/lib/picklable_file_io.py +++ b/package/MDAnalysis/lib/picklable_file_io.py @@ -80,9 +80,12 @@ class FileIOPicklable(io.FileIO): Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode : str - only reading ('r') mode works. + only reading ('r') mode works. It exists to be consistent + with a wider API. Example ------- @@ -227,12 +230,14 @@ class BZ2Picklable(bz2.BZ2File): Note ---- This class only supports reading files in binary mode. If you need to open - to open a compressed file in text mode, use the :func:`bz2_pickle_open`. + to open a compressed file in text mode, use :func:`bz2_pickle_open`. Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode : str can only be 'r', 'rb' to make pickle work. @@ -292,7 +297,9 @@ class GzipPicklable(gzip.GzipFile): Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode : str can only be 'r', 'rb' to make pickle work. @@ -334,14 +341,16 @@ def __setstate__(self, args): def pickle_open(name, mode='rt'): """Open file and return a stream with pickle function implemented. - This function returns either BufferIOPicklable or TextIOPicklable wrapped - FileIOPicklable object given different reading mode. It can be used as a - context manager, and replace the built-in :func:`open` function - in read mode that only returns an unpicklable file object. + This function returns a FileIOPicklable object wrapped in a + BufferIOPicklable class when given the "rb" reading mode, + or a FileIOPicklable object wrapped in a TextIOPicklable class with the "r" + or "rt" reading mode. It can be used as a context manager, and replace the + built-in :func:`open` function in read mode that only returns an + unpicklable file object. In order to serialize a :class:`MDAnalysis.core.Universe`, this function - can used to open trajectory/topology files--an object composition approach, - as opposed to class inheritance, which is more flexible and easier for - pickle implementation for new readers. + can used to open trajectory/topology files. This object composition is more + flexible and easier than class inheritance to implement pickling + for new readers. Note ---- @@ -350,7 +359,9 @@ def pickle_open(name, mode='rt'): Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode: {'r', 'rt', 'rb'} (optional) 'r': open for reading in text mode; 'rt': read in text mode (default); @@ -390,7 +401,7 @@ def pickle_open(name, mode='rt'): """ if mode not in {'r', 'rt', 'rb'}: raise ValueError("Only read mode ('r', 'rt', 'rb') " - "iles can be pickled.") + "files can be pickled.") name = os.fspath(name) raw = FileIOPicklable(name) if mode == 'rb': @@ -403,10 +414,12 @@ def bz2_pickle_open(name, mode='rb'): """Open a bzip2-compressed file in binary or text mode with pickle function implemented. - This function returns either BZ2Picklable or TextIOPicklable wrapped - BZ2Picklable object given different reading mode. It can be used as a - context manager, and replace the built-in :func:`bz2.open` function - in read mode that only returns an unpicklable file object. + This function returns a BZ2Picklable object when given the "rb" or "r" + reading mode, or a BZ2Picklable object wrapped in a TextIOPicklable class + with the "rt" reading mode. + It can be used as a context manager, and replace the built-in + :func:`bz2.open` function in read mode that only returns an + unpicklable file object. Note ---- @@ -415,7 +428,9 @@ def bz2_pickle_open(name, mode='rb'): Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode: {'r', 'rt', 'rb'} (optional) 'r': open for reading in binary mode; 'rt': read in text mode; @@ -471,10 +486,12 @@ def gzip_pickle_open(name, mode='rb'): """Open a gzip-compressed file in binary or text mode with pickle function implemented. - This function returns either GzipPicklable or TextIOPicklable wrapped - GzipPicklable object given different reading mode. It can be used as a - context manager, and replace the built-in :func:`gzip.open` function - in read mode that only returns an unpicklable file object. + This function returns a GzipPicklable object when given the "rb" or "r" + reading mode, or a GzipPicklable object wrapped in a TextIOPicklable class + with the "rt" reading mode. + It can be used as a context manager, and replace the built-in + :func:`gzip.open` function in read mode that only returns an + unpicklable file object. Note ---- @@ -483,7 +500,9 @@ def gzip_pickle_open(name, mode='rb'): Parameters ---------- name : str - a filename given a text or byte string. + either a text or byte string giving the name (and the path + if the file isn't in the current working directory) of the file to + be opened. mode: {'r', 'rt', 'rb'} (optional) 'r': open for reading in binary mode; 'rt': read in text mode; diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index 83311e94aa5..ad3898eb807 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -29,7 +29,7 @@ If the new reader uses :func:`util.anyopen()` (e.g. :class:`MDAnalysis.coordinates.PDB.PDBReader`), the reading handler can be pickled without modification. If the new reader uses I/O classes from other package -(e.g. :class:`MDAnalysis.coordinates.GSD.GSDReader`)), +(e.g. :class:`MDAnalysis.coordinates.GSD.GSDReader`), and cannot be pickled natively, create a new picklable class inherited from the file class in that package (e.g. :class:`MDAnalysis.coordinates.GSD.GSDPicklable`), From c99867f7a7d7fc83b4172ca8ce55d16804ee7dca Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 3 Aug 2020 16:32:15 +0200 Subject: [PATCH 94/98] add pickle test to single_framereader --- testsuite/MDAnalysisTests/coordinates/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 9ddff4241a8..32ebe734c22 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -120,6 +120,13 @@ def test_last_slice(self): frames = [ts.frame for ts in trj_iter] assert_equal(frames, np.arange(self.universe.trajectory.n_frames)) + def test_pickle_singleframe_reader(self): + reader = self.universe.trajectory + reader_p = pickle.loads(pickle.dumps(reader)) + assert_equal(len(reader), len(reader_p)) + assert_equal(reader.ts, reader_p.ts, + "Single-frame timestep is changed after pickling") + class BaseReference(object): def __init__(self): From a70bc8be70c70b7950dec903b48c5dfd6aa817ef Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Mon, 3 Aug 2020 17:38:24 +0200 Subject: [PATCH 95/98] add pickle test to fhiams --- testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py index 81bc52b4e7d..0832e907756 100644 --- a/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py +++ b/testsuite/MDAnalysisTests/parallelism/test_multiprocessing.py @@ -36,6 +36,7 @@ DMS, DLP_CONFIG, DLP_HISTORY, + FHIAIMS, INPCRD, GMS_ASYMOPT, GMS_SYMOPT, @@ -130,6 +131,7 @@ def test_universe_unpickle_in_new_process(): ('DCD', DCD, dict()), ('DMS', DMS, dict()), ('CONFIG', DLP_CONFIG, dict()), + ('FHIAIMS', FHIAIMS, dict()), ('HISTORY', DLP_HISTORY, dict()), ('INPCRD', INPCRD, dict()), ('LAMMPSDUMP', LAMMPSDUMP, dict()), From bc487a5f2fb34e25aabe27341d9bde7f17fca945 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 6 Aug 2020 17:18:03 +0200 Subject: [PATCH 96/98] test doc --- .../coordinates/pickle_readers.rst | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index ad3898eb807..c693092ffe4 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -62,11 +62,21 @@ If the new reader is written in Cython, read :class:`lib.formats.libmdaxdr` and Tests ^^^^^ -If the test for the new reader uses :class:`BaseReaderTest`, whether -the current timestep information is saved, and whether its relative -position is maintained, i.e. next() reads the right next timestep, -are already tested. - +_SingleFrameReader Test +~~~~~~~~~~~~~~~~~~~~~~~ +If the new reader is a single-frame reader, the basic test should normally +inherited from :class:`_SingleFrameReader`, where the pickliablity is tested. + +BaseReaderTest and MultiframeReaderTest +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If the test for the new reader uses :class:`BaseReaderTest` or +:class:`MultiframeReaderTest`, whether the current timestep information is +saved (the former), whether its relative position is maintained, +i.e. next() reads the right next timestep, and whether its last timestep +can be pickled, are already tested. + +File handler Test +~~~~~~~~~~~~~~~~~ If the new reader accesses the file with :func:`util.anyopen`, add necessary tests inside ``parallelism/test_multiprocessing.py`` for the reader. From a1bb47e21ba0f087b0555d30eaf0ee29e732a473 Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 6 Aug 2020 17:19:03 +0200 Subject: [PATCH 97/98] test doc title --- .../source/documentation_pages/coordinates/pickle_readers.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index c693092ffe4..4266a5662dd 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -60,8 +60,10 @@ methods for the reader. If the new reader is written in Cython, read :class:`lib.formats.libmdaxdr` and :class:`lib.formats.libdcd` files as references. +.. _test_pickle: + Tests -^^^^^ +----- _SingleFrameReader Test ~~~~~~~~~~~~~~~~~~~~~~~ If the new reader is a single-frame reader, the basic test should normally From 5ace1e016b226d746ca35453eaadf5085094d54e Mon Sep 17 00:00:00 2001 From: Yuxuan Zhuang Date: Thu, 6 Aug 2020 17:20:24 +0200 Subject: [PATCH 98/98] test doc title 2 --- .../documentation_pages/coordinates/pickle_readers.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst index 4266a5662dd..0866590297f 100644 --- a/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst +++ b/package/doc/sphinx/source/documentation_pages/coordinates/pickle_readers.rst @@ -65,12 +65,12 @@ If the new reader is written in Cython, read :class:`lib.formats.libmdaxdr` and Tests ----- _SingleFrameReader Test -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ If the new reader is a single-frame reader, the basic test should normally inherited from :class:`_SingleFrameReader`, where the pickliablity is tested. BaseReaderTest and MultiframeReaderTest -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the test for the new reader uses :class:`BaseReaderTest` or :class:`MultiframeReaderTest`, whether the current timestep information is saved (the former), whether its relative position is maintained, @@ -78,7 +78,7 @@ i.e. next() reads the right next timestep, and whether its last timestep can be pickled, are already tested. File handler Test -~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^ If the new reader accesses the file with :func:`util.anyopen`, add necessary tests inside ``parallelism/test_multiprocessing.py`` for the reader.