From 51c6bfad3607dd84460d11159999a1d5a1f7a6af Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 23 Oct 2015 09:31:05 +0200 Subject: [PATCH 1/6] Add Base tests for Reader/Writer classes This introduces a single test class for all the different format readers and writers. It is currently not used by any of them though. This is done to start the unification process for testing the reader/writer classes to be sure all follow the same API. Testing new common API's is now also just a matter of adding it to the base class. --- testsuite/MDAnalysisTests/coordinates/base.py | 200 +++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 2441687be56..dd42c63da44 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -1,8 +1,11 @@ import MDAnalysis as mda import numpy as np +from six.moves import zip -from numpy.testing import assert_equal, assert_raises, assert_almost_equal +from numpy.testing import (assert_equal, assert_raises, assert_almost_equal, + assert_array_almost_equal, raises) from unittest import TestCase +import tempdir from MDAnalysisTests.coordinates.reference import RefAdKSmall @@ -92,3 +95,198 @@ def test_last_slice(self): trj_iter = self.universe.trajectory[-1:] frames = [ts.frame for ts in trj_iter] assert_equal(frames, np.arange(self.universe.trajectory.n_frames)) + + +class BaseReference(object): + def __init__(self): + self.trajectory = None + self.n_atoms = 5 + self.n_frames = 5 + # default for the numpy test functions + self.prec = 6 + self.first_frame = np.arange(3 * self.n_atoms).reshape(self.n_atoms, 3) + self.second_frame = 2 ** 1 * self.first_frame + self.last_frame = 2 ** 4 * self.first_frame + self.jump_to_frame = 3 # second to last frame + # remember frames are 0 indexed + self.jump_to_frame_positions = 2 ** 3 * self.first_frame + self.has_forces = False + self.has_velocities = False + self.dimensions = np.array([80, 80, 80, 60, 60, 90], dtype=np.float32) + self.volume = mda.lib.mdamath.box_volume(self.dimensions) + self.time = 0 + self.dt = 1 + self.totaltime = 5 + + +class BaseReaderTest(object): + def __init__(self, reference): + self.ref = reference + self.reader = self.ref.reader(self.ref.trajectory) + + def test_n_atoms(self): + assert_equal(self.reader.n_atoms, self.ref.n_atoms) + + def test_n_frames(self): + assert_equal(len(self.reader), self.ref.n_frames) + + def test_first_frame(self): + self.reader.rewind() + assert_array_almost_equal(self.reader.ts.positions, + self.ref.first_frame, decimal=self.ref.prec) + + def test_double_close(self): + self.reader.close() + self.reader.close() + self.reader._reopen() + + def test_reopen(self): + self.reader.close() + self.reader._reopen() + ts = self.reader.next() + assert_array_almost_equal(ts.positions, self.ref.first_frame, + decimal=self.ref.prec) + + def test_last_frame(self): + ts = self.reader[-1] + assert_array_almost_equal(ts.positions, self.ref.last_frame, + decimal=self.ref.prec) + + def test_next_gives_second_frame(self): + reader = self.ref.reader(self.ref.trajectory) + ts = reader.next() + assert_array_almost_equal(ts.positions, self.ref.second_frame, + decimal=self.ref.prec) + + @raises(IndexError) + def test_go_over_last_frame(self): + self.reader[self.ref.n_frames + 1] + + def test_frame_jump(self): + ts = self.reader[self.ref.jump_to_frame] + assert_array_almost_equal(ts.positions, + self.ref.jump_to_frame_positions, + decimal=self.ref.prec) + + def test_get_writer(self): + with tempdir.in_tempdir(): + self.outfile = 'test-writer' + self.ref.ext + with self.reader.Writer(self.outfile) as W: + assert_equal(isinstance(W, self.ref.writer), True) + assert_equal(W.n_atoms, self.reader.n_atoms) + + def test_get_writer_2(self): + with tempdir.in_tempdir(): + self.outfile = 'test-writer' + self.ref.ext + with self.reader.Writer(self.outfile, n_atoms=100) as W: + assert_equal(isinstance(W, self.ref.writer), True) + assert_equal(W.n_atoms, 100) + + def test_has_velocities(self): + assert(self.reader.ts.has_velocities == self.ref.has_velocities) + + def test_velocities(self): + if self.ref.has_velocities: + ts = self.reader.rewind() + assert_array_almost_equal(ts.velocities, + self.ref.velocities, + self.ref.prec) + + def test_has_forces(self): + assert(self.reader.ts.has_forces == self.ref.has_forces) + + def test_forces(self): + if self.ref.has_forces: + ts = self.reader.rewind() + assert_array_almost_equal(ts.forces, self.ref.forces, + self.ref.prec) + + def test_dt(self): + assert_equal(self.reader.dt, self.ref.dt) + + def test_total_time(self): + assert_equal(self.reader.totaltime, self.ref.totaltime) + + def test_dimensions(self): + assert_array_almost_equal(self.reader.ts.dimensions, + self.ref.dimensions, + decimal=self.ref.prec) + + def test_volume(self): + self.reader.rewind() + vol = self.reader.ts.volume + assert_array_almost_equal(vol, self.ref.volume) + + def test_iter(self): + for i, ts in enumerate(self.reader): + assert_array_almost_equal(ts.positions, + 2**i * self.ref.first_frame, + decimal=self.ref.prec) + assert_equal(i, ts.time) + + +class BaseWriterTest(object): + def __init__(self, reference): + self.ref = reference + self.tmpdir = tempdir.TempDir() + self.reader = self.ref.reader(self.ref.trajectory) + + def tmp_file(self, name): + return self.tmpdir.name + name + '.' + self.ref.ext + + def test_write_trajectory_timestep(self): + outfile = self.tmp_file('write-timestep-test') + with self.ref.writer(outfile) as W: + for ts in self.reader: + W.write(ts) + self._check_copy(outfile) + + def test_write_trajectory_atomgroup(self): + uni = mda.Universe(self.ref.topology, self.ref.trajectory) + outfile = self.tmp_file('write-atoms-test') + with self.ref.writer(outfile) as w: + for ts in uni.trajectory: + w.write(uni.atoms) + self._check_copy(outfile) + + def test_write_trajectory_universe(self): + uni = mda.Universe(self.ref.topology, self.ref.trajectory) + outfile = self.tmp_file('write-uni-test') + with self.ref.writer(outfile) as w: + for ts in uni.trajectory: + w.write(uni) + self._check_copy(outfile) + + def test_write_selection(self): + uni = mda.Universe(self.ref.topology, self.ref.trajectory) + sel_str = 'resid 1' + sel = uni.select_atoms(sel_str) + outfile = self.tmp_file('write-selection-test') + + with self.ref.writer(outfile) as W: + for ts in uni.trajectory: + W.write(sel.atoms) + + copy = self.ref.reader(outfile) + for orig_ts, copy_ts in zip(uni.trajectory, copy): + assert_array_almost_equal( + copy_ts._pos, sel.atoms.positions, self.ref.prec, + err_msg="coordinate mismatch between original and written " + "trajectory at frame {} (orig) vs {} (copy)".format( + orig_ts.frame, copy_ts.frame)) + + def _check_copy(self, fname): + copy = self.ref.reader(fname) + assert_equal(self.reader.n_frames, copy.n_frames) + for orig_ts, copy_ts in zip(self.reader, copy): + assert_array_almost_equal( + copy_ts._pos, orig_ts._pos, self.ref.prec, + err_msg="coordinate mismatch between original and written " + "trajectory at frame {} (orig) vs {} (copy)".format( + orig_ts.frame, copy_ts.frame)) + + @raises(mda.NoDataError) + def test_write_none(self): + outfile = self.tmp_file('write-none') + with self.ref.writer(outfile) as w: + w.write(None) From 232ee952219d4d931b3b054c49c303ce23ab0430 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 23 Oct 2015 09:33:10 +0200 Subject: [PATCH 2/6] Convert XYZ-format to be tested with new test-class The XYZ Reader/Writer is now extensivly tested with the new base test class for reader and writers. It also serves as an example how to add format specific tests to these new classes. As a bonus the coverage of the XYZ coordinate module is increased. --- .../coordinates/data/README.md | 24 ++ .../coordinates/data/__init__.py | 6 + .../coordinates/data/create_data.py | 26 ++ .../MDAnalysisTests/coordinates/data/test.xyz | 35 +++ .../coordinates/data/test.xyz.bz2 | Bin 0 -> 217 bytes .../coordinates/data/test_topology.pdb | 8 + .../MDAnalysisTests/coordinates/test_xyz.py | 274 ++++++------------ 7 files changed, 186 insertions(+), 187 deletions(-) create mode 100644 testsuite/MDAnalysisTests/coordinates/data/README.md create mode 100644 testsuite/MDAnalysisTests/coordinates/data/__init__.py create mode 100644 testsuite/MDAnalysisTests/coordinates/data/create_data.py create mode 100644 testsuite/MDAnalysisTests/coordinates/data/test.xyz create mode 100644 testsuite/MDAnalysisTests/coordinates/data/test.xyz.bz2 create mode 100644 testsuite/MDAnalysisTests/coordinates/data/test_topology.pdb diff --git a/testsuite/MDAnalysisTests/coordinates/data/README.md b/testsuite/MDAnalysisTests/coordinates/data/README.md new file mode 100644 index 00000000000..8ea185d2076 --- /dev/null +++ b/testsuite/MDAnalysisTests/coordinates/data/README.md @@ -0,0 +1,24 @@ +Coordinate tests can have special small test-files that cover everything that +needs to be tested. That can mean mail formated files or files that don't +fullfill their standard completely but are still read by most tools (eq. PDB). + +Here follows a list of each file with a short description how it was generated +and how the content can be validated. + +text.xyz +-------- +## Creation +Written with MDAnalysis using the 'create_data.py' script + +## Validation +Manually examining the file in a text editor of your choice. The atom names +should be the same as in 'test_topology.pdb'. + +test.xyz.bz2 +------------ +## Creation + + bzip2 test.xyz + +## Validation +unpack and check that the content is the same as test.xyz diff --git a/testsuite/MDAnalysisTests/coordinates/data/__init__.py b/testsuite/MDAnalysisTests/coordinates/data/__init__.py new file mode 100644 index 00000000000..59122650f77 --- /dev/null +++ b/testsuite/MDAnalysisTests/coordinates/data/__init__.py @@ -0,0 +1,6 @@ +from pkg_resources import resource_filename + + +def get_file(fname): + """return full path to trajectory named 'fname'""" + return resource_filename(__name__, fname) diff --git a/testsuite/MDAnalysisTests/coordinates/data/create_data.py b/testsuite/MDAnalysisTests/coordinates/data/create_data.py new file mode 100644 index 00000000000..e67c4925dbf --- /dev/null +++ b/testsuite/MDAnalysisTests/coordinates/data/create_data.py @@ -0,0 +1,26 @@ +import MDAnalysis as mda +import numpy as np +from six.moves import range + + +def create_test_trj(uni, fname, wrong_natoms=False): + n_atoms = uni.atoms.n_atoms + if wrong_natoms: + n_atoms -= 2 + pos = np.arange(3 * n_atoms).reshape(n_atoms, 3) + uni.trajectory.ts.dt = 1 + with mda.Writer(fname, n_atoms) as w: + for i in range(5): + uni.atoms.positions = 2 ** i * pos + uni.trajectory.ts.time = i + w.write(uni) + + +def main(): + pdb = 'test_topology.pdb' + u = mda.Universe(pdb) + + create_test_trj(u, 'test.xyz') + +if __name__ == '__main__': + main() diff --git a/testsuite/MDAnalysisTests/coordinates/data/test.xyz b/testsuite/MDAnalysisTests/coordinates/data/test.xyz new file mode 100644 index 00000000000..00b374e7c2e --- /dev/null +++ b/testsuite/MDAnalysisTests/coordinates/data/test.xyz @@ -0,0 +1,35 @@ +5 +frame 0 + CA 0.00000 1.00000 2.00000 + CA 3.00000 4.00000 5.00000 + CA 6.00000 7.00000 8.00000 + CA 9.00000 10.00000 11.00000 + CA 12.00000 13.00000 14.00000 +5 +frame 0 + CA 0.00000 2.00000 4.00000 + CA 6.00000 8.00000 10.00000 + CA 12.00000 14.00000 16.00000 + CA 18.00000 20.00000 22.00000 + CA 24.00000 26.00000 28.00000 +5 +frame 0 + CA 0.00000 4.00000 8.00000 + CA 12.00000 16.00000 20.00000 + CA 24.00000 28.00000 32.00000 + CA 36.00000 40.00000 44.00000 + CA 48.00000 52.00000 56.00000 +5 +frame 0 + CA 0.00000 8.00000 16.00000 + CA 24.00000 32.00000 40.00000 + CA 48.00000 56.00000 64.00000 + CA 72.00000 80.00000 88.00000 + CA 96.00000 104.00000 112.00000 +5 +frame 0 + CA 0.00000 16.00000 32.00000 + CA 48.00000 64.00000 80.00000 + CA 96.00000 112.00000 128.00000 + CA 144.00000 160.00000 176.00000 + CA 192.00000 208.00000 224.00000 diff --git a/testsuite/MDAnalysisTests/coordinates/data/test.xyz.bz2 b/testsuite/MDAnalysisTests/coordinates/data/test.xyz.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..3eb0138ab8715703cf0b8779c0cbf7689d3d84b8 GIT binary patch literal 217 zcmV;~04D!JT4*^jL0KkKSzF})qyPc+-GFEiKmmW?C;%e@5CAX%87T~z6I6O-G5}Bk zh|^6P2vjK000xA2sDvP7TqLJ7$Re2V5d_RoN^FCH#1sjVrUE9Z0DB?;1ZMi@Z7y0; z-xDH`N^20gSg7ec@~-0PP8ydDDN`;SOPO%fQi%i%k=|=}K-H~lZG{l>q8o3#aU5Z= zrY!O^s#f;*WL=doHj#VM#UTj2Rt;eEL^8&B$a#%UgR$5s@fIL8!PX9I1JMBbARokj TBceX1 Date: Tue, 20 Oct 2015 18:31:48 +0200 Subject: [PATCH 3/6] XYZReader.writer handles n_atoms kwarg Now when n_atoms is not specified we assume that the same number of atoms as in the reader should be used. This conforms to the other standart APIs. --- package/MDAnalysis/coordinates/XYZ.py | 44 ++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/package/MDAnalysis/coordinates/XYZ.py b/package/MDAnalysis/coordinates/XYZ.py index 0099485ae32..451b616e071 100644 --- a/package/MDAnalysis/coordinates/XYZ.py +++ b/package/MDAnalysis/coordinates/XYZ.py @@ -155,7 +155,12 @@ def _get_atomnames(self, atoms): pass # list or string (can be a single atom name... deal with this in # write_next_timestep() once we know n_atoms) - return np.asarray(util.asiterable(atoms)) + if self.n_atoms is None: + return np.asarray(util.asiterable(atoms)) + if isinstance(atoms, list): + return atoms + else: + return np.asarray([atoms for _ in range(self.n_atoms)]) def close(self): """Close the trajectory file and finalize the writing""" @@ -215,6 +220,7 @@ def write_next_timestep(self, ts=None): else: ts = self.ts + if self.n_atoms is not None: if self.n_atoms != ts.n_atoms: raise ValueError('n_atoms keyword was specified indicating ' @@ -379,21 +385,31 @@ def open_trajectory(self): return self.xyzfile - def Writer(self, filename, **kwargs): - """Returns a XYZWriter for *filename* with the same parameters as this XYZ. - - All values can be changed through keyword arguments. - - :Arguments: - *filename* - filename of the output DCD trajectory - :Keywords: - *atoms* - names of the atoms (if not taken from atom groups) + def Writer(self, filename, n_atoms=None, **kwargs): + """Returns a XYZWriter for *filename* with the same parameters as this + XYZ. - :Returns: :class:`XYZWriter` (see there for more details) + Parameters + ---------- + filename: str + filename of the output trajectory + n_atoms: int (optional) + number of atoms. If none is given use the same number of atoms from + the reader instance is used + **kwargs: + See :class:`XYZWriter` for additional kwargs + + Returns + ------- + :class:`XYZWriter` (see there for more details) + + See Also + -------- + :class: `XYZWriter` """ - return XYZWriter(filename, **kwargs) + if n_atoms is None: + n_atoms = self.n_atoms + return XYZWriter(filename, n_atoms=n_atoms, **kwargs) def close(self): """Close xyz trajectory file if it was open.""" From 25693e02632455ac1cd378639d899b4e5d4026f2 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 23 Oct 2015 12:42:45 +0200 Subject: [PATCH 4/6] Add test files to test package. --- testsuite/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testsuite/setup.py b/testsuite/setup.py index 961eb92ca05..c7d311e0ffd 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -128,6 +128,8 @@ 'data/dlpoly/CONFIG*', 'data/dlpoly/HISTORY*', 'data/*.xml', + 'coordinates/data/*xyz', + 'coordinates/data/*xyz.bz2', ], }, classifiers=CLASSIFIERS, From d207b805ebf36a614a1328ebd2e34e67e80cd0f5 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sat, 24 Oct 2015 00:18:56 +0200 Subject: [PATCH 5/6] Move special coord test data to normal data-folder All test data live in one place and is sorted logically with the help of the filesystem tree. It also follows the convention to have a constant refering to the file on disk. --- .../MDAnalysisTests/coordinates/data/__init__.py | 6 ------ testsuite/MDAnalysisTests/coordinates/test_xyz.py | 8 ++++---- .../data => data/coordinates}/README.md | 0 .../data => data/coordinates}/create_data.py | 0 .../{coordinates/data => data/coordinates}/test.xyz | 0 .../data => data/coordinates}/test.xyz.bz2 | Bin .../data => data/coordinates}/test_topology.pdb | 0 testsuite/MDAnalysisTests/datafiles.py | 6 ++++++ testsuite/setup.py | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 testsuite/MDAnalysisTests/coordinates/data/__init__.py rename testsuite/MDAnalysisTests/{coordinates/data => data/coordinates}/README.md (100%) rename testsuite/MDAnalysisTests/{coordinates/data => data/coordinates}/create_data.py (100%) rename testsuite/MDAnalysisTests/{coordinates/data => data/coordinates}/test.xyz (100%) rename testsuite/MDAnalysisTests/{coordinates/data => data/coordinates}/test.xyz.bz2 (100%) rename testsuite/MDAnalysisTests/{coordinates/data => data/coordinates}/test_topology.pdb (100%) diff --git a/testsuite/MDAnalysisTests/coordinates/data/__init__.py b/testsuite/MDAnalysisTests/coordinates/data/__init__.py deleted file mode 100644 index 59122650f77..00000000000 --- a/testsuite/MDAnalysisTests/coordinates/data/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from pkg_resources import resource_filename - - -def get_file(fname): - """return full path to trajectory named 'fname'""" - return resource_filename(__name__, fname) diff --git a/testsuite/MDAnalysisTests/coordinates/test_xyz.py b/testsuite/MDAnalysisTests/coordinates/test_xyz.py index 7f7c0961062..3eba6ae40ad 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_xyz.py +++ b/testsuite/MDAnalysisTests/coordinates/test_xyz.py @@ -3,7 +3,7 @@ from numpy.testing import assert_array_almost_equal, raises -from MDAnalysisTests.coordinates.data import get_file +from MDAnalysisTests.datafiles import COORDINATES_XYZ, COORDINATES_XYZ_BZ2 from MDAnalysisTests.coordinates.base import (BaseReaderTest, BaseReference, BaseWriterTest) @@ -11,9 +11,9 @@ class XYZReference(BaseReference): def __init__(self): super(XYZReference, self).__init__() - self.trajectory = get_file('test.xyz') + self.trajectory = COORDINATES_XYZ # XYZ is it's own Topology - self.topology = get_file('test.xyz') + self.topology = COORDINATES_XYZ self.reader = mda.coordinates.XYZ.XYZReader self.writer = mda.coordinates.XYZ.XYZWriter self.ext = 'xyz' @@ -76,7 +76,7 @@ def test_no_conversion(self): class XYZ_BZ_Reference(XYZReference): def __init__(self): super(XYZ_BZ_Reference, self).__init__() - self.trajectory = get_file('test.xyz.bz2') + self.trajectory = COORDINATES_XYZ_BZ2 self.ext = 'xyz.bz2' diff --git a/testsuite/MDAnalysisTests/coordinates/data/README.md b/testsuite/MDAnalysisTests/data/coordinates/README.md similarity index 100% rename from testsuite/MDAnalysisTests/coordinates/data/README.md rename to testsuite/MDAnalysisTests/data/coordinates/README.md diff --git a/testsuite/MDAnalysisTests/coordinates/data/create_data.py b/testsuite/MDAnalysisTests/data/coordinates/create_data.py similarity index 100% rename from testsuite/MDAnalysisTests/coordinates/data/create_data.py rename to testsuite/MDAnalysisTests/data/coordinates/create_data.py diff --git a/testsuite/MDAnalysisTests/coordinates/data/test.xyz b/testsuite/MDAnalysisTests/data/coordinates/test.xyz similarity index 100% rename from testsuite/MDAnalysisTests/coordinates/data/test.xyz rename to testsuite/MDAnalysisTests/data/coordinates/test.xyz diff --git a/testsuite/MDAnalysisTests/coordinates/data/test.xyz.bz2 b/testsuite/MDAnalysisTests/data/coordinates/test.xyz.bz2 similarity index 100% rename from testsuite/MDAnalysisTests/coordinates/data/test.xyz.bz2 rename to testsuite/MDAnalysisTests/data/coordinates/test.xyz.bz2 diff --git a/testsuite/MDAnalysisTests/coordinates/data/test_topology.pdb b/testsuite/MDAnalysisTests/data/coordinates/test_topology.pdb similarity index 100% rename from testsuite/MDAnalysisTests/coordinates/data/test_topology.pdb rename to testsuite/MDAnalysisTests/data/coordinates/test_topology.pdb diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index fb9187f70b7..cf773da7f8c 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -88,10 +88,16 @@ "waterPSF","waterDCD","rmsfArray", "HoomdXMLdata", "Make_Whole", # for testing the function lib.mdamath.make_whole, has 9 atoms + "COORDINATES_XYZ", + "COORDINATES_XYZ_BZ2", ] from pkg_resources import resource_filename +COORDINATES_XYZ = resource_filename(__name__, 'data/coordinates/test.xyz') +COORDINATES_XYZ_BZ2 = resource_filename( + __name__, 'data/coordinates/test.xyz.bz2') + PSF = resource_filename(__name__, 'data/adk.psf') PSF_notop = resource_filename(__name__, 'data/adk_notop.psf') PSF_BAD = resource_filename(__name__, 'data/adk_notop_BAD.psf') diff --git a/testsuite/setup.py b/testsuite/setup.py index c7d311e0ffd..12fd19d71c0 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -128,8 +128,8 @@ 'data/dlpoly/CONFIG*', 'data/dlpoly/HISTORY*', 'data/*.xml', - 'coordinates/data/*xyz', - 'coordinates/data/*xyz.bz2', + 'data/coordinates/*xyz', + 'data/coordinates/*xyz.bz2', ], }, classifiers=CLASSIFIERS, From 63aa6e9d450c34b76a068b2b4df6ce339e564d2b Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Oct 2015 14:31:38 +0100 Subject: [PATCH 6/6] Add assert_timestep_almost_equal utility function to test the equality of timesteps with limited precisions, eq XTC and PDB. The new standard reader/writer tests now use the timestep equality method. This makes them actually a little easier and shorter. --- testsuite/MDAnalysisTests/coordinates/base.py | 131 +++++++++++------- 1 file changed, 84 insertions(+), 47 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index dd42c63da44..7737263c5f4 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -1,6 +1,7 @@ import MDAnalysis as mda import numpy as np from six.moves import zip +from MDAnalysis.coordinates.base import Timestep from numpy.testing import (assert_equal, assert_raises, assert_almost_equal, assert_array_almost_equal, raises) @@ -104,20 +105,38 @@ def __init__(self): self.n_frames = 5 # default for the numpy test functions self.prec = 6 - self.first_frame = np.arange(3 * self.n_atoms).reshape(self.n_atoms, 3) - self.second_frame = 2 ** 1 * self.first_frame - self.last_frame = 2 ** 4 * self.first_frame - self.jump_to_frame = 3 # second to last frame + + self.first_frame = Timestep(self.n_atoms) + self.first_frame.positions = np.arange( + 3 * self.n_atoms).reshape(self.n_atoms, 3) + self.first_frame.frame = 0 + + self.second_frame = self.first_frame.copy() + self.second_frame.positions = 2 ** 1 * self.first_frame.positions + self.second_frame.frame = 1 + + self.last_frame = self.first_frame.copy() + self.last_frame.positions = 2 ** 4 * self.first_frame.positions + self.last_frame.frame = self.n_frames - 1 + # remember frames are 0 indexed - self.jump_to_frame_positions = 2 ** 3 * self.first_frame - self.has_forces = False - self.has_velocities = False + self.jump_to_frame = self.first_frame.copy() + self.jump_to_frame.positions = 2 ** 3 * self.first_frame.positions + self.jump_to_frame.frame = 3 + self.dimensions = np.array([80, 80, 80, 60, 60, 90], dtype=np.float32) self.volume = mda.lib.mdamath.box_volume(self.dimensions) self.time = 0 self.dt = 1 self.totaltime = 5 + def iter_ts(self, i): + ts = self.first_frame.copy() + ts.positions = 2**i * self.first_frame.positions + ts.time = i + ts.frame = i + return ts + class BaseReaderTest(object): def __init__(self, reference): @@ -132,8 +151,8 @@ def test_n_frames(self): def test_first_frame(self): self.reader.rewind() - assert_array_almost_equal(self.reader.ts.positions, - self.ref.first_frame, decimal=self.ref.prec) + assert_timestep_almost_equal(self.reader.ts, self.ref.first_frame, + decimal=self.ref.prec) def test_double_close(self): self.reader.close() @@ -144,29 +163,28 @@ def test_reopen(self): self.reader.close() self.reader._reopen() ts = self.reader.next() - assert_array_almost_equal(ts.positions, self.ref.first_frame, - decimal=self.ref.prec) + assert_timestep_almost_equal(ts, self.ref.first_frame, + decimal=self.ref.prec) def test_last_frame(self): ts = self.reader[-1] - assert_array_almost_equal(ts.positions, self.ref.last_frame, - decimal=self.ref.prec) + assert_timestep_almost_equal(ts, self.ref.last_frame, + decimal=self.ref.prec) def test_next_gives_second_frame(self): reader = self.ref.reader(self.ref.trajectory) ts = reader.next() - assert_array_almost_equal(ts.positions, self.ref.second_frame, - decimal=self.ref.prec) + assert_timestep_almost_equal(ts, self.ref.second_frame, + decimal=self.ref.prec) @raises(IndexError) def test_go_over_last_frame(self): self.reader[self.ref.n_frames + 1] def test_frame_jump(self): - ts = self.reader[self.ref.jump_to_frame] - assert_array_almost_equal(ts.positions, - self.ref.jump_to_frame_positions, - decimal=self.ref.prec) + ts = self.reader[self.ref.jump_to_frame.frame] + assert_timestep_almost_equal(ts, self.ref.jump_to_frame, + decimal=self.ref.prec) def test_get_writer(self): with tempdir.in_tempdir(): @@ -182,25 +200,6 @@ def test_get_writer_2(self): assert_equal(isinstance(W, self.ref.writer), True) assert_equal(W.n_atoms, 100) - def test_has_velocities(self): - assert(self.reader.ts.has_velocities == self.ref.has_velocities) - - def test_velocities(self): - if self.ref.has_velocities: - ts = self.reader.rewind() - assert_array_almost_equal(ts.velocities, - self.ref.velocities, - self.ref.prec) - - def test_has_forces(self): - assert(self.reader.ts.has_forces == self.ref.has_forces) - - def test_forces(self): - if self.ref.has_forces: - ts = self.reader.rewind() - assert_array_almost_equal(ts.forces, self.ref.forces, - self.ref.prec) - def test_dt(self): assert_equal(self.reader.dt, self.ref.dt) @@ -219,10 +218,9 @@ def test_volume(self): def test_iter(self): for i, ts in enumerate(self.reader): - assert_array_almost_equal(ts.positions, - 2**i * self.ref.first_frame, - decimal=self.ref.prec) - assert_equal(i, ts.time) + assert_timestep_almost_equal(ts, self.ref.iter_ts(i), + decimal=self.ref.prec) + class BaseWriterTest(object): @@ -279,14 +277,53 @@ def _check_copy(self, fname): copy = self.ref.reader(fname) assert_equal(self.reader.n_frames, copy.n_frames) for orig_ts, copy_ts in zip(self.reader, copy): - assert_array_almost_equal( - copy_ts._pos, orig_ts._pos, self.ref.prec, - err_msg="coordinate mismatch between original and written " - "trajectory at frame {} (orig) vs {} (copy)".format( - orig_ts.frame, copy_ts.frame)) + assert_timestep_almost_equal( + copy_ts, orig_ts, decimal=self.ref.prec) @raises(mda.NoDataError) def test_write_none(self): outfile = self.tmp_file('write-none') with self.ref.writer(outfile) as w: w.write(None) + + +def assert_timestep_almost_equal(A, B, decimal=6, verbose=True): + if not isinstance(A, Timestep): + raise AssertionError('A is not of type Timestep') + if not isinstance(B, Timestep): + raise AssertionError('B is not of type Timestep') + + if A.frame != B.frame: + raise AssertionError('A and B refer to different frames: ' + 'A.frame = {}, B.frame={}'.format( + A.frame, B.frame)) + + if A.time != B.time: + raise AssertionError('A and B refer to different times: ' + 'A.time = {}, B.time={}'.format( + A.time, B.time)) + + if A.n_atoms != B.n_atoms: + raise AssertionError('A and B have a differnent number of atoms: ' + 'A.n_atoms = {}, B.n_atoms = {}'.format( + A.n_atoms, B.n_atoms)) + + assert_array_almost_equal(A.positions, B.positions, decimal=decimal, + err_msg='Timestep positions', verbose=verbose) + + if A.has_velocities != B.has_velocities: + raise AssertionError('Only one Timestep has velocities:' + 'A.has_velocities = {}, B.has_velocities'.format( + A.has_velocities, B.has_velocities)) + if A.has_velocities: + assert_array_almost_equal(A.velocities, B.velocities, decimal=decimal, + err_msg='Timestep velocities', + verbose=verbose) + + if A.has_forces != B.has_forces: + raise AssertionError('Only one Timestep has forces:' + 'A.has_forces = {}, B.has_forces'.format( + A.has_forces, B.has_forces)) + if A.has_forces: + assert_array_almost_equal(A.forces, B.forces, decimal=decimal, + err_msg='Timestep forces', verbose=verbose)