From ec70173c23c9521fb37f93fea3955fbded3e012c Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Thu, 22 Jun 2017 11:06:30 +0100 Subject: [PATCH] Added pytest-raises Converted test_units to use parametrize Some hacking of test_distances to use pytest features Completed moving test_distances to pytest style rewrote test_transformations into pytest style make scipy and matplotlib full dependencies (#1159) scipy and matplotlib are imported at top in analysis - updated all modules - removed any code that guards against scipy or matplotlib import - conforms to style guide https://github.com/MDAnalysis/mdanalysis/wiki/Style-Guide#module-imports-in-mdanalysisanalysis - fixes #1159 - fixes #1361 removed conditional skipping of tests when scipy or matplotlib are missing minor clean ups --- .travis.yml | 4 +- package/CHANGELOG | 3 + .../MDAnalysisTests/utils/test_distances.py | 776 ++++++++-------- .../utils/test_transformations.py | 848 ++++++++---------- testsuite/MDAnalysisTests/utils/test_units.py | 80 +- 5 files changed, 755 insertions(+), 956 deletions(-) diff --git a/.travis.yml b/.travis.yml index ecf2f848120..54adc1bbeef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST} ${PYTEST_FLAGS}; python ./testsuite/MDAnalysisTests/mda_nosetests ${NOSE_TEST_LIST} ${NOSE_FLAGS}" - SETUP_CMD="" - BUILD_CMD="pip install -v package/ && pip install testsuite/" - - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib scipy griddataformats pytest=3.1.2 pytest-cov=2.5.1" - - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy griddataformats seaborn coveralls clustalw=2.1 pytest=3.1.2 pytest-cov=2.5.1" + - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib scipy griddataformats pytest pytest-cov pytest-raises" + - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy griddataformats seaborn coveralls clustalw=2.1 pytest pytest-cov pytest-raises" # Install griddataformats from PIP so that scipy is only installed in the full build (#1147) - PIP_DEPENDENCIES='' - CONDA_CHANNELS='biobuilds conda-forge' diff --git a/package/CHANGELOG b/package/CHANGELOG index af8b4b3156d..39a58fb9929 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -48,6 +48,9 @@ Fixes * Fixed dtype of numpy arrays to accomodate 32 bit architectures (Issue #1362) * Groups are hashable on python 3 (Issue #1397) +Changes + * scipy and matplotlib are now required dependencies (Issue #1159) + Changes * scipy and matplotlib are now required dependencies (Issue #1159) diff --git a/testsuite/MDAnalysisTests/utils/test_distances.py b/testsuite/MDAnalysisTests/utils/test_distances.py index 0acef18c720..83708446c46 100644 --- a/testsuite/MDAnalysisTests/utils/test_distances.py +++ b/testsuite/MDAnalysisTests/utils/test_distances.py @@ -24,6 +24,7 @@ import MDAnalysis.lib.distances import numpy as np +import pytest from numpy.testing import (TestCase, dec, raises, assert_, assert_almost_equal, assert_equal, assert_raises,) @@ -33,43 +34,49 @@ from MDAnalysis.lib import mdamath from MDAnalysisTests import parser_not_found -class _TestDistanceArray(TestCase): - # override backend in test classes - - __test__ = False - - backend = None - - def setUp(self): - self.box = np.array([1., 1., 2.], dtype=np.float32) - self.points = np.array( - [ - [0, 0, 0], [1, 1, 2], [1, 0, 2], # identical under PBC - [0.5, 0.5, 1.5], - ], dtype=np.float32) - self.ref = self.points[0:1] - self.conf = self.points[1:] - - def _dist(self, n, ref=None): - if ref is None: - ref = self.ref[0] - else: - ref = np.asarray(ref, dtype=np.float32) - x = self.points[n] + +@pytest.fixture() +def ref_system(): + box = np.array([1., 1., 2.], dtype=np.float32) + points = np.array( + [ + [0, 0, 0], [1, 1, 2], [1, 0, 2], # identical under PBC + [0.5, 0.5, 1.5], + ], dtype=np.float32) + ref = points[0:1] + conf = points[1:] + + return box, points, ref, conf + + +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestDistanceArray(object): + @staticmethod + def _dist(x, ref): + ref = np.asarray(ref, dtype=np.float32) r = x - ref return np.sqrt(np.dot(r, r)) - def test_noPBC(self): - d = MDAnalysis.lib.distances.distance_array(self.ref, self.points, - backend=self.backend) - assert_almost_equal(d, np.array([[self._dist(0), self._dist(1), self._dist(2), self._dist(3)]])) + def test_noPBC(self, backend, ref_system): + box, points, ref, conf = ref_system + + d = MDAnalysis.lib.distances.distance_array(ref, points, backend=backend) + + assert_almost_equal(d, np.array([[ + self._dist(points[0], ref[0]), + self._dist(points[1], ref[0]), + self._dist(points[2], ref[0]), + self._dist(points[3], ref[0])] + ])) + + def test_PBC(self, backend, ref_system): + box, points, ref, conf = ref_system + + d = MDAnalysis.lib.distances.distance_array(ref, points, box=box, backend=backend) - def test_PBC(self): - d = MDAnalysis.lib.distances.distance_array(self.ref, self.points, - box=self.box, backend=self.backend) - assert_almost_equal(d, np.array([[0., 0., 0., self._dist(3, ref=[1, 1, 2])]])) + assert_almost_equal(d, np.array([[0., 0., 0., self._dist(points[3], ref=[1, 1, 2])]])) - def test_PBC2(self): + def test_PBC2(self, backend): a = np.array([7.90146923, -13.72858524, 3.75326586], dtype=np.float32) b = np.array([-1.36250901, 13.45423985, -0.36317623], dtype=np.float32) box = np.array([5.5457325, 5.5457325, 5.5457325], dtype=np.float32) @@ -80,85 +87,68 @@ def mindist(a, b, box): ref = mindist(a, b, box) val = MDAnalysis.lib.distances.distance_array(np.array([a]), np.array([b]), - box=box, backend=self.backend)[0, 0] + box=box, backend=backend)[0, 0] assert_almost_equal(val, ref, decimal=6, err_msg="Issue 151 not correct (PBC in distance array)") - -class TestDistanceArray_Serial(_TestDistanceArray): - __test__ = True - - backend = "serial" - - -class TestDistanceArray_OpenMP(_TestDistanceArray): - __test__ = True - - backend = "OpenMP" - - -class _TestDistanceArrayDCD(TestCase): - __test__ = False - - backend = None - - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def setUp(self): - self.universe = MDAnalysis.Universe(PSF, DCD) - self.trajectory = self.universe.trajectory - self.ca = self.universe.select_atoms('name CA') - # reasonable precision so that tests succeed on 32 and 64 bit machines - # (the reference values were obtained on 64 bit) - # Example: - # Items are not equal: wrong maximum distance value - # ACTUAL: 52.470254967456412 - # DESIRED: 52.470257062419059 - self.prec = 5 - - def tearDown(self): - del self.universe - del self.trajectory - del self.ca - - def test_simple(self): - U = self.universe - self.trajectory.rewind() +@pytest.fixture() +def DCD_Universe(): + universe = MDAnalysis.Universe(PSF, DCD) + trajectory = universe.trajectory + + return universe, trajectory + +@pytest.mark.skipif(parser_not_found('DCD'), + reason='DCD parser not available. Are you using python 3?') +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestDistanceArrayDCD(object): + # reasonable precision so that tests succeed on 32 and 64 bit machines + # (the reference values were obtained on 64 bit) + # Example: + # Items are not equal: wrong maximum distance value + # ACTUAL: 52.470254967456412 + # DESIRED: 52.470257062419059 + prec = 5 + + @attr('issue') + def test_simple(self, DCD_Universe, backend): + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions - self.trajectory[10] + trajectory[10] x1 = U.atoms.positions - d = MDAnalysis.lib.distances.distance_array(x0, x1, backend=self.backend) + d = MDAnalysis.lib.distances.distance_array(x0, x1, backend=backend) assert_equal(d.shape, (3341, 3341), "wrong shape (should be (Natoms,Natoms))") assert_almost_equal(d.min(), 0.11981228170520701, self.prec, err_msg="wrong minimum distance value") assert_almost_equal(d.max(), 53.572192429459619, self.prec, err_msg="wrong maximum distance value") - def test_outarray(self): - U = self.universe - self.trajectory.rewind() + def test_outarray(self, DCD_Universe, backend): + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions - self.trajectory[10] + trajectory[10] x1 = U.atoms.positions natoms = len(U.atoms) d = np.zeros((natoms, natoms), np.float64) - MDAnalysis.lib.distances.distance_array(x0, x1, result=d, backend=self.backend) + MDAnalysis.lib.distances.distance_array(x0, x1, result=d, backend=backend) assert_equal(d.shape, (natoms, natoms), "wrong shape, shoud be (Natoms,Natoms) entries") assert_almost_equal(d.min(), 0.11981228170520701, self.prec, err_msg="wrong minimum distance value") assert_almost_equal(d.max(), 53.572192429459619, self.prec, err_msg="wrong maximum distance value") - def test_periodic(self): + def test_periodic(self, DCD_Universe, backend): # boring with the current dcd as that has no PBC - U = self.universe - self.trajectory.rewind() + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions - self.trajectory[10] + trajectory[10] x1 = U.atoms.positions d = MDAnalysis.lib.distances.distance_array(x0, x1, box=U.coord.dimensions, - backend=self.backend) + backend=backend) assert_equal(d.shape, (3341, 3341), "should be square matrix with Natoms entries") assert_almost_equal(d.min(), 0.11981228170520701, self.prec, err_msg="wrong minimum distance value with PBC") @@ -166,42 +156,18 @@ def test_periodic(self): err_msg="wrong maximum distance value with PBC") -class TestDistanceArrayDCD_Serial(_TestDistanceArrayDCD): - __test__ = True - - backend = "serial" +@pytest.mark.skipif(parser_not_found('DCD'), + reason='DCD parser not available. Are you using python 3?') +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestSelfDistanceArrayDCD(object): + prec = 5 -class TestDistanceArrayDCD_OpenMP(_TestDistanceArrayDCD): - __test__ = True - - backend = "OpenMP" - - -class _TestSelfDistanceArrayDCD(TestCase): - __test__ = False - - backend = None - - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def setUp(self): - self.universe = MDAnalysis.Universe(PSF, DCD) - self.trajectory = self.universe.trajectory - self.ca = self.universe.select_atoms('name CA') - # see comments above on precision - self.prec = 5 - - def tearDown(self): - del self.universe - del self.trajectory - del self.ca - - def test_simple(self): - U = self.universe - self.trajectory.rewind() + def test_simple(self, DCD_Universe, backend): + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions - d = MDAnalysis.lib.distances.self_distance_array(x0, backend=self.backend) + d = MDAnalysis.lib.distances.self_distance_array(x0, backend=backend) N = 3341 * (3341 - 1) / 2 assert_equal(d.shape, (N,), "wrong shape (should be (Natoms*(Natoms-1)/2,))") assert_almost_equal(d.min(), 0.92905562402529318, self.prec, @@ -209,29 +175,29 @@ def test_simple(self): assert_almost_equal(d.max(), 52.4702570624190590, self.prec, err_msg="wrong maximum distance value") - def test_outarray(self): - U = self.universe - self.trajectory.rewind() + def test_outarray(self, DCD_Universe, backend): + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions natoms = len(U.atoms) N = natoms * (natoms - 1) // 2 d = np.zeros((N,), np.float64) - MDAnalysis.lib.distances.self_distance_array(x0, result=d, backend=self.backend) + MDAnalysis.lib.distances.self_distance_array(x0, result=d, backend=backend) assert_equal(d.shape, (N,), "wrong shape (should be (Natoms*(Natoms-1)/2,))") assert_almost_equal(d.min(), 0.92905562402529318, self.prec, err_msg="wrong minimum distance value") assert_almost_equal(d.max(), 52.4702570624190590, self.prec, err_msg="wrong maximum distance value") - def test_periodic(self): + def test_periodic(self, DCD_Universe, backend): # boring with the current dcd as that has no PBC - U = self.universe - self.trajectory.rewind() + U, trajectory = DCD_Universe + trajectory.rewind() x0 = U.atoms.positions natoms = len(U.atoms) N = natoms * (natoms - 1) / 2 d = MDAnalysis.lib.distances.self_distance_array(x0, box=U.coord.dimensions, - backend=self.backend) + backend=backend) assert_equal(d.shape, (N,), "wrong shape (should be (Natoms*(Natoms-1)/2,))") assert_almost_equal(d.min(), 0.92905562402529318, self.prec, err_msg="wrong minimum distance value with PBC") @@ -239,19 +205,8 @@ def test_periodic(self): err_msg="wrong maximum distance value with PBC") -class TestSelfDistanceArrayDCD_Serial(_TestSelfDistanceArrayDCD): - __test__ = True - - backend = "serial" - - -class TestSelfDistanceArrayDCD_OpenMP(_TestSelfDistanceArrayDCD): - __test__ = True - - backend = "OpenMP" - - -class _TestTriclinicDistances(TestCase): +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestTriclinicDistances(object): """Unit tests for the Triclinic PBC functions. Tests: # transforming to and from S space (fractional coords) @@ -262,69 +217,74 @@ class _TestTriclinicDistances(TestCase): mda.lib.distances.distance_array """ - __test__ = False + prec = 2 - backend = None + @staticmethod + @pytest.fixture() + def TRIC(): + return MDAnalysis.Universe(TRIC) - def setUp(self): - self.universe = MDAnalysis.Universe(TRIC) - self.prec = 2 + @staticmethod + @pytest.fixture() + def box(TRIC): + return MDAnalysis.coordinates.core.triclinic_vectors(TRIC.dimensions) - self.box = MDAnalysis.coordinates.core.triclinic_vectors(self.universe.dimensions) - self.boxV = MDAnalysis.coordinates.core.triclinic_box(self.box[0], self.box[1], self.box[2]) + @staticmethod + @pytest.fixture() + def boxV(box): + return MDAnalysis.coordinates.core.triclinic_box(box[0], box[1], box[2]) - self.S_mol1 = np.array([self.universe.atoms[383].position]) - self.S_mol2 = np.array([self.universe.atoms[390].position]) + @staticmethod + @pytest.fixture() + def S_mol(TRIC): + S_mol1 = np.array([TRIC.atoms[383].position]) + S_mol2 = np.array([TRIC.atoms[390].position]) - def tearDown(self): - del self.universe - del self.boxV - del self.box - del self.S_mol1 - del self.S_mol2 - del self.prec + return S_mol1, S_mol2 - def test_transforms(self): + def test_transforms(self, S_mol, box, boxV, backend): from MDAnalysis.lib.distances import transform_StoR, transform_RtoS # To check the cython coordinate transform, the same operation is done in numpy # Is a matrix multiplication of Coords x Box = NewCoords, so can use np.dot - + S_mol1, S_mol2 = S_mol # Test transformation - R_mol1 = transform_StoR(self.S_mol1, self.box, backend=self.backend) - R_np1 = np.dot(self.S_mol1, self.box) + R_mol1 = transform_StoR(S_mol1, box, backend=backend) + R_np1 = np.dot(S_mol1, box) # Test transformation when given box in different form - R_mol2 = transform_StoR(self.S_mol2, self.boxV, backend=self.backend) - R_np2 = np.dot(self.S_mol2, self.box) + R_mol2 = transform_StoR(S_mol2, boxV, backend=backend) + R_np2 = np.dot(S_mol2, box) assert_almost_equal(R_mol1, R_np1, self.prec, err_msg="StoR transform failed with box") assert_almost_equal(R_mol2, R_np2, self.prec, err_msg="StoR transform failed with boxV") # Round trip test # boxV here althought initial transform with box - S_test1 = transform_RtoS(R_mol1, self.boxV, backend=self.backend) + S_test1 = transform_RtoS(R_mol1, boxV, backend=backend) # and vice versa, should still work - S_test2 = transform_RtoS(R_mol2, self.box, backend=self.backend) + S_test2 = transform_RtoS(R_mol2, box, backend=backend) - assert_almost_equal(S_test1, self.S_mol1, self.prec, err_msg="Round trip failed in transform") - assert_almost_equal(S_test2, self.S_mol2, self.prec, err_msg="Round trip failed in transform") + assert_almost_equal(S_test1, S_mol1, self.prec, err_msg="Round trip failed in transform") + assert_almost_equal(S_test2, S_mol2, self.prec, err_msg="Round trip failed in transform") - def test_selfdist(self): + def test_selfdist(self, S_mol, box, boxV, backend): from MDAnalysis.lib.distances import self_distance_array from MDAnalysis.lib.distances import transform_StoR - R_coords = transform_StoR(self.S_mol1, self.box, backend=self.backend) + S_mol1, S_mol2 = S_mol + + R_coords = transform_StoR(S_mol1, box, backend=backend) # Transform functions are tested elsewhere so taken as working here - dists = self_distance_array(R_coords, box=self.box, backend=self.backend) + dists = self_distance_array(R_coords, box=box, backend=backend) # Manually calculate self_distance_array manual = np.zeros(len(dists), dtype=np.float64) distpos = 0 for i, Ri in enumerate(R_coords): for Rj in R_coords[i + 1:]: Rij = Rj - Ri - Rij -= round(Rij[2] / self.box[2][2]) * self.box[2] - Rij -= round(Rij[1] / self.box[1][1]) * self.box[1] - Rij -= round(Rij[0] / self.box[0][0]) * self.box[0] + Rij -= round(Rij[2] / box[2][2]) * box[2] + Rij -= round(Rij[1] / box[1][1]) * box[1] + Rij -= round(Rij[0] / box[0][0]) * box[0] Rij = np.linalg.norm(Rij) # find norm of Rij vector manual[distpos] = Rij # and done, phew distpos += 1 @@ -334,18 +294,18 @@ def test_selfdist(self): # Do it again for input 2 (has wider separation in points) # Also use boxV here in self_dist calculation - R_coords = transform_StoR(self.S_mol2, self.box, backend=self.backend) + R_coords = transform_StoR(S_mol2, box, backend=backend) # Transform functions are tested elsewhere so taken as working here - dists = self_distance_array(R_coords, box=self.boxV, backend=self.backend) + dists = self_distance_array(R_coords, box=boxV, backend=backend) # Manually calculate self_distance_array manual = np.zeros(len(dists), dtype=np.float64) distpos = 0 for i, Ri in enumerate(R_coords): for Rj in R_coords[i + 1:]: Rij = Rj - Ri - Rij -= round(Rij[2] / self.box[2][2]) * self.box[2] - Rij -= round(Rij[1] / self.box[1][1]) * self.box[1] - Rij -= round(Rij[0] / self.box[0][0]) * self.box[0] + Rij -= round(Rij[2] / box[2][2]) * box[2] + Rij -= round(Rij[1] / box[1][1]) * box[1] + Rij -= round(Rij[0] / box[0][0]) * box[0] Rij = np.linalg.norm(Rij) # find norm of Rij vector manual[distpos] = Rij # and done, phew distpos += 1 @@ -353,23 +313,25 @@ def test_selfdist(self): assert_almost_equal(dists, manual, self.prec, err_msg="self_distance_array failed with input 2") - def test_distarray(self): + def test_distarray(self, S_mol, box, boxV, backend): from MDAnalysis.lib.distances import distance_array from MDAnalysis.lib.distances import transform_StoR - R_mol1 = transform_StoR(self.S_mol1, self.box, backend=self.backend) - R_mol2 = transform_StoR(self.S_mol2, self.box, backend=self.backend) + S_mol1, S_mol2 = S_mol + + R_mol1 = transform_StoR(S_mol1, box, backend=backend) + R_mol2 = transform_StoR(S_mol2, box, backend=backend) # Try with box - dists = distance_array(R_mol1, R_mol2, box=self.box, backend=self.backend) + dists = distance_array(R_mol1, R_mol2, box=box, backend=backend) # Manually calculate distance_array manual = np.zeros((len(R_mol1), len(R_mol2))) for i, Ri in enumerate(R_mol1): for j, Rj in enumerate(R_mol2): Rij = Rj - Ri - Rij -= round(Rij[2] / self.box[2][2]) * self.box[2] - Rij -= round(Rij[1] / self.box[1][1]) * self.box[1] - Rij -= round(Rij[0] / self.box[0][0]) * self.box[0] + Rij -= round(Rij[2] / box[2][2]) * box[2] + Rij -= round(Rij[1] / box[1][1]) * box[1] + Rij -= round(Rij[0] / box[0][0]) * box[0] Rij = np.linalg.norm(Rij) # find norm of Rij vector manual[i][j] = Rij @@ -377,75 +339,73 @@ def test_distarray(self): err_msg="distance_array failed with box") # Now check using boxV - dists = distance_array(R_mol1, R_mol2, box=self.boxV, backend=self.backend) + dists = distance_array(R_mol1, R_mol2, box=boxV, backend=backend) assert_almost_equal(dists, manual, self.prec, err_msg="distance_array failed with boxV") - def test_pbc_dist(self): + def test_pbc_dist(self, S_mol, boxV, backend): from MDAnalysis.lib.distances import distance_array + S_mol1, S_mol2 = S_mol results = np.array([[37.629944]]) - - dists = distance_array(self.S_mol1, self.S_mol2, box=self.boxV, - backend=self.backend) + dists = distance_array(S_mol1, S_mol2, box=boxV, + backend=backend) assert_almost_equal(dists, results, self.prec, err_msg="distance_array failed to retrieve PBC distance") -class TestTriclinicDistances_Serial(_TestTriclinicDistances): - __test__ = True - - backend = "serial" - - -class TestTriclinicDistances_OpenMP(_TestTriclinicDistances): - __test__ = True - backend = "OpenMP" - - -class _TestCythonFunctions(TestCase): +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestCythonFunctions(object): # Unit tests for calc_bonds calc_angles and calc_dihedrals in lib.distances # Tests both numerical results as well as input types as Cython will silently # produce nonsensical results if given wrong data types otherwise. + prec = 5 - __test__ = False + @staticmethod + @pytest.fixture() + def box(): + return np.array([10., 10., 10.], dtype=np.float32) - backend = None + @staticmethod + @pytest.fixture() + def triclinic_box(): + return np.array([[10., 0., 0.], [1., 10., 0., ], [1., 0., 10.]], dtype=np.float32) - def setUp(self): - self.prec = 5 - self.box = np.array([10., 10., 10.], dtype=np.float32) - self.box2 = np.array([[10., 0., 0.], [1., 10., 0., ], [1., 0., 10.]], dtype=np.float32) + @staticmethod + @pytest.fixture() + def positions(): # dummy atom data - self.a = np.array([[0., 0., 0.], [0., 0., 0.], [0., 11., 0.], [1., 1., 1.]], dtype=np.float32) - self.b = np.array([[0., 0., 0.], [1., 1., 1.], [0., 0., 0.], [29., -21., 99.]], dtype=np.float32) - self.c = np.array([[0., 0., 0.], [2., 2., 2.], [11., 0., 0.], [1., 9., 9.]], dtype=np.float32) - self.d = np.array([[0., 0., 0.], [3., 3., 3.], [11., -11., 0.], [65., -65., 65.]], dtype=np.float32) - self.wrongtype = np.array([[0., 0., 0.], [3., 3., 3.], [3., 3., 3.], [3., 3., 3.]], - dtype=np.float64) # declared as float64 and should raise TypeError - self.wronglength = np.array([[0., 0., 0.], [3., 3., 3.]], - dtype=np.float32) # has a different length to other inputs and should raise - # ValueError - - def tearDown(self): - del self.box - del self.box2 - del self.a - del self.b - del self.c - del self.d - del self.wrongtype - del self.wronglength - - def test_bonds(self): - dists = MDAnalysis.lib.distances.calc_bonds(self.a, self.b, - backend=self.backend) + + a = np.array([[0., 0., 0.], [0., 0., 0.], [0., 11., 0.], [1., 1., 1.]], dtype=np.float32) + b = np.array([[0., 0., 0.], [1., 1., 1.], [0., 0., 0.], [29., -21., 99.]], dtype=np.float32) + c = np.array([[0., 0., 0.], [2., 2., 2.], [11., 0., 0.], [1., 9., 9.]], dtype=np.float32) + d = np.array([[0., 0., 0.], [3., 3., 3.], [11., -11., 0.], [65., -65., 65.]], dtype=np.float32) + return a, b, c, d + + @staticmethod + @pytest.fixture() + def wrongtype(): + # declared as float64 and should raise TypeError + return np.array([[0., 0., 0.], [3., 3., 3.], [3., 3., 3.], [3., 3., 3.]], + dtype=np.float64) + + @staticmethod + @pytest.fixture() + def wronglength(): + # has a different length to other inputs and should raise ValueError + return np.array([[0., 0., 0.], [3., 3., 3.]], + dtype=np.float32) + + def test_bonds(self, positions, box, backend): + a, b, c, d = positions + dists = MDAnalysis.lib.distances.calc_bonds(a, b, + backend=backend) assert_equal(len(dists), 4, err_msg="calc_bonds results have wrong length") - dists_pbc = MDAnalysis.lib.distances.calc_bonds(self.a, self.b, box=self.box, - backend=self.backend) - # tests 0 length + dists_pbc = MDAnalysis.lib.distances.calc_bonds(a, b, box=box, + backend=backend) + #tests 0 length assert_almost_equal(dists[0], 0.0, self.prec, err_msg="Zero length calc_bonds fail") assert_almost_equal(dists[1], 1.7320508075688772, self.prec, err_msg="Standard length calc_bonds fail") # arbitrary length check @@ -458,41 +418,46 @@ def test_bonds(self): err_msg="PBC check #2 w/o box") # lengths in all directions assert_almost_equal(dists_pbc[3], 3.46410072, self.prec, err_msg="PBC check #w with box") - # Bad input checking - def test_bonds_wrongtype(self): - assert_raises(TypeError, MDAnalysis.lib.distances.calc_bonds, self.a, - self.wrongtype, backend=self.backend) + #Bad input checking + def test_bonds_wrongtype(self, positions, wrongtype, wronglength, backend): + a, b, c, d = positions + assert_raises(TypeError, MDAnalysis.lib.distances.calc_bonds, a, + wrongtype, backend=backend) assert_raises(TypeError, MDAnalysis.lib.distances.calc_bonds, - self.wrongtype, self.b, backend=self.backend) - assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, self.a, - self.wronglength, backend=self.backend) + wrongtype, b, backend=backend) + assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, a, + wronglength, backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, - self.wronglength, self.b, backend=self.backend) + wronglength, b, backend=backend) - def test_bonds_badbox(self): + def test_bonds_badbox(self, positions, backend): + a, b, c, d = positions badboxtype = np.array([10., 10., 10.], dtype=np.float64) badboxsize = np.array([[10., 10.], [10., 10., ]], dtype=np.float32) - assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, self.a, - self.b, box=badboxsize, backend=self.backend) # Bad box data - assert_raises(TypeError, MDAnalysis.lib.distances.calc_bonds, self.a, - self.b, box=badboxtype, backend=self.backend) # Bad box type + assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, a, + b, box=badboxsize, backend=backend) # Bad box data + assert_raises(TypeError, MDAnalysis.lib.distances.calc_bonds, a, + b, box=badboxtype, backend=backend) # Bad box type - def test_bonds_badresult(self): - badresult = np.zeros(len(self.a) - 1) + def test_bonds_badresult(self, positions, backend): + a, b, c, d = positions + badresult = np.zeros(len(a) - 1) assert_raises(ValueError, MDAnalysis.lib.distances.calc_bonds, - self.a, self.b, result=badresult, backend=self.backend) # Bad result array + a, b, result=badresult, backend=backend) # Bad result array - def test_bonds_triclinic(self): - dists = MDAnalysis.lib.distances.calc_bonds(self.a, self.b, - box=self.box2, backend=self.backend) + def test_bonds_triclinic(self, positions, triclinic_box, backend): + a, b, c, d = positions + dists = MDAnalysis.lib.distances.calc_bonds(a, b, + box=triclinic_box, backend=backend) reference = np.array([0.0, 1.7320508, 1.4142136, 2.82842712]) assert_almost_equal(dists, reference, self.prec, err_msg="calc_bonds with triclinic box failed") - def test_angles(self): - angles = MDAnalysis.lib.distances.calc_angles(self.a, self.b, self.c, - backend=self.backend) + def test_angles(self, positions, backend): + a, b, c, d = positions + angles = MDAnalysis.lib.distances.calc_angles(a, b, c, + backend=backend) # Check calculated values assert_equal(len(angles), 4, err_msg="calc_angles results have wrong length") # assert_almost_equal(angles[0], 0.0, self.prec, @@ -505,32 +470,35 @@ def test_angles(self): err_msg="Small angle failed in calc_angles") # Check data type checks - def test_angles_wrongtype(self): + def test_angles_wrongtype(self, positions, wrongtype, wronglength, backend): + a, b, c, d = positions assert_raises(TypeError, MDAnalysis.lib.distances.calc_angles, - self.a, self.wrongtype, self.c, backend=self.backend) # try inputting float64 values + a, wrongtype, c, backend=backend) # try inputting float64 values assert_raises(TypeError, MDAnalysis.lib.distances.calc_angles, - self.wrongtype, self.b, self.c, backend=self.backend) + wrongtype, b, c, backend=backend) assert_raises(TypeError, MDAnalysis.lib.distances.calc_angles, - self.a, self.b, self.wrongtype, backend=self.backend) + a, b, wrongtype, backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_angles, - self.a, self.wronglength, self.c, - backend=self.backend) # try inputting arrays of different length + a, wronglength, c, + backend=backend) # try inputting arrays of different length assert_raises(ValueError, MDAnalysis.lib.distances.calc_angles, - self.wronglength, self.b, self.c, - backend=self.backend) + wronglength, b, c, + backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_angles, - self.a, self.b, self.wronglength, - backend=self.backend) + a, b, wronglength, + backend=backend) - def test_angles_bad_result(self): - badresult = np.zeros(len(self.a) - 1) + def test_angles_bad_result(self, positions, backend): + a, b, c, d = positions + badresult = np.zeros(len(a) - 1) assert_raises(ValueError, MDAnalysis.lib.distances.calc_angles, - self.a, self.b, self.c, result=badresult, backend=self.backend) # Bad result array + a, b, c, result=badresult, backend=backend) # Bad result array - def test_dihedrals(self): - dihedrals = MDAnalysis.lib.distances.calc_dihedrals(self.a, self.b, - self.c, self.d, - backend=self.backend) + def test_dihedrals(self, positions, backend): + a, b, c, d = positions + dihedrals = MDAnalysis.lib.distances.calc_dihedrals(a, b, + c, d, + backend=backend) # Check calculated values assert_equal(len(dihedrals), 4, err_msg="calc_dihedrals results have wrong length") # assert_almost_equal(dihedrals[0], 0.0, self.prec, err_msg="Zero length dihedral failed") @@ -540,58 +508,62 @@ def test_dihedrals(self): err_msg="arbitrary dihedral angle failed") # Check data type checks - def test_dihedrals_wrongtype(self): + def test_dihedrals_wrongtype(self, positions, wrongtype, backend): + a, b, c, d = positions assert_raises(TypeError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.wrongtype, self.c, self.d, - backend=self.backend) # try inputting float64 values + a, wrongtype, c, d, + backend=backend) # try inputting float64 values assert_raises(TypeError, MDAnalysis.lib.distances.calc_dihedrals, - self.wrongtype, self.b, self.c, self.d, - backend=self.backend) + wrongtype, b, c, d, + backend=backend) assert_raises(TypeError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.b, self.wrongtype, self.d, - backend=self.backend) + a, b, wrongtype, d, + backend=backend) assert_raises(TypeError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.b, self.c, self.wrongtype, - backend=self.backend) + a, b, c, wrongtype, + backend=backend) - def test_dihedrals_wronglength(self): + def test_dihedrals_wronglength(self, positions, wronglength, backend): + a, b, c, d = positions assert_raises(ValueError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.wronglength, self.c, self.d, - backend=self.backend) + a, wronglength, c, d, + backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_dihedrals, - self.wronglength, self.b, self.c, self.d, - backend=self.backend) + wronglength, b, c, d, + backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.b, self.wronglength, self.d, - backend=self.backend) + a, b, wronglength, d, + backend=backend) assert_raises(ValueError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.b, self.c, self.wronglength, - backend=self.backend) + a, b, c, wronglength, + backend=backend) - def test_dihedrals_bad_result(self): - badresult = np.zeros(len(self.a) - 1) + def test_dihedrals_bad_result(self, positions, backend): + a, b, c, d = positions + badresult = np.zeros(len(a) - 1) assert_raises(ValueError, MDAnalysis.lib.distances.calc_dihedrals, - self.a, self.b, self.c, self.d, result=badresult, - backend=self.backend) # Bad result array + a, b, c, d, result=badresult, + backend=backend) # Bad result array - def test_numpy_compliance(self): + def test_numpy_compliance(self, positions, backend): + a, b, c, d = positions # Checks that the cython functions give identical results to the numpy versions - bonds = MDAnalysis.lib.distances.calc_bonds(self.a, self.b, - backend=self.backend) - angles = MDAnalysis.lib.distances.calc_angles(self.a, self.b, self.c, - backend=self.backend) - dihedrals = MDAnalysis.lib.distances.calc_dihedrals(self.a, self.b, - self.c, self.d, - backend=self.backend) - - bonds_numpy = np.array([mdamath.norm(y - x) for x, y in zip(self.a, self.b)]) - vec1 = self.a - self.b - vec2 = self.c - self.b + bonds = MDAnalysis.lib.distances.calc_bonds(a, b, + backend=backend) + angles = MDAnalysis.lib.distances.calc_angles(a, b, c, + backend=backend) + dihedrals = MDAnalysis.lib.distances.calc_dihedrals(a, b, + c, d, + backend=backend) + + bonds_numpy = np.array([mdamath.norm(y - x) for x, y in zip(a, b)]) + vec1 = a - b + vec2 = c - b angles_numpy = np.array([mdamath.angle(x, y) for x, y in zip(vec1, vec2)]) - ab = self.b - self.a - bc = self.c - self.b - cd = self.d - self.c + ab = b - a + bc = c - b + cd = d - c dihedrals_numpy = np.array([mdamath.dihedral(x, y, z) for x, y, z in zip(ab, bc, cd)]) assert_almost_equal(bonds, bonds_numpy, self.prec, @@ -604,32 +576,13 @@ def test_numpy_compliance(self): err_msg="Cython dihedrals didn't match numpy calculations") -class TestCythonFunctions_Serial(_TestCythonFunctions): - __test__ = True - - backend = "serial" - - -class TestCythonFunctions_OpenMP(_TestCythonFunctions): - __test__ = True - - backend = "OpenMP" - - -class _Test_apply_PBC(TestCase): - __test__ = False - - backend = None - - def setUp(self): - self.prec = 6 - - def tearDown(self): - del self.prec +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class Test_apply_PBC(object): + prec = 6 - @dec.skipif(parser_not_found('DCD'), - 'DCD parser not available. Are you using python 3?') - def test_ortho_PBC(self): + @pytest.mark.skipif(parser_not_found('DCD'), + reason='DCD parser not available. Are you using python 3?') + def test_ortho_PBC(self, backend): from MDAnalysis.lib.distances import apply_PBC U = MDAnalysis.Universe(PSF, DCD) @@ -637,8 +590,8 @@ def test_ortho_PBC(self): box1 = np.array([2.5, 2.5, 3.5], dtype=np.float32) box2 = np.array([2.5, 2.5, 3.5, 90., 90., 90.], dtype=np.float32) - cyth1 = apply_PBC(atoms, box1, backend=self.backend) - cyth2 = apply_PBC(atoms, box2, backend=self.backend) + cyth1 = apply_PBC(atoms, box1, backend=backend) + cyth2 = apply_PBC(atoms, box2, backend=backend) reference = atoms - np.floor(atoms / box1) * box1 assert_almost_equal(cyth1, reference, self.prec, @@ -646,7 +599,7 @@ def test_ortho_PBC(self): assert_almost_equal(cyth2, reference, self.prec, err_msg="Ortho apply_PBC #2 failed comparison with np") - def test_tric_PBC(self): + def test_tric_PBC(self, backend): from MDAnalysis.lib.distances import apply_PBC U = MDAnalysis.Universe(TRIC) @@ -664,8 +617,8 @@ def numpy_PBC(coords, box): return coords - cyth1 = apply_PBC(atoms, box1, backend=self.backend) - cyth2 = apply_PBC(atoms, box2, backend=self.backend) + cyth1 = apply_PBC(atoms, box1, backend=backend) + cyth2 = apply_PBC(atoms, box2, backend=backend) reference = numpy_PBC(atoms, box2) assert_almost_equal(cyth1, reference, self.prec, @@ -674,120 +627,91 @@ def numpy_PBC(coords, box): err_msg="Trlclinic apply_PBC failed comparison with np") -class _Test_apply_PBC_Serial(_Test_apply_PBC): - __test__ = True - - backend = "serial" - - -class _Test_apply_PBC_OpenMP(_Test_apply_PBC): - __test__ = True - - backend = "OpenMP" - - -class _TestPeriodicAngles(TestCase): +@pytest.mark.parametrize('backend', ['serial', 'openmp']) +class TestPeriodicAngles(object): """Test case for properly considering minimum image convention when calculating angles and dihedrals (Issue 172) """ - - __test__ = False - - def setUp(self): - self.prec = 5 - self.a = np.array([[0.0, 1.0, 0.0]], dtype=np.float32) - self.b = np.array([[0.0, 0.0, 0.0]], dtype=np.float32) - self.c = np.array([[1.0, 0.0, 0.0]], dtype=np.float32) - self.d = np.array([[1.0, 0.0, 1.0]], dtype=np.float32) - self.box = np.array([10.0, 10.0, 10.0], dtype=np.float32) - - def tearDown(self): - del self.prec - del self.a - del self.b - del self.c - del self.d - del self.box - - def test_angles(self): + @staticmethod + @pytest.fixture() + def positions(): + a = np.array([[0.0, 1.0, 0.0]], dtype=np.float32) + b = np.array([[0.0, 0.0, 0.0]], dtype=np.float32) + c = np.array([[1.0, 0.0, 0.0]], dtype=np.float32) + d = np.array([[1.0, 0.0, 1.0]], dtype=np.float32) + box = np.array([10.0, 10.0, 10.0], dtype=np.float32) + return a, b, c, d, box + + prec = 5 + + def test_angles(self, positions, backend): from MDAnalysis.lib.distances import calc_angles # Shift atom coordinates a few box lengths in random directions and see if we still get same results - a2 = (self.a + self.box * (-1, 0, 0)).astype(np.float32) # seem to get converted to float64 otherwise - b2 = (self.b + self.box * (1, 0, 1)).astype(np.float32) - c2 = (self.c + self.box * (-2, 5, -7)).astype(np.float32) + a, b, c, d, box = positions + a2 = (a + box * (-1, 0, 0)).astype(np.float32) # seem to get converted to float64 otherwise + b2 = (b + box * (1, 0, 1)).astype(np.float32) + c2 = (c + box * (-2, 5, -7)).astype(np.float32) - ref = calc_angles(self.a, self.b, self.c, backend=self.backend) + ref = calc_angles(a, b, c, backend=backend) - test1 = calc_angles(a2, self.b, self.c, box=self.box, backend=self.backend) - test2 = calc_angles(self.a, b2, self.c, box=self.box, backend=self.backend) - test3 = calc_angles(self.a, self.b, c2, box=self.box, backend=self.backend) - test4 = calc_angles(a2, b2, c2, box=self.box, backend=self.backend) + test1 = calc_angles(a2, b, c, box=box, backend=backend) + test2 = calc_angles(a, b2, c, box=box, backend=backend) + test3 = calc_angles(a, b, c2, box=box, backend=backend) + test4 = calc_angles(a2, b2, c2, box=box, backend=backend) for val in [test1, test2, test3, test4]: assert_almost_equal(ref, val, self.prec, err_msg="Min image in angle calculation failed") - def test_dihedrals(self): + def test_dihedrals(self, positions, backend): from MDAnalysis.lib.distances import calc_dihedrals - - a2 = (self.a + self.box * (-1, 0, 0)).astype(np.float32) - b2 = (self.b + self.box * (1, 0, 1)).astype(np.float32) - c2 = (self.c + self.box * (-2, 5, -7)).astype(np.float32) - d2 = (self.d + self.box * (0, -5, 0)).astype(np.float32) - - ref = calc_dihedrals(self.a, self.b, self.c, self.d, backend=self.backend) - - test1 = calc_dihedrals(a2, self.b, self.c, self.d, box=self.box, - backend=self.backend) - test2 = calc_dihedrals(self.a, b2, self.c, self.d, box=self.box, - backend=self.backend) - test3 = calc_dihedrals(self.a, self.b, c2, self.d, box=self.box, - backend=self.backend) - test4 = calc_dihedrals(self.a, self.b, self.c, d2, box=self.box, - backend=self.backend) - test5 = calc_dihedrals(a2, b2, c2, d2, box=self.box, - backend=self.backend) + a, b, c, d, box = positions + a2 = (a + box * (-1, 0, 0)).astype(np.float32) + b2 = (b + box * (1, 0, 1)).astype(np.float32) + c2 = (c + box * (-2, 5, -7)).astype(np.float32) + d2 = (d + box * (0, -5, 0)).astype(np.float32) + + ref = calc_dihedrals(a, b, c, d, backend=backend) + + test1 = calc_dihedrals(a2, b, c, d, box=box, + backend=backend) + test2 = calc_dihedrals(a, b2, c, d, box=box, + backend=backend) + test3 = calc_dihedrals(a, b, c2, d, box=box, + backend=backend) + test4 = calc_dihedrals(a, b, c, d2, box=box, + backend=backend) + test5 = calc_dihedrals(a2, b2, c2, d2, box=box, + backend=backend) for val in [test1, test2, test3, test4, test5]: assert_almost_equal(ref, val, self.prec, err_msg="Min image in dihedral calculation failed") -class TestPeriodicAngles_Serial(_TestPeriodicAngles): - __test__ = True - - backend = "serial" - -class TestPeriodicAngles_OpenMP(_TestPeriodicAngles): - __test__ = True +class TestDistanceBackendSelection(object): + @staticmethod + @pytest.fixture() + def backend_selection_pos(): + positions = np.random.rand(10, 3) + N = positions.shape[0] + result = np.empty(N * (N - 1) // 2, dtype=np.float64) - backend = "OpenMP" + return positions, result - -class TestDistanceBackendSelection(TestCase): - def setUp(self): - self.positions = np.random.rand(10, 3) - N = self.positions.shape[0] - self.result = np.empty(N * (N - 1) // 2, dtype=np.float64) - - def _case_insensitivity_test(self, backend): + @pytest.mark.parametrize('backend', [ + "serial", "Serial", "SeRiAL", "SERIAL", + "openmp", "OpenMP", "oPENmP", "OPENMP", + pytest.mark.raises('not_implemented_stuff', exception=ValueError), + ]) + def test_case_insensitivity(self, backend, backend_selection_pos): + positions, result = backend_selection_pos try: MDAnalysis.lib.distances._run("calc_self_distance_array", - args=(self.positions, self.result), + args=(positions, result), backend=backend) except RuntimeError: raise AssertionError("Failed to understand backend {0}".format(backend)) - def test_case_insensitivity(self): - for backend in ("serial", "Serial", "SeRiAL", "SERIAL", - "openmp", "OpenMP", "oPENmP", "OPENMP"): - yield self._case_insensitivity_test, backend - - @raises(ValueError) - def test_missing_backend_raises_ValueError(self): - MDAnalysis.lib.distances._run("calc_self_distance_array", - args=(self.positions, self.result), - backend="not_implemented_stuff") - def test_used_openmpflag(): assert_(isinstance(MDAnalysis.lib.distances.USED_OPENMP, bool)) diff --git a/testsuite/MDAnalysisTests/utils/test_transformations.py b/testsuite/MDAnalysisTests/utils/test_transformations.py index b834c0fb633..14c6af04489 100644 --- a/testsuite/MDAnalysisTests/utils/test_transformations.py +++ b/testsuite/MDAnalysisTests/utils/test_transformations.py @@ -25,7 +25,7 @@ from itertools import permutations import numpy as np -import unittest +import pytest from numpy.testing import (assert_allclose, assert_equal, assert_almost_equal, assert_array_equal) @@ -38,29 +38,8 @@ Testing transformations is weird because there are 2 versions of many of these functions. This is because both python and Cython versions of these functions exist. To test therefore, each test has to be done twice, -once for each backend. - -The general pattern for this is, - -1) Create tests which call self.f (the function) - -2) Create mixins which define self.f (one of the two backends) - -Eg: - -class _ClipMatrix(object): - def test_this(self): - result = self.f(stuff) - assert_awesome(me) - -class TestClipMatrixNP(_ClipMatrix): - f = staticmethod(MDAnalysis.lib.transformations._py_clip_matrix) - -class TestClipMatrixCY(_ClipMatrix): - f = staticmethod(MDAnalysis.lib.transformations.clip_matrix) - -Note that the function to be tested needs to be defined as a static method! - +once for each backend. This is done through parametrizing and passing +in both versions of the function as an argument. This should ensure that both versions work and are covered! """ @@ -69,34 +48,24 @@ class TestClipMatrixCY(_ClipMatrix): _ATOL = 1e-06 -class _IdentityMatrix(object): - def test_identity_matrix(self): - I = self.f() - assert_allclose(I, np.dot(I, I)) - assert_equal(np.sum(I), np.trace(I)) - assert_allclose(I, np.identity(4, dtype=np.float64)) - - -class TestIdentityMatrixNP(_IdentityMatrix): - f = staticmethod(t._py_identity_matrix) - - -class TestIdentityMatrixCy(_IdentityMatrix): - f = staticmethod(t.identity_matrix) - - -class _TranslationMatrix(object): - def test_translation_matrix(self): - v = np.array([0.2, 0.2, 0.2]) - assert_allclose(v, self.f(v)[:3, 3]) - - -class TestTranslationMatrixNP(_TranslationMatrix): - f = staticmethod(t._py_translation_matrix) +@pytest.mark.parametrize('f', [ + t._py_identity_matrix, + t.identity_matrix +]) +def test_identity_matrix(f): + I = f() + assert_allclose(I, np.dot(I, I)) + assert_equal(np.sum(I), np.trace(I)) + assert_allclose(I, np.identity(4, dtype=np.float64)) -class TestTranslationMatrixCy(_TranslationMatrix): - f = staticmethod(t.translation_matrix) +@pytest.mark.parametrize('f', [ + t._py_translation_matrix, + t.translation_matrix, +]) +def test_translation_matrix(f): + v = np.array([0.2, 0.2, 0.2]) + assert_allclose(v, f(v)[:3, 3]) def test_translation_from_matrix(): @@ -106,26 +75,21 @@ def test_translation_from_matrix(): assert_allclose(v0, v1) -class _ReflectionMatrix(object): - def test_reflection_matrix(self): - v0 = np.array([0.2, 0.2, 0.2, 1.0]) # arbitrary values - v1 = np.array([0.4, 0.4, 0.4]) - R = self.f(v0, v1) - assert_allclose(2., np.trace(R)) - assert_allclose(v0, np.dot(R, v0)) - v2 = v0.copy() - v2[:3] += v1 - v3 = v0.copy() - v2[:3] -= v1 - assert_allclose(v2, np.dot(R, v3)) - - -class TestReflectionMatrixNP(_ReflectionMatrix): - f = staticmethod(t._py_reflection_matrix) - - -class TestReflectionMatrixCy(_ReflectionMatrix): - f = staticmethod(t.reflection_matrix) +@pytest.mark.parametrize('f', [ + t._py_reflection_matrix, + t.reflection_matrix, +]) +def test_reflection_matrix(f): + v0 = np.array([0.2, 0.2, 0.2, 1.0]) # arbitrary values + v1 = np.array([0.4, 0.4, 0.4]) + R = f(v0, v1) + assert_allclose(2., np.trace(R)) + assert_allclose(v0, np.dot(R, v0)) + v2 = v0.copy() + v2[:3] += v1 + v3 = v0.copy() + v2[:3] -= v1 + assert_allclose(v2, np.dot(R, v3)) def test_reflection_from_matrix(): @@ -137,30 +101,25 @@ def test_reflection_from_matrix(): assert_equal(t.is_same_transform(M0, M1), True) -class _RotationMatrix(object): - def test_rotation_matrix(self): - R = self.f(np.pi / 2.0, [0, 0, 1], [1, 0, 0]) - assert_allclose(np.dot(R, [0, 0, 0, 1]), [1., -1., 0., 1.]) - angle = 0.2 * 2 * np.pi # arbitrary value - direc = np.array([0.2, 0.2, 0.2]) - point = np.array([0.4, 0.4, 0.4]) - R0 = self.f(angle, direc, point) - R1 = self.f(angle - 2 * np.pi, direc, point) - assert_equal(t.is_same_transform(R0, R1), True) - R0 = self.f(angle, direc, point) - R1 = self.f(-angle, -direc, point) - assert_equal(t.is_same_transform(R0, R1), True) - I = np.identity(4, np.float64) - assert_allclose(I, self.f(np.pi * 2, direc), atol=_ATOL) - assert_allclose(2., np.trace(self.f(np.pi / 2, direc, point))) - - -class TestRotationMatrixNP(_RotationMatrix): - f = staticmethod(t._py_rotation_matrix) - - -class TestRotationMatrixCy(_RotationMatrix): - f = staticmethod(t.rotation_matrix) +@pytest.mark.parametrize('f', [ + t._py_rotation_matrix, + t.rotation_matrix, +]) +def test_rotation_matrix(f): + R = f(np.pi / 2.0, [0, 0, 1], [1, 0, 0]) + assert_allclose(np.dot(R, [0, 0, 0, 1]), [1., -1., 0., 1.]) + angle = 0.2 * 2 * np.pi # arbitrary value + direc = np.array([0.2, 0.2, 0.2]) + point = np.array([0.4, 0.4, 0.4]) + R0 = f(angle, direc, point) + R1 = f(angle - 2 * np.pi, direc, point) + assert_equal(t.is_same_transform(R0, R1), True) + R0 = f(angle, direc, point) + R1 = f(-angle, -direc, point) + assert_equal(t.is_same_transform(R0, R1), True) + I = np.identity(4, np.float64) + assert_allclose(I, f(np.pi * 2, direc), atol=_ATOL) + assert_allclose(2., np.trace(f(np.pi / 2, direc, point))) def test_rotation_from_matrix(): @@ -173,19 +132,14 @@ def test_rotation_from_matrix(): assert_equal(t.is_same_transform(R0, R1), True) -class _ScaleMatrix(object): - def test_scale_matrix(self): - v = np.array([14.1, 15.1, 16.1, 1]) - S = self.f(-1.234) - assert_allclose(np.dot(S, v)[:3], -1.234 * v[:3]) - - -class TestScaleMatrixNP(_ScaleMatrix): - f = staticmethod(t._py_scale_matrix) - - -class TestScaleMatrixCy(_ScaleMatrix): - f = staticmethod(t.scale_matrix) +@pytest.mark.parametrize('f', [ + t._py_scale_matrix, + t.scale_matrix, +]) +def test_scale_matrix(f): + v = np.array([14.1, 15.1, 16.1, 1]) + S = f(-1.234) + assert_allclose(np.dot(S, v)[:3], -1.234 * v[:3]) def test_scale_from_matrix(): @@ -201,84 +155,90 @@ def test_scale_from_matrix(): S1 = t.scale_matrix(factor, origin, direction) assert_equal(t.is_same_transform(S0, S1), True) - -class _ProjectionMatrix(object): - def test_projection_matrix_1(self): - P = self.f((0, 0, 0), (1, 0, 0)) +@pytest.mark.parametrize('f', [ + t._py_projection_matrix, + t.projection_matrix, +]) +class TestProjectionMatrix(object): + def test_projection_matrix_1(self, f): + P = f((0, 0, 0), (1, 0, 0)) assert_allclose(P[1:, 1:], np.identity(4)[1:, 1:], atol=_ATOL) - def test_projection_matrix_2(self): + def test_projection_matrix_2(self, f): point = np.array([0.2, 0.2, 0.2]) # arbitrary values normal = np.array([0.4, 0.4, 0.4]) direct = np.array([0.6, 0.6, 0.6]) persp = np.array([0.8, 0.8, 0.8]) - P0 = self.f(point, normal) + P0 = f(point, normal) # TODO: why isn't this used anymore? - P1 = self.f(point, normal, direction=direct) - P2 = self.f(point, normal, perspective=persp) - P3 = self.f(point, normal, perspective=persp, pseudo=True) + P1 = f(point, normal, direction=direct) + P2 = f(point, normal, perspective=persp) + P3 = f(point, normal, perspective=persp, pseudo=True) assert_equal(t.is_same_transform(P2, np.dot(P0, P3)), True) - def test_projection_matrix_3(self): - P = self.f((3, 0, 0), (1, 1, 0), (1, 0, 0)) + def test_projection_matrix_3(self, f): + P = f((3, 0, 0), (1, 1, 0), (1, 0, 0)) v0 = np.array([14.1, 15.1, 16.1, 1]) # arbitrary values v1 = np.dot(P, v0) assert_allclose(v1[1], v0[1], atol=_ATOL) assert_allclose(v1[0], 3.0 - v1[1], atol=_ATOL) -class TestProjectionMatrixNP(_ProjectionMatrix): - f = staticmethod(t._py_projection_matrix) +class TestProjectionFromMatrix(object): + @staticmethod + @pytest.fixture() + def data(): + point = np.array([0.2, 0.2, 0.2]) # arbitrary values + normal = np.array([0.4, 0.4, 0.4]) + direct = np.array([0.6, 0.6, 0.6]) + persp = np.array([0.8, 0.8, 0.8]) + return point, normal, direct, persp -class TestProjectionMatrixCy(_ProjectionMatrix): - f = staticmethod(t.projection_matrix) - - -class TestProjectionFromMatrix(TestCase): - def setUp(self): - self.point = np.array([0.2, 0.2, 0.2]) # arbitrary values - self.normal = np.array([0.4, 0.4, 0.4]) - self.direct = np.array([0.6, 0.6, 0.6]) - self.persp = np.array([0.8, 0.8, 0.8]) - - def test_projection_from_matrix_1(self): - P0 = t.projection_matrix(self.point, self.normal) + def test_projection_from_matrix_1(self, data): + point, normal, direct, persp = data + P0 = t.projection_matrix(point, normal) result = t.projection_from_matrix(P0) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) - def test_projection_from_matrix_2(self): - P0 = t.projection_matrix(self.point, self.normal, self.direct) + def test_projection_from_matrix_2(self, data): + point, normal, direct, persp = data + P0 = t.projection_matrix(point, normal, direct) result = t.projection_from_matrix(P0) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) - def test_projection_from_matrix_3(self): + def test_projection_from_matrix_3(self, data): + point, normal, direct, persp = data P0 = t.projection_matrix( - self.point, self.normal, perspective=self.persp, pseudo=False) + point, normal, perspective=persp, pseudo=False) result = t.projection_from_matrix(P0, pseudo=False) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) - def test_projection_from_matrix_4(self): + def test_projection_from_matrix_4(self, data): + point, normal, direct, persp = data P0 = t.projection_matrix( - self.point, self.normal, perspective=self.persp, pseudo=True) + point, normal, perspective=persp, pseudo=True) result = t.projection_from_matrix(P0, pseudo=True) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) -class _ClipMatrix(unittest.TestCase): - __test__ = False - def test_clip_matrix_1(self): +@pytest.mark.parametrize('f', [ + t._py_clip_matrix, + t.clip_matrix, +]) +class TestClipMatrix(object): + def test_clip_matrix_1(self, f): frustrum = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) # arbitrary values frustrum[1] += frustrum[0] frustrum[3] += frustrum[2] frustrum[5] += frustrum[4] - M = self.f(perspective=False, *frustrum) + M = f(perspective=False, *frustrum) assert_allclose( np.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]), np.array([-1., -1., -1., 1.])) @@ -286,64 +246,47 @@ def test_clip_matrix_1(self): np.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0]), np.array([1., 1., 1., 1.])) - def test_clip_matrix_2(self): + def test_clip_matrix_2(self, f): frustrum = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) # arbitrary values frustrum[1] += frustrum[0] frustrum[3] += frustrum[2] frustrum[5] += frustrum[4] - M = self.f(perspective=True, *frustrum) + M = f(perspective=True, *frustrum) v = np.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]) assert_allclose(v / v[3], np.array([-1., -1., -1., 1.])) v = np.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0]) assert_allclose(v / v[3], np.array([1., 1., -1., 1.])) - def test_clip_matrix_frustrum_left_right_bounds(self): + def test_clip_matrix_frustrum_left_right_bounds(self, f): '''ValueError should be raised if left > right.''' frustrum = np.array([0.4, 0.3, 0.3, 0.7, 0.5, 1.1]) - with self.assertRaises(ValueError): - self.f(*frustrum) + with pytest.raises(ValueError): + f(*frustrum) - def test_clip_matrix_frustrum_bottom_top_bounds(self): + def test_clip_matrix_frustrum_bottom_top_bounds(self, f): '''ValueError should be raised if bottom > top.''' frustrum = np.array([0.1, 0.3, 0.71, 0.7, 0.5, 1.1]) - with self.assertRaises(ValueError): - self.f(*frustrum) + with pytest.raises(ValueError): + f(*frustrum) - def test_clip_matrix_frustrum_near_far_bounds(self): + def test_clip_matrix_frustrum_near_far_bounds(self, f): '''ValueError should be raised if near > far.''' frustrum = np.array([0.1, 0.3, 0.3, 0.7, 1.5, 1.1]) - with self.assertRaises(ValueError): - self.f(*frustrum) - - -class TestClipMatrixNP(_ClipMatrix): - __test__ = True + with pytest.raises(ValueError): + f(*frustrum) - f = staticmethod(t._py_clip_matrix) - -class TestClipMatrixCy(_ClipMatrix): - __test__ = True - - f = staticmethod(t.clip_matrix) - - -class _ShearMatrix(object): - def test_shear_matrix(self): - angle = 0.2 * 4 * np.pi # arbitrary values - direct = np.array([0.2, 0.2, 0.2]) - point = np.array([0.3, 0.4, 0.5]) - normal = np.cross(direct, np.array([0.8, 0.6, 0.4])) - S = self.f(angle, direct, point, normal) - assert_allclose(1.0, np.linalg.det(S), atol=_ATOL) - - -class TestShearMatrixNP(_ShearMatrix): - f = staticmethod(t._py_shear_matrix) - - -class TestShearMatrixCy(_ShearMatrix): - f = staticmethod(t.shear_matrix) +@pytest.mark.parametrize('f', [ + t._py_shear_matrix, + t.shear_matrix, +]) +def test_shear_matrix(f): + angle = 0.2 * 4 * np.pi # arbitrary values + direct = np.array([0.2, 0.2, 0.2]) + point = np.array([0.3, 0.4, 0.5]) + normal = np.cross(direct, np.array([0.8, 0.6, 0.4])) + S = f(angle, direct, point, normal) + assert_allclose(1.0, np.linalg.det(S), atol=_ATOL) def test_shear_from_matrix(): @@ -400,254 +343,201 @@ def test_compose_matrix(): assert_equal(t.is_same_transform(M0, M1), True) -class _OrthogonalizationMatrix(object): - def test_orthogonalization_matrix_1(self): - O = self.f((10., 10., 10.), (90., 90., 90.)) +@pytest.mark.parametrize('f', [ + t._py_orthogonalization_matrix, + t.orthogonalization_matrix, +]) +class TestOrthogonalizationMatrix(object): + def test_orthogonalization_matrix_1(self, f): + O = f((10., 10., 10.), (90., 90., 90.)) assert_allclose(O[:3, :3], np.identity(3, float) * 10, atol=_ATOL) - def test_orthogonalization_matrix_2(self): - O = self.f([9.8, 12.0, 15.5], [87.2, 80.7, 69.7]) + def test_orthogonalization_matrix_2(self, f): + O = f([9.8, 12.0, 15.5], [87.2, 80.7, 69.7]) assert_allclose(np.sum(O), 43.063229, atol=_ATOL) -class TestOrthogonalizationMatrixNP(_OrthogonalizationMatrix): - f = staticmethod(t._py_orthogonalization_matrix) - - -class TestOrthogonalizationMatrixCy(_OrthogonalizationMatrix): - f = staticmethod(t.orthogonalization_matrix) - - -class _SuperimpositionMatrix(object): - def test_superimposition_matrix(self): - v0 = np.sin(np.linspace(0, 0.99, 30)).reshape(3, - 10) # arbitrary values - M = self.f(v0, v0) - assert_allclose(M, np.identity(4), atol=_ATOL) - - R = t.random_rotation_matrix(np.array([0.3, 0.4, 0.5])) - v0 = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 1)) - v1 = np.dot(R, v0) - M = self.f(v0, v1) - assert_allclose(v1, np.dot(M, v0), atol=_ATOL) - - v0 = np.sin(np.linspace(-1, 1, 400)).reshape(4, 100) - v0[3] = 1.0 - v1 = np.dot(R, v0) - M = self.f(v0, v1) - assert_allclose(v1, np.dot(M, v0), atol=_ATOL) - - S = t.scale_matrix(0.45) - T = t.translation_matrix(np.array([0.2, 0.2, 0.2]) - 0.5) - M = t.concatenate_matrices(T, R, S) - v1 = np.dot(M, v0) - v0[:3] += np.sin(np.linspace(0.0, 1e-9, 300)).reshape(3, -1) - M = self.f(v0, v1, scaling=True) - assert_allclose(v1, np.dot(M, v0), atol=_ATOL) - - M = self.f(v0, v1, scaling=True, usesvd=False) - assert_allclose(v1, np.dot(M, v0), atol=_ATOL) - - v = np.empty((4, 100, 3), dtype=np.float64) - v[:, :, 0] = v0 - M = self.f(v0, v1, scaling=True, usesvd=False) - assert_allclose(v1, np.dot(M, v[:, :, 0]), atol=_ATOL) - - -class TestSuperimpositionMatrixNP(_SuperimpositionMatrix): - f = staticmethod(t._py_superimposition_matrix) - - -class TestSuperimpositionMatrixCy(_SuperimpositionMatrix): - f = staticmethod(t.superimposition_matrix) - - -class _EulerMatrix(object): - def test_euler_matrix_1(self): - R = self.f(1, 2, 3, 'syxz') +@pytest.mark.parametrize('f', [ + t._py_superimposition_matrix, + t.superimposition_matrix, +]) +def test_superimposition_matrix(f): + v0 = np.sin(np.linspace(0, 0.99, 30)).reshape(3, 10) # arbitrary values + M = f(v0, v0) + assert_allclose(M, np.identity(4), atol=_ATOL) + + R = t.random_rotation_matrix(np.array([0.3, 0.4, 0.5])) + v0 = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 1)) + v1 = np.dot(R, v0) + M = f(v0, v1) + assert_allclose(v1, np.dot(M, v0), atol=_ATOL) + + v0 = np.sin(np.linspace(-1, 1, 400)).reshape(4, 100) + v0[3] = 1.0 + v1 = np.dot(R, v0) + M = f(v0, v1) + assert_allclose(v1, np.dot(M, v0), atol=_ATOL) + + S = t.scale_matrix(0.45) + T = t.translation_matrix(np.array([0.2, 0.2, 0.2]) - 0.5) + M = t.concatenate_matrices(T, R, S) + v1 = np.dot(M, v0) + v0[:3] += np.sin(np.linspace(0.0, 1e-9, 300)).reshape(3, -1) + M = f(v0, v1, scaling=True) + assert_allclose(v1, np.dot(M, v0), atol=_ATOL) + + M = f(v0, v1, scaling=True, usesvd=False) + assert_allclose(v1, np.dot(M, v0), atol=_ATOL) + + v = np.empty((4, 100, 3), dtype=np.float64) + v[:, :, 0] = v0 + M = f(v0, v1, scaling=True, usesvd=False) + assert_allclose(v1, np.dot(M, v[:, :, 0]), atol=_ATOL) + + +@pytest.mark.parametrize('f', [ + t._py_euler_matrix, + t.euler_matrix, +]) +class TestEulerMatrix(object): + def test_euler_matrix_1(self, f): + R = f(1, 2, 3, 'syxz') assert_allclose(np.sum(R[0]), -1.34786452) - def test_euler_matrix_2(self): - R = self.f(1, 2, 3, (0, 1, 0, 1)) + def test_euler_matrix_2(self, f): + R = f(1, 2, 3, (0, 1, 0, 1)) assert_allclose(np.sum(R[0]), -0.383436184) -class TestEulerMatrixNP(_EulerMatrix): - f = staticmethod(t._py_euler_matrix) - - -class TestEulerMatrixCy(_EulerMatrix): - f = staticmethod(t.euler_matrix) - - -class _EulerFromMatrix(object): - def test_euler_from_matrix_1(self): +@pytest.mark.parametrize('f', [ + t._py_euler_from_matrix, + t.euler_from_matrix, +]) +class TestEulerFromMatrix(object): + def test_euler_from_matrix_1(self, f): R0 = t.euler_matrix(1, 2, 3, 'syxz') - al, be, ga = self.f(R0, 'syxz') + al, be, ga = f(R0, 'syxz') R1 = t.euler_matrix(al, be, ga, 'syxz') assert_allclose(R0, R1) - def test_euler_from_matrix_2(self): + def test_euler_from_matrix_2(self, f): angles = 4.0 * np.pi * np.array([-0.3, -0.3, -0.3]) # arbitrary values for axes in t._AXES2TUPLE.keys(): R0 = t.euler_matrix(axes=axes, *angles) - R1 = t.euler_matrix(axes=axes, *self.f(R0, axes)) + R1 = t.euler_matrix(axes=axes, *f(R0, axes)) assert_allclose(R0, R1, err_msg=("{0} failed".format(axes))) -class TestEulerFromMatrixNP(_EulerFromMatrix): - f = staticmethod(t._py_euler_from_matrix) - - -class TestEulerFromMatrixCy(_EulerFromMatrix): - f = staticmethod(t.euler_from_matrix) - - def test_euler_from_quaternion(): angles = t.euler_from_quaternion([0.99810947, 0.06146124, 0, 0]) assert_allclose(angles, [0.123, 0, 0], atol=_ATOL) -class _QuaternionFromEuler(object): - def test_quaternion_from_euler(self): - q = self.f(1, 2, 3, 'ryxz') - assert_allclose( - q, [0.435953, 0.310622, -0.718287, 0.444435], atol=_ATOL) - - -class TestQuaternionFromEulerNP(_QuaternionFromEuler): - f = staticmethod(t._py_quaternion_from_euler) - +@pytest.mark.parametrize('f', [ + t._py_quaternion_from_euler, + t.quaternion_from_euler, +]) +def test_quaternion_from_euler(f): + q = f(1, 2, 3, 'ryxz') + assert_allclose(q, [0.435953, 0.310622, -0.718287, 0.444435], atol=_ATOL) -class TestQuaternionFromEulerCy(_QuaternionFromEuler): - f = staticmethod(t.quaternion_from_euler) +@pytest.mark.parametrize('f', [ + t._py_quaternion_about_axis, + t.quaternion_about_axis, +]) +def test_quaternion_about_axis(f): + q = f(0.123, (1, 0, 0)) + assert_allclose(q, [0.99810947, 0.06146124, 0, 0], atol=_ATOL) -class _QuaternionAboutAxis(object): - def test_quaternion_about_axis(self): - q = self.f(0.123, (1, 0, 0)) - assert_allclose(q, [0.99810947, 0.06146124, 0, 0], atol=_ATOL) - -class TestQuaternionAboutAxisNP(_QuaternionAboutAxis): - f = staticmethod(t._py_quaternion_about_axis) - - -class TestQuaternionAboutAxisCy(_QuaternionAboutAxis): - f = staticmethod(t.quaternion_about_axis) - - -class _QuaternionMatrix(object): - def test_quaternion_matrix_1(self): - M = self.f([0.99810947, 0.06146124, 0, 0]) +@pytest.mark.parametrize('f', [ + t._py_quaternion_matrix, + t.quaternion_matrix, +]) +class TestQuaternionMatrix(object): + def test_quaternion_matrix_1(self, f): + M = f([0.99810947, 0.06146124, 0, 0]) assert_allclose(M, t.rotation_matrix(0.123, (1, 0, 0)), atol=_ATOL) - def test_quaternion_matrix_2(self): - M = self.f([1, 0, 0, 0]) + def test_quaternion_matrix_2(self, f): + M = f([1, 0, 0, 0]) assert_allclose(M, t.identity_matrix(), atol=_ATOL) - def test_quaternion_matrix_3(self): - M = self.f([0, 1, 0, 0]) + def test_quaternion_matrix_3(self, f): + M = f([0, 1, 0, 0]) assert_allclose(M, np.diag([1, -1, -1, 1]), atol=_ATOL) -class TestQuaternionMatrixNP(_QuaternionMatrix): - f = staticmethod(t._py_quaternion_matrix) - - -class TestQuaternionMatrixCy(_QuaternionMatrix): - f = staticmethod(t.quaternion_matrix) - - -class _QuaternionFromMatrix(object): - def test_quaternion_from_matrix_1(self): - q = self.f(t.identity_matrix(), True) +@pytest.mark.parametrize('f', [ + t._py_quaternion_from_matrix, + t.quaternion_from_matrix, +]) +class TestQuaternionFromMatrix(object): + def test_quaternion_from_matrix_1(self, f): + q = f(t.identity_matrix(), True) assert_allclose(q, [1., 0., 0., 0.], atol=_ATOL) - def test_quaternion_from_matrix_2(self): - q = self.f(np.diag([1., -1., -1., 1.])) + def test_quaternion_from_matrix_2(self, f): + q = f(np.diag([1., -1., -1., 1.])) check = (np.allclose( q, [0, 1, 0, 0], atol=_ATOL) or np.allclose( q, [0, -1, 0, 0], atol=_ATOL)) assert_equal(check, True) - def test_quaternion_from_matrix_3(self): + def test_quaternion_from_matrix_3(self, f): R = t.rotation_matrix(0.123, (1, 2, 3)) - q = self.f(R, True) + q = f(R, True) assert_allclose( q, [0.9981095, 0.0164262, 0.0328524, 0.0492786], atol=_ATOL) - def test_quaternion_from_matrix_4(self): + def test_quaternion_from_matrix_4(self, f): R = [[-0.545, 0.797, 0.260, 0], [0.733, 0.603, -0.313, 0], [-0.407, 0.021, -0.913, 0], [0, 0, 0, 1]] - q = self.f(R) + q = f(R) assert_allclose(q, [0.19069, 0.43736, 0.87485, -0.083611], atol=_ATOL) - def test_quaternion_from_matrix_5(self): + def test_quaternion_from_matrix_5(self, f): R = [[0.395, 0.362, 0.843, 0], [-0.626, 0.796, -0.056, 0], [-0.677, -0.498, 0.529, 0], [0, 0, 0, 1]] - q = self.f(R) + q = f(R) assert_allclose( q, [0.82336615, -0.13610694, 0.46344705, -0.29792603], atol=_ATOL) - def test_quaternion_from_matrix_6(self): + def test_quaternion_from_matrix_6(self, f): R = t.random_rotation_matrix() - q = self.f(R) + q = f(R) assert_equal(t.is_same_transform(R, t.quaternion_matrix(q)), True) -class TestQuaternionFromMatrixNP(_QuaternionFromMatrix): - f = staticmethod(t._py_quaternion_from_matrix) - - -class TestQuaternionFromMatrixCy(_QuaternionFromMatrix): - f = staticmethod(t.quaternion_from_matrix) - - -class _QuaternionMultiply(object): - def test_quaternion_multiply(self): - q = self.f([4, 1, -2, 3], [8, -5, 6, 7]) - assert_allclose(q, [28, -44, -14, 48]) - - -class TestQuaternionMultiplyNP(_QuaternionMultiply): - f = staticmethod(t._py_quaternion_multiply) - - -class TestQuaternionMultiplyCy(_QuaternionMultiply): - f = staticmethod(t.quaternion_multiply) - - -class _QuaternionConjugate(object): - def test_quaternion_conjugate(self): - q0 = t.random_quaternion() - q1 = self.f(q0) - check = q1[0] == q0[0] and all(q1[1:] == -q0[1:]) - assert_equal(check, True) - - -class TestQuaternionConjugateNP(_QuaternionConjugate): - f = staticmethod(t._py_quaternion_conjugate) - - -class TestQuaternionConjugateCy(_QuaternionConjugate): - f = staticmethod(t.quaternion_conjugate) - - -class _QuaternionInverse(object): - def test_quaternion_inverse(self): - q0 = t.random_quaternion() - q1 = self.f(q0) - assert_allclose( - t.quaternion_multiply(q0, q1), [1, 0, 0, 0], atol=_ATOL) +@pytest.mark.parametrize('f', [ + t._py_quaternion_multiply, + t.quaternion_multiply, +]) +def test_quaternion_multiply(f): + q = f([4, 1, -2, 3], [8, -5, 6, 7]) + assert_allclose(q, [28, -44, -14, 48]) -class TestQuaternionInverseNP(_QuaternionInverse): - f = staticmethod(t._py_quaternion_inverse) +@pytest.mark.parametrize('f', [ + t._py_quaternion_conjugate, + t.quaternion_conjugate, +]) +def test_quaternion_conjugate(f): + q0 = t.random_quaternion() + q1 = f(q0) + check = q1[0] == q0[0] and all(q1[1:] == -q0[1:]) + assert_equal(check, True) -class TestQuaternionInverseCy(_QuaternionInverse): - f = staticmethod(t.quaternion_inverse) +@pytest.mark.parametrize('f', [ + t._py_quaternion_inverse, + t.quaternion_inverse, +]) +def test_quaternion_inverse(f): + q0 = t.random_quaternion() + q1 = f(q0) + assert_allclose(t.quaternion_multiply(q0, q1), [1, 0, 0, 0], atol=_ATOL) def test_quaternion_real(): @@ -658,203 +548,167 @@ def test_quaternion_imag(): assert_allclose(t.quaternion_imag([3.0, 0.0, 1.0, 2.0]), [0.0, 1.0, 2.0]) -class _QuaternionSlerp(object): - def test_quaternion_slerp(self): - q0 = t.random_quaternion() - q1 = t.random_quaternion() - q = self.f(q0, q1, 0.0) - assert_allclose(q, q0, atol=_ATOL) - - q = self.f(q0, q1, 1.0, 1) - assert_allclose(q, q1, atol=_ATOL) - - q = self.f(q0, q1, 0.5) - angle = np.arccos(np.dot(q0, q)) +@pytest.mark.parametrize('f', [ + t._py_quaternion_slerp, + t.quaternion_slerp, +]) +def test_quaternion_slerp(f): + q0 = t.random_quaternion() + q1 = t.random_quaternion() + q = f(q0, q1, 0.0) + assert_allclose(q, q0, atol=_ATOL) - check = (np.allclose(2.0, np.arccos(np.dot(q0, q1)) / angle) or - np.allclose(2.0, np.arccos(-np.dot(q0, q1)) / angle)) - - assert_equal(check, True) + q = f(q0, q1, 1.0, 1) + assert_allclose(q, q1, atol=_ATOL) + q = f(q0, q1, 0.5) + angle = np.arccos(np.dot(q0, q)) -class TestQuaternionSlerpNP(_QuaternionSlerp): - f = staticmethod(t._py_quaternion_slerp) + check = (np.allclose(2.0, np.arccos(np.dot(q0, q1)) / angle) or + np.allclose(2.0, np.arccos(-np.dot(q0, q1)) / angle)) + assert_equal(check, True) -class TestQuaternionSlerpCy(_QuaternionSlerp): - f = staticmethod(t.quaternion_slerp) - -class _RandomQuaternion(object): - def test_random_quaternion_1(self): - q = self.f() +@pytest.mark.parametrize('f', [ + t._py_random_quaternion, + t.random_quaternion, +]) +class TestRandomQuaternion(object): + def test_random_quaternion_1(self, f): + q = f() assert_allclose(1.0, t.vector_norm(q)) - def test_random_quaternion_2(self): - q = self.f(np.array([0.2, 0.2, 0.2])) + def test_random_quaternion_2(self, f): + q = f(np.array([0.2, 0.2, 0.2])) assert_equal(len(q.shape), 1) assert_equal(q.shape[0] == 4, True) -class TestRandomQuaternionNP(_RandomQuaternion): - f = staticmethod(t._py_random_quaternion) - - -class TestRandomQuaternionCy(_RandomQuaternion): - f = staticmethod(t.random_quaternion) - +@pytest.mark.parametrize('f', [ + t._py_random_rotation_matrix, + t.random_rotation_matrix, +]) +def test_random_rotation_matrix(f): + R = f() + assert_allclose(np.dot(R.T, R), np.identity(4), atol=_ATOL) -class _RandomRotationMatrix(object): - def test_random_rotation_matrix(self): - R = self.f() - assert_allclose(np.dot(R.T, R), np.identity(4), atol=_ATOL) - -class TestRandomRotationMatrixNP(_RandomRotationMatrix): - f = staticmethod(t._py_random_rotation_matrix) - - -class TestRandomRotationMatrixCy(_RandomRotationMatrix): - f = staticmethod(t.random_rotation_matrix) - - -class _InverseMatrix(object): - def _check_inverse(self, size): +@pytest.mark.parametrize('f', [ + t._py_inverse_matrix, + t.inverse_matrix, +]) +class TestInverseMatrix(object): + @pytest.mark.parametrize('size', list(range(1, 7))) + def test_inverse(self, size, f): # Create a known random state to generate numbers from # these numbers will then be uncorrelated but deterministic rs = np.random.RandomState(1234) M0 = rs.randn(size, size) - M1 = self.f(M0) + M1 = f(M0) assert_allclose(M1, np.linalg.inv(M0), err_msg=str(size), atol=_ATOL) - def test_inverse_matrix(self): + def test_inverse_matrix(self, f): M0 = t.random_rotation_matrix() - M1 = self.f(M0.T) + M1 = f(M0.T) assert_allclose(M1, np.linalg.inv(M0.T)) - for size in range(1, 7): - yield self._check_inverse, size - - -class TestInverseMatrixNP(_InverseMatrix): - f = staticmethod(t._py_inverse_matrix) - - -class TestInverseMatrixCy(_InverseMatrix): - f = staticmethod(t.inverse_matrix) +@pytest.mark.parametrize('f', [ + t._py_is_same_transform, + t.is_same_transform, +]) +class TestIsSameTransform(object): + def test_is_same_transform_1(self, f): + assert_equal(f(np.identity(4), np.identity(4)), True) -class _IsSameTransform(object): - def test_is_same_transform_1(self): - assert_equal(self.f(np.identity(4), np.identity(4)), True) + def test_is_same_transform_2(self, f): + assert_equal(f(t.random_rotation_matrix(), np.identity(4)), False) - def test_is_same_transform_2(self): - assert_equal(self.f(t.random_rotation_matrix(), np.identity(4)), False) - -class TestIsSameTransformNP(_IsSameTransform): - f = staticmethod(t._py_is_same_transform) - - -class TestIsSameTransformCy(_IsSameTransform): - f = staticmethod(t.is_same_transform) - - -class _RandomVector(object): - def test_random_vector_1(self): - v = self.f(1000) +@pytest.mark.parametrize('f', [ + t._py_random_vector, + t.random_vector, +]) +class TestRandomVector(object): + def test_random_vector_1(self, f): + v = f(1000) check = np.all(v >= 0.0) and np.all(v < 1.0) assert_equal(check, True) - def test_random_vector_2(self): - v0 = self.f(10) - v1 = self.f(10) + def test_random_vector_2(self, f): + v0 = f(10) + v1 = f(10) assert_equal(np.any(v0 == v1), False) -class TestRandomVectorNP(_RandomVector): - f = staticmethod(t._py_random_vector) - - -class TestRandomVectorCy(_RandomVector): - f = staticmethod(t.random_vector) - - -class _UnitVector(object): - def test_unit_vector_1(self): +@pytest.mark.parametrize('f', [ + t._py_unit_vector, + t.unit_vector, +]) +class TestUnitVector(object): + def test_unit_vector_1(self, f): v0 = np.array([0.2, 0.2, 0.2]) - v1 = self.f(v0) + v1 = f(v0) assert_allclose(v1, v0 / np.linalg.norm(v0), atol=_ATOL) - def test_unit_vector_2(self): + def test_unit_vector_2(self, f): v0 = np.sin(np.linspace(0, 10, 5 * 4 * 3)).reshape(5, 4, 3) - v1 = self.f(v0, axis=-1) + v1 = f(v0, axis=-1) v2 = v0 / np.expand_dims(np.sqrt(np.sum(v0 * v0, axis=2)), 2) assert_allclose(v1, v2, atol=_ATOL) - def test_unit_vector_3(self): + def test_unit_vector_3(self, f): v0 = np.sin(np.linspace(0, 10, 5 * 4 * 3)).reshape(5, 4, 3) - v1 = self.f(v0, axis=1) + v1 = f(v0, axis=1) v2 = v0 / np.expand_dims(np.sqrt(np.sum(v0 * v0, axis=1)), 1) assert_allclose(v1, v2, atol=_ATOL) - def test_unit_vector_4(self): + def test_unit_vector_4(self, f): v0 = np.sin(np.linspace(0, 10, 5 * 4 * 3)).reshape(5, 4, 3) v1 = np.empty((5, 4, 3), dtype=np.float64) v2 = v0 / np.expand_dims(np.sqrt(np.sum(v0 * v0, axis=1)), 1) - self.f(v0, axis=1, out=v1) + f(v0, axis=1, out=v1) assert_allclose(v1, v2, atol=_ATOL) - def test_unit_vector_5(self): - assert_equal(list(self.f([])), []) + def test_unit_vector_5(self, f): + assert_equal(list(f([])), []) - def test_unit_vector_6(self): - assert_equal(list(self.f([1.0])), [1.0]) + def test_unit_vector_6(self, f): + assert_equal(list(f([1.0])), [1.0]) -class TestUnitVectorNP(_UnitVector): - f = staticmethod(t._py_unit_vector) - - -class TestUnitVectorCy(_UnitVector): - f = staticmethod(t.unit_vector) - - -class _VectorNorm(object): - def test_vector_norm_1(self): +@pytest.mark.parametrize('f', [ + t._py_vector_norm, + t.vector_norm, +]) +class TestVectorNorm(object): + def test_vector_norm_1(self, f): v = np.array([0.2, 0.2, 0.2]) - n = self.f(v) + n = f(v) assert_allclose(n, np.linalg.norm(v), atol=_ATOL) - def test_vector_norm_2(self): + def test_vector_norm_2(self, f): v = np.sin(np.linspace(0, 10, 6 * 5 * 3)).reshape(6, 5, 3) - n = self.f(v, axis=-1) + n = f(v, axis=-1) assert_allclose(n, np.sqrt(np.sum(v * v, axis=2)), atol=_ATOL) - def test_vector_norm_3(self): + def test_vector_norm_3(self, f): v = np.sin(np.linspace(0, 10, 6 * 5 * 3)).reshape(6, 5, 3) - n = self.f(v, axis=1) + n = f(v, axis=1) assert_allclose(n, np.sqrt(np.sum(v * v, axis=1)), atol=_ATOL) - def test_vector_norm_4(self): + def test_vector_norm_4(self, f): v = np.sin(np.linspace(0, 10, 5 * 4 * 3)).reshape(5, 4, 3) n = np.empty((5, 3), dtype=np.float64) - self.f(v, axis=1, out=n) + f(v, axis=1, out=n) assert_allclose(n, np.sqrt(np.sum(v * v, axis=1)), atol=_ATOL) - def test_vector_norm_5(self): - assert_equal(self.f([]), 0.0) - - def test_vector_norm_6(self): - assert_equal(self.f([1.0]), 1.0) - - -class TestVectorNormNP(_VectorNorm): - f = staticmethod(t._py_vector_norm) - + def test_vector_norm_5(self, f): + assert_equal(f([]), 0.0) -class TestVectorNormCy(_VectorNorm): - f = staticmethod(t.vector_norm) + def test_vector_norm_6(self, f): + assert_equal(f([1.0]), 1.0) class TestArcBall(object): diff --git a/testsuite/MDAnalysisTests/utils/test_units.py b/testsuite/MDAnalysisTests/utils/test_units.py index 6e010d1e485..f2ae6218952 100644 --- a/testsuite/MDAnalysisTests/utils/test_units.py +++ b/testsuite/MDAnalysisTests/utils/test_units.py @@ -21,15 +21,16 @@ # from __future__ import unicode_literals, absolute_import import six +import pytest import numpy as np -from numpy.testing import assert_equal, assert_almost_equal, assert_raises,TestCase +from numpy.testing import assert_equal, assert_almost_equal, assert_raises from MDAnalysis import units from MDAnalysis.core import flags -class TestDefaultUnits(TestCase): +class TestDefaultUnits(object): def test_length(self): assert_equal(flags['length_unit'], 'Angstrom', u"The default length unit should be Angstrom (in core.flags)") @@ -43,7 +44,8 @@ def test_convert_gromacs_trajectories(self): u"The default behaviour should be to auto-convert Gromacs trajectories") -class TestUnitEncoding(TestCase): + +class TestUnitEncoding(object): def test_unicode(self): try: assert_equal(units.lengthUnit_factor[u"\u212b"], 1.0) @@ -68,9 +70,9 @@ class TestConstants(object): 'calorie': 4.184, # J } - def test_constant(self): - for name, value in six.iteritems(self.constants_reference): - yield self.check_physical_constant, name, value + @pytest.mark.parametrize('name, value', constants_reference.items()) + def test_constant(self, name, value): + self.check_physical_constant(name, value) @staticmethod def check_physical_constant(name, reference): @@ -80,33 +82,49 @@ def check_physical_constant(name, reference): class TestConversion(object): @staticmethod def _assert_almost_equal_convert(value, u1, u2, ref): - assert_almost_equal(units.convert(value, u1, u2), ref, + val = units.convert(value, u1, u2) + assert_almost_equal(val, ref, err_msg="Conversion {0} --> {1} failed".format(u1, u2)) - # generate individual test cases using nose's test generator mechanism - def test_length(self): - nm = 12.34567 - A = nm * 10. - yield self._assert_almost_equal_convert, nm, 'nm', 'A', A - yield self._assert_almost_equal_convert, A, 'Angstrom', 'nm', nm - - def test_time(self): - yield self._assert_almost_equal_convert, 1, 'ps', 'AKMA', 20.45482949774598 - yield self._assert_almost_equal_convert, 1, 'AKMA', 'ps', 0.04888821 - - def test_energy(self): - yield self._assert_almost_equal_convert, 1, 'kcal/mol', 'kJ/mol', 4.184 - yield self._assert_almost_equal_convert, 1, 'kcal/mol', 'eV', 0.0433641 - - def test_force(self): - yield self._assert_almost_equal_convert, 1, 'kJ/(mol*A)', 'J/m', 1.66053892103219e-11 - yield self._assert_almost_equal_convert, 2.5, 'kJ/(mol*nm)', 'kJ/(mol*A)', 0.25 - yield self._assert_almost_equal_convert, 1, 'kcal/(mol*Angstrom)', 'kJ/(mol*Angstrom)', 4.184 - - def test_unit_unknown(self): - nm = 12.34567 - assert_raises(ValueError, units.convert, nm, 'Stone', 'nm') - assert_raises(ValueError, units.convert, nm, 'nm', 'Stone') + nm = 12.34567 + A = nm * 10. + @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( + (nm, 'nm', 'A', A), + (A, 'Angstrom', 'nm', nm), + )) + def test_length(self, quantity, unit1, unit2, ref): + self._assert_almost_equal_convert(quantity, unit1, unit2, ref) + + @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( + (1, 'ps', 'AKMA', 20.45482949774598), + (1, 'AKMA', 'ps', 0.04888821), + )) + def test_time(self, quantity, unit1, unit2, ref): + self._assert_almost_equal_convert(quantity, unit1, unit2, ref) + + @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( + (1, 'kcal/mol', 'kJ/mol', 4.184), + (1, 'kcal/mol', 'eV', 0.0433641), + )) + def test_energy(self, quantity, unit1, unit2, ref): + self._assert_almost_equal_convert(quantity, unit1, unit2, ref) + + @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( + (1, 'kJ/(mol*A)', 'J/m', 1.66053892103219e-11), + (2.5, 'kJ/(mol*nm)', 'kJ/(mol*A)', 0.25), + (1, 'kcal/(mol*Angstrom)', 'kJ/(mol*Angstrom)', 4.184), + )) + def test_force(self, quantity, unit1, unit2, ref): + self._assert_almost_equal_convert(quantity, unit1, unit2, ref) + + @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( + pytest.mark.raises((nm, 'Stone', 'nm', None), exception=ValueError), + pytest.mark.raises((nm, 'nm', 'Stone', None), exception=ValueError), + )) + def test_unit_unknown(self, quantity, unit1, unit2, ref): + val = units.convert(quantity, unit1, unit2) + assert_almost_equal(val, ref, + err_msg="Conversion {0} --> {1} failed".format(unit1, unit2)) def test_unit_unconvertable(self): nm = 12.34567