From 51649adc6f6cddf6c5f54d241edebac13ca51ffc Mon Sep 17 00:00:00 2001 From: Dominik 'Rathann' Mierzejewski Date: Fri, 9 Jun 2017 11:42:19 +0200 Subject: [PATCH 01/10] use correct int types This fixes ERRORs and FAILs in the testsuite on 32bit: TypeError: Cannot cast array data from dtype('int64') to dtype('int32') according to the rule 'safe' and assert_(out[0].dtype == np.int64) File "/usr/lib/python2.7/site-packages/numpy/testing/utils.py", line 92, in assert_ raise AssertionError(smsg) AssertionError --- package/MDAnalysis/core/groups.py | 2 +- package/MDAnalysis/core/topology.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index c386e079ef0..f2459c9f1dd 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -419,7 +419,7 @@ def __init__(self, *args): ix, u = args # indices for the objects I hold - self._ix = np.asarray(ix, dtype=np.int64) + self._ix = np.asarray(ix, dtype=np.intp) self._u = u self._cache = dict() diff --git a/package/MDAnalysis/core/topology.py b/package/MDAnalysis/core/topology.py index 2c11d2f3632..2653acfb84d 100644 --- a/package/MDAnalysis/core/topology.py +++ b/package/MDAnalysis/core/topology.py @@ -125,12 +125,12 @@ def make_downshift_arrays(upshift, nparents): counter += 1 # If parent is skipped, eg (0, 0, 2, 2, etc) while counter != upshift[order[x:y][0]]: - downshift.append(np.array([], dtype=np.int)) + downshift.append(np.array([], dtype=np.int64)) counter += 1 - downshift.append(np.sort(np.array(order[x:y], copy=True, dtype=np.int))) + downshift.append(np.sort(np.array(order[x:y], copy=True, dtype=np.int64))) # Add entries for childless parents at end of range while counter < (nparents - 1): - downshift.append(np.array([], dtype=np.int)) + downshift.append(np.array([], dtype=np.int64)) counter += 1 # Add None to end of array to force it to be of type Object # Without this, a rectangular array gets squashed into a single array From 280dc138c7ec8d0f68015067307419474fab0643 Mon Sep 17 00:00:00 2001 From: Dominik 'Rathann' Mierzejewski Date: Fri, 9 Jun 2017 11:49:09 +0200 Subject: [PATCH 02/10] relax test_symmetry test due to lower precision on 32bit --- testsuite/MDAnalysisTests/analysis/test_psa.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_psa.py b/testsuite/MDAnalysisTests/analysis/test_psa.py index 3d4d097f10b..ddc0b8f4e71 100644 --- a/testsuite/MDAnalysisTests/analysis/test_psa.py +++ b/testsuite/MDAnalysisTests/analysis/test_psa.py @@ -221,7 +221,8 @@ def test_symmetry(self): for a given Hausdorff metric, h.''' forward = self.h(self.path_1, self.path_2) reverse = self.h(self.path_2, self.path_1) - self.assertEqual(forward, reverse) + # lower precision on 32bit + assert_almost_equal(forward, reverse, decimal=15) def test_hausdorff_value(self): '''Test that the undirected Hausdorff From 1f61efd9a7b9e88801a04b445c8ce54bfae309f5 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Mon, 12 Jun 2017 14:47:13 +0100 Subject: [PATCH 03/10] Aesthetic fixes to test_gro Removed TestCase usage --- .../MDAnalysisTests/coordinates/test_gro.py | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_gro.py b/testsuite/MDAnalysisTests/coordinates/test_gro.py index beb202bdac0..ca08e35a3a4 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_gro.py +++ b/testsuite/MDAnalysisTests/coordinates/test_gro.py @@ -20,22 +20,32 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # from __future__ import absolute_import -from unittest import TestCase import MDAnalysis as mda import numpy as np from MDAnalysis.coordinates.GRO import GROReader, GROWriter from MDAnalysisTests import make_Universe -from MDAnalysisTests.coordinates.base import BaseReference, BaseReaderTest, BaseWriterTest, BaseTimestepTest +from MDAnalysisTests.coordinates.base import ( + BaseReference, BaseReaderTest, BaseWriterTest, BaseTimestepTest, +) from MDAnalysisTests.coordinates.reference import RefAdK -from MDAnalysisTests.datafiles import COORDINATES_GRO, COORDINATES_GRO_INCOMPLETE_VELOCITY, COORDINATES_GRO_BZ2, GRO, \ - GRO_large +from MDAnalysisTests.datafiles import ( + COORDINATES_GRO, + COORDINATES_GRO_INCOMPLETE_VELOCITY, + COORDINATES_GRO_BZ2, + GRO, + GRO_large, +) from nose.plugins.attrib import attr -from numpy.testing import (assert_almost_equal, ) -from numpy.testing import assert_array_almost_equal, dec, assert_equal, assert_raises - - -class TestGROReaderOld(TestCase, RefAdK): +from numpy.testing import ( + assert_almost_equal, + assert_array_almost_equal, + dec, + assert_equal, + assert_raises +) + +class TestGROReaderOld(RefAdK): def setUp(self): self.universe = mda.Universe(GRO) self.ts = self.universe.trajectory.ts @@ -88,7 +98,7 @@ def test_unitcell(self): err_msg="unit cell dimensions (rhombic dodecahedron)") -class TestGROReaderNoConversionOld(TestCase, RefAdK): +class TestGROReaderNoConversionOld(RefAdK): def setUp(self): self.universe = mda.Universe(GRO, convert_units=False) self.ts = self.universe.trajectory.ts From b3adb1b23b69c69b8194f357d082893ca5bf1b20 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Mon, 12 Jun 2017 15:30:08 +0100 Subject: [PATCH 04/10] Added tests for issue #1395 --- testsuite/MDAnalysisTests/coordinates/test_gro.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_gro.py b/testsuite/MDAnalysisTests/coordinates/test_gro.py index ca08e35a3a4..e240ad3aa21 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_gro.py +++ b/testsuite/MDAnalysisTests/coordinates/test_gro.py @@ -24,7 +24,7 @@ import MDAnalysis as mda import numpy as np from MDAnalysis.coordinates.GRO import GROReader, GROWriter -from MDAnalysisTests import make_Universe +from MDAnalysisTests import make_Universe, tempdir from MDAnalysisTests.coordinates.base import ( BaseReference, BaseReaderTest, BaseWriterTest, BaseTimestepTest, ) @@ -38,6 +38,7 @@ ) from nose.plugins.attrib import attr from numpy.testing import ( + assert_, assert_almost_equal, assert_array_almost_equal, dec, @@ -417,6 +418,18 @@ def test_writer_large_residue_count(self): err_msg="Writing GRO file with > 99 999 " "resids does not truncate properly.") +@tempdir.run_in_tempdir() +def test_growriter_resid_truncation(): + u = make_Universe(extras=['resids'], trajectory=True) + u.residues[0].resid = 123456789 + u.atoms.write('out.gro') + + with open('out.gro', 'r') as grofile: + grofile.readline() + grofile.readline() + line = grofile.readline() + # larger digits should get truncated + assert_(line.startswith('56789UNK')) class TestGROTimestep(BaseTimestepTest): Timestep = mda.coordinates.GRO.Timestep From 6baf47ea84f84aa68498e17f9b399d30cfd542c5 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Mon, 12 Jun 2017 16:03:51 +0100 Subject: [PATCH 05/10] Added lib.util.ltruncate_integer --- package/MDAnalysis/lib/util.py | 25 ++++++++++++++++++++++ testsuite/MDAnalysisTests/lib/test_util.py | 16 ++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/package/MDAnalysis/lib/util.py b/package/MDAnalysis/lib/util.py index c40c15be7b0..d75517c7553 100644 --- a/package/MDAnalysis/lib/util.py +++ b/package/MDAnalysis/lib/util.py @@ -1574,3 +1574,28 @@ def __eq__(self, other): except AssertionError: return False return True + + +def ltruncate_int(value, ndigits): + """Truncate an integer, retaining least significant digits + + Parameters + ---------- + value : int + value to truncate + ndigits : int + number of digits to keep + + Returns + ------- + truncated : int + only the `ndigits` least significant digits from `value` + + Examples + -------- + >>> ltruncate_int(123, 2) + 23 + >>> ltruncate_int(1234, 5) + 1234 + """ + return int(str(value)[-ndigits:]) diff --git a/testsuite/MDAnalysisTests/lib/test_util.py b/testsuite/MDAnalysisTests/lib/test_util.py index 1db65566a7d..ddf4558f884 100644 --- a/testsuite/MDAnalysisTests/lib/test_util.py +++ b/testsuite/MDAnalysisTests/lib/test_util.py @@ -968,3 +968,19 @@ def test_iter(self): seen.append(val) for val in ['this', 'that', 'other']: assert_(val in seen) + + +class TestTruncateInteger(object): + @staticmethod + def _check_vals(a, b): + assert_(util.ltruncate_int(*a) == b) + + def test_ltruncate_int(self): + for vals, exp in ( + ((1234, 1), 4), + ((1234, 2), 34), + ((1234, 3), 234), + ((1234, 4), 1234), + ((1234, 5), 1234), + ): + yield self._check_vals, vals, exp From 46768e5e42cc370ed8ba6d0e00075717feb13107 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Mon, 12 Jun 2017 17:11:09 +0100 Subject: [PATCH 06/10] Changed coordinate writers to use ltruncate_int Fixes issue #1395 --- package/CHANGELOG | 5 +++-- package/MDAnalysis/coordinates/CRD.py | 6 +++--- package/MDAnalysis/coordinates/GRO.py | 4 ++-- package/MDAnalysis/coordinates/PDB.py | 4 ++-- package/MDAnalysis/coordinates/PDBQT.py | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index d91b3b49d86..d3d96542a51 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,13 +13,14 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -mm/dd/yy +mm/dd/17 - * 0.16.2 + * 0.16.2 richardjgowers Enhancements Fixes + * fixed GROWriter truncating long resids from the wrong end (Issue #1395) Changes diff --git a/package/MDAnalysis/coordinates/CRD.py b/package/MDAnalysis/coordinates/CRD.py index 70e7b857575..c005da25e8f 100644 --- a/package/MDAnalysis/coordinates/CRD.py +++ b/package/MDAnalysis/coordinates/CRD.py @@ -251,9 +251,9 @@ def write(self, selection, frame=None): current_resid += 1 # Truncate numbers - serial = int(str(i + 1)[-serial_len:]) - resid = int(str(resid)[-resid_len:]) - current_resid = int(str(current_resid)[-totres_len:]) + serial = util.ltruncate_int(i + 1, serial_len) + resid = util.ltruncate_int(resid, resid_len) + current_resid = util.ltruncate_int(current_resid, totres_len) crd.write(at_fmt.format( serial=serial, totRes=current_resid, resname=resname, diff --git a/package/MDAnalysis/coordinates/GRO.py b/package/MDAnalysis/coordinates/GRO.py index 3a36fa69d2f..7691b9fb2cb 100644 --- a/package/MDAnalysis/coordinates/GRO.py +++ b/package/MDAnalysis/coordinates/GRO.py @@ -377,8 +377,8 @@ def write(self, obj): # all attributes could be infinite cycles! for atom_index, resid, resname, name in zip( range(ag_or_ts.n_atoms), resids, resnames, names): - truncated_atom_index = int(str(atom_index + 1)[-5:]) - truncated_resid = int(str(resid)[:5]) + truncated_atom_index = util.ltruncate_int(atom_index + 1, 5) + truncated_resid = util.ltruncate_int(resid, 5) if has_velocities: output_gro.write(self.fmt['xyz_v'].format( resid=truncated_resid, diff --git a/package/MDAnalysis/coordinates/PDB.py b/package/MDAnalysis/coordinates/PDB.py index 2ac9b899f3f..910b42ca939 100644 --- a/package/MDAnalysis/coordinates/PDB.py +++ b/package/MDAnalysis/coordinates/PDB.py @@ -902,12 +902,12 @@ def get_attr(attrname, default): for i, atom in enumerate(atoms): vals = {} - vals['serial'] = int(str(i + 1)[-5:]) # check for overflow here? + vals['serial'] = util.ltruncate_int(i + 1, 5) # check for overflow here? vals['name'] = self._deduce_PDB_atom_name(atomnames[i], resnames[i]) vals['altLoc'] = altlocs[i][:1] vals['resName'] = resnames[i][:4] vals['chainID'] = segids[i][:1] - vals['resSeq'] = int(str(resids[i])[-4:]) + vals['resSeq'] = util.ltruncate_int(resids[i], 4) vals['iCode'] = icodes[i][:1] vals['pos'] = pos[i] # don't take off atom so conversion works vals['occupancy'] = occupancies[i] diff --git a/package/MDAnalysis/coordinates/PDBQT.py b/package/MDAnalysis/coordinates/PDBQT.py index 1cc2d4d3657..e16e2792831 100644 --- a/package/MDAnalysis/coordinates/PDBQT.py +++ b/package/MDAnalysis/coordinates/PDBQT.py @@ -320,12 +320,12 @@ def write(self, selection, frame=None): attrs['resids'], attrs['icodes'], attrs['occupancies'], attrs['tempfactors'], attrs['charges'], attrs['types']), start=1): - serial = int(str(serial)[-5:]) # check for overflow here? + serial = util.ltruncate_int(serial, 5) # check for overflow here? + resid = util.ltruncate_int(resid, 4) name = name[:4] if len(name) < 4: name = " " + name # customary to start in column 14 chainid = chainid.strip()[-1:] # take the last character - resid = int(str(resid)[-4:]) # check for overflow here? self.pdb.write(self.fmt['ATOM'].format( serial=serial, From 6a3e42798af3dbb12792a3034fb76139abc8964f Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Mon, 12 Jun 2017 07:58:40 +0100 Subject: [PATCH 07/10] More work on making indexing arrays use intp (#1362) - Added tests for 32 bit index support - added @rathann to AUTHORS --- package/AUTHORS | 2 + package/CHANGELOG | 3 +- package/MDAnalysis/core/groups.py | 4 +- package/MDAnalysis/core/topology.py | 14 +-- .../MDAnalysisTests/core/test_index_dtype.py | 92 +++++++++++++++++++ 5 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 testsuite/MDAnalysisTests/core/test_index_dtype.py diff --git a/package/AUTHORS b/package/AUTHORS index 4267d50f93d..4133aafd72b 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -90,6 +90,8 @@ Chronological list of authors - Sang Young Noh - Andrew William King - Kathleen Clark + - Dominik 'Rathann' Mierzejewski + External code ------------- diff --git a/package/CHANGELOG b/package/CHANGELOG index d3d96542a51..666386fe8ff 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -15,12 +15,13 @@ The rules for this file: ------------------------------------------------------------------------------ mm/dd/17 - * 0.16.2 richardjgowers + * 0.16.2 richardjgowers, rathann Enhancements Fixes * fixed GROWriter truncating long resids from the wrong end (Issue #1395) + * Fixed dtype of numpy arrays to accomodate 32 bit architectures (Issue #1362) Changes diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index f2459c9f1dd..963f069d3f2 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -2464,7 +2464,7 @@ def ix_array(self): -------- ix """ - return np.array([self.ix]) + return np.array([self.ix], dtype=np.intp) class Atom(ComponentBase): @@ -2736,7 +2736,7 @@ def update_selection(self): ix = sum([sel.apply(bg) for sel in sels[1:]], sels[0].apply(bg)).ix else: - ix = np.array([], dtype=np.int) + ix = np.array([], dtype=np.intp) # Run back through AtomGroup init with this information to remake ourselves super(UpdatingAtomGroup, self).__init__(ix, self.universe) self.is_uptodate = True diff --git a/package/MDAnalysis/core/topology.py b/package/MDAnalysis/core/topology.py index 2653acfb84d..3b98daabc91 100644 --- a/package/MDAnalysis/core/topology.py +++ b/package/MDAnalysis/core/topology.py @@ -125,12 +125,12 @@ def make_downshift_arrays(upshift, nparents): counter += 1 # If parent is skipped, eg (0, 0, 2, 2, etc) while counter != upshift[order[x:y][0]]: - downshift.append(np.array([], dtype=np.int64)) + downshift.append(np.array([], dtype=np.intp)) counter += 1 - downshift.append(np.sort(np.array(order[x:y], copy=True, dtype=np.int64))) + downshift.append(np.sort(np.array(order[x:y], copy=True, dtype=np.intp))) # Add entries for childless parents at end of range while counter < (nparents - 1): - downshift.append(np.array([], dtype=np.int64)) + downshift.append(np.array([], dtype=np.intp)) counter += 1 # Add None to end of array to force it to be of type Object # Without this, a rectangular array gets squashed into a single array @@ -210,18 +210,18 @@ def __init__(self, # built atom-to-residue mapping, and vice-versa if atom_resindex is None: - self._AR = np.zeros(n_atoms, dtype=np.int64) + self._AR = np.zeros(n_atoms, dtype=np.intp) else: - self._AR = atom_resindex.copy() + self._AR = np.asarray(atom_resindex, dtype=np.intp).copy() if not len(self._AR) == n_atoms: raise ValueError("atom_resindex must be len n_atoms") self._RA = make_downshift_arrays(self._AR, n_residues) # built residue-to-segment mapping, and vice-versa if residue_segindex is None: - self._RS = np.zeros(n_residues, dtype=np.int64) + self._RS = np.zeros(n_residues, dtype=np.intp) else: - self._RS = residue_segindex.copy() + self._RS = np.asarray(residue_segindex, dtype=np.intp).copy() if not len(self._RS) == n_residues: raise ValueError("residue_segindex must be len n_residues") self._SR = make_downshift_arrays(self._RS, n_segments) diff --git a/testsuite/MDAnalysisTests/core/test_index_dtype.py b/testsuite/MDAnalysisTests/core/test_index_dtype.py new file mode 100644 index 00000000000..43e083a6a74 --- /dev/null +++ b/testsuite/MDAnalysisTests/core/test_index_dtype.py @@ -0,0 +1,92 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- http://www.mdanalysis.org +# Copyright (c) 2006-2016 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# + +"""32 bit compat tests + +Tests for making sure that integer arrays used for indexing use `np.intp`. +This dtype is important for platform independent indexing of other arrays. + +""" +from __future__ import absolute_import + +import numpy as np +from numpy.testing import ( + assert_, +) +from MDAnalysisTests import make_Universe + + +class TestIndexDtype(object): + def setUp(self): + self.u = make_Universe() + + def tearDown(self): + del self.u + + def test_ag_ix(self): + assert_(self.u.atoms.ix.dtype == np.intp) + + def test_rg_ix(self): + assert_(self.u.residues.ix.dtype == np.intp) + + def test_sg_ix(self): + assert_(self.u.segments.ix.dtype == np.intp) + + def test_atom_ix_array(self): + assert_(self.u.atoms[0].ix_array.dtype == np.intp) + + def test_residue_ix_array(self): + assert_(self.u.residues[0].ix_array.dtype == np.intp) + + def test_segment_ix_array(self): + assert_(self.u.segments[0].ix_array.dtype == np.intp) + + def test_atomgroup_indices(self): + assert_(self.u.atoms.indices.dtype == np.intp) + + def test_atomgroup_residue_upshift(self): + assert_(self.u.atoms.resindices.dtype == np.intp) + + def test_atomgroup_segment_upshift(self): + assert_(self.u.atoms.segindices.dtype == np.intp) + + def test_residuegroup_atom_downshift(self): + # downshift arrays are a list (one for each residue) + assert_(all((arr.dtype == np.intp) + for arr in self.u.residues.indices)) + + def test_residuegroup_resindices(self): + assert_(self.u.residues.resindices.dtype == np.intp) + + def test_residuegroup_segment_upshift(self): + assert_(self.u.residues.segindices.dtype == np.intp) + + def test_segmentgroup_atom_downshift(self): + assert_(all((arr.dtype == np.intp) + for arr in self.u.segments.indices)) + + def test_segmentgroup_residue_downshift(self): + assert_(all((arr.dtype == np.intp) + for arr in self.u.segments.resindices)) + + def test_segmentgroup_segindices(self): + assert_(self.u.segments.segindices.dtype == np.intp) From 9a34b0f58fc8213d5dc8caecee00c7b936f72df3 Mon Sep 17 00:00:00 2001 From: Jonathan Barnoud Date: Tue, 13 Jun 2017 19:26:29 +0200 Subject: [PATCH 08/10] Explicitly define __hash__ for groups Groups (AtomGroup, ResidueGroup, SegmentGroup) cannot be stored in sets or used as dict key if they are not hashable. In python 3, the __hash__ method is not defined implicitly anymore when a class has a __eq__ method. Fixes #1397 --- package/CHANGELOG | 7 ++- package/MDAnalysis/core/groups.py | 3 + testsuite/MDAnalysisTests/core/test_groups.py | 63 ++++++++++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 666386fe8ff..66827ab51e0 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,15 +13,18 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -mm/dd/17 - * 0.16.2 richardjgowers, rathann + +mm/dd/17 richardjgowers, rathann, jbarnoud + + * 0.16.2 Enhancements Fixes * fixed GROWriter truncating long resids from the wrong end (Issue #1395) * Fixed dtype of numpy arrays to accomodate 32 bit architectures (Issue #1362) + * Groups are hashable on python 3 (Issue #1397) Changes diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index 963f069d3f2..5c317740ffb 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -423,6 +423,9 @@ def __init__(self, *args): self._u = u self._cache = dict() + def __hash__(self): + return hash((self._u, self.__class__, tuple(self.ix.tolist()))) + def __len__(self): return len(self._ix) diff --git a/testsuite/MDAnalysisTests/core/test_groups.py b/testsuite/MDAnalysisTests/core/test_groups.py index 725dcfc296b..b4731a03516 100644 --- a/testsuite/MDAnalysisTests/core/test_groups.py +++ b/testsuite/MDAnalysisTests/core/test_groups.py @@ -25,6 +25,7 @@ import itertools import numpy as np from numpy.testing import ( + dec, assert_, assert_array_equal, assert_equal, @@ -34,7 +35,7 @@ import six import MDAnalysis as mda -from MDAnalysisTests import make_Universe +from MDAnalysisTests import make_Universe, parser_not_found from MDAnalysisTests.datafiles import PSF, DCD from MDAnalysis.core import groups from MDAnalysis.core.topology import Topology @@ -578,6 +579,8 @@ def test_groupby_int(self): class TestReprs(object): + @dec.skipif(parser_not_found('DCD'), + 'DCD parser not available. Are you using python 3?') def setUp(self): self.u = mda.Universe(PSF, DCD) @@ -919,6 +922,64 @@ def check_operator(op, method, level): yield check_operator, op, method, level +class TestGroupHash(object): + """ + Groups should be hashable. + + See issue #1397 + """ + def test_hash_exists(self): + def _hash_type(group): + assert_(isinstance(hash(group), int)) + + u = make_Universe(size=(3, 3, 3)) + for level in ('atoms', 'residues', 'segments'): + group = getattr(u, level) + yield _hash_type, group + + def test_hash_equality(self): + def _hash_equal(a, b): + assert_equal(hash(a), hash(b)) + + u = make_Universe(size=(3, 3, 3)) + for level in ('atoms', 'residues', 'segments'): + a = getattr(u, level)[0:-1] + b = getattr(u, level)[0:-1] + yield _hash_equal, a, b + + def test_hash_difference(self): + def _hash_not_equal(a, b): + assert_(hash(a) != hash(b)) + + u = make_Universe(size=(3, 3, 3)) + for level in ('atoms', 'residues', 'segments'): + a = getattr(u, level)[:-1] + b = getattr(u, level)[1:] + yield _hash_not_equal, a, b + + def test_hash_difference_cross(self): + def _hash_not_equal(a, b): + assert_(hash(a) != hash(b)) + + u = make_Universe(size=(3, 3, 3)) + levels = ('atoms', 'residues', 'segments') + for level_a, level_b in itertools.permutations(levels, 2): + a = getattr(u, level_a)[0:-1] + b = getattr(u, level_b)[0:-1] + yield _hash_not_equal, a, b + + def test_hash_diff_cross_universe(self): + def _hash_not_equal(a, b): + assert_(hash(a) != hash(b)) + + u = make_Universe(size=(3, 3, 3)) + u2 = make_Universe(size=(3, 3, 3)) + for level in ('atoms', 'residues', 'segments'): + a = getattr(u, level) + b = getattr(u2, level) + yield _hash_not_equal, a, b + + class TestAtomGroup(object): @staticmethod From 059f664f97702cfe6c1164b3aa9e0eb4d7c0897d Mon Sep 17 00:00:00 2001 From: Dominik 'Rathann' Mierzejewski Date: Mon, 19 Jun 2017 01:50:07 +0200 Subject: [PATCH 09/10] fix int type in asserts in TestDownshiftArrays this was missed in eed776181c90646d960eb00a275bffb3ffb36a20 --- testsuite/MDAnalysisTests/core/test_topology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/core/test_topology.py b/testsuite/MDAnalysisTests/core/test_topology.py index 9561369c2fb..9354a875ef1 100644 --- a/testsuite/MDAnalysisTests/core/test_topology.py +++ b/testsuite/MDAnalysisTests/core/test_topology.py @@ -476,12 +476,12 @@ def assert_rows_match(a, b): def test_downshift_dtype_square(self): out = make_downshift_arrays(self.square, self.square_size) assert_(out.dtype == object) - assert_(out[0].dtype == np.int64) + assert_(out[0].dtype == np.intp) def test_downshift_dtype_ragged(self): out = make_downshift_arrays(self.ragged, self.ragged_size) assert_(out.dtype == object) - assert_(out[0].dtype == np.int64) + assert_(out[0].dtype == np.intp) # Check shape and size # Shape should be size N+1 as None is appended From efa4628e16e6293ed08443f2b9771bf0a24d4f0b Mon Sep 17 00:00:00 2001 From: Dominik 'Rathann' Mierzejewski Date: Thu, 22 Jun 2017 13:19:09 +0200 Subject: [PATCH 10/10] fix TRZ file reader on big-endian arches Numpy dtype int (iN) and float (fN) specifiers assume native-endian by default, so force them to be little-endian. Closes #1424. --- package/MDAnalysis/coordinates/TRZ.py | 134 +++++++++++++------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/package/MDAnalysis/coordinates/TRZ.py b/package/MDAnalysis/coordinates/TRZ.py index 58fb5abe819..8b9b5bf4e21 100644 --- a/package/MDAnalysis/coordinates/TRZ.py +++ b/package/MDAnalysis/coordinates/TRZ.py @@ -180,46 +180,46 @@ def __init__(self, trzfilename, n_atoms=None, **kwargs): **self._ts_kwargs) # structured dtype of a single trajectory frame - readarg = str(n_atoms) + 'f4' + readarg = str(n_atoms) + '