Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ Changes
(Issue #1010)
* atoms.translate/rotateby don't accept AtomGroup tuples as
parameters anymore (Issue #1025)
* atoms.rotate by default now uses the centroid as center of
rotation like rotateby (Issue #1022)
* atoms.rotate can be given center of rotation (Issue #1022)
* XTCFile and TRRFile only raise IOError now on error.
* Deprecate usage of MDAnalaysis.core.AtomGroup
* get_writer_for() returns NullWriter when filename=None instead of
Expand Down
29 changes: 13 additions & 16 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,34 +672,31 @@ def translate(self, t):
atomgroup.universe.trajectory.ts.positions[atomgroup.indices] += vector
return self

def rotate(self, R, point=None):
def rotate(self, R, point=(0, 0, 0)):
r"""Apply a rotation matrix `R` to the selection's coordinates.
:math:`\mathsf{R}` is a 3x3 orthogonal matrix that transforms a vector
:math:`\mathbf{x} \rightarrow \mathbf{x}'`:

.. math::

\mathbf{x}' = \mathsf{R}\mathbf{x}

Parameters
----------
R : array_like
3x3 rotation matrix to use for applying rotation.
point : array_like, optional
Center of rotation. If ``None`` then the center of geometry of this
group is used.
Center of rotation

Returns
-------
self : AtomGroup

Notes
-----
By default (``point=None``) the rotation is performed around
the centroid of the group (:meth:`center_of_geometry`). In
order to perform a rotation around, say, the origin, use
``point=[0, 0, 0]``.

:math:`\mathsf{R}` is a 3x3 orthogonal matrix that transforms a vector
:math:`\mathbf{x} \rightarrow \mathbf{x}'`:

.. math::

\mathbf{x}' = \mathsf{R}\mathbf{x}
By default rotates around center of origin ``point=(0, 0, 0)``. To
rotate around center of geometry of the atomgroup use ``ag.rotate(R,
point=ag.centroid)``.

See Also
--------
Expand All @@ -708,10 +705,10 @@ def rotate(self, R, point=None):

"""
R = np.asarray(R)
point = np.asarray(point) if point is not None else self.centroid()
point = np.asarray(point)

self.translate(-point)
# changes the coordinates (in place)
self.translate(-point)
x = self.atoms.unique.universe.trajectory.ts.positions
idx = self.atoms.unique.indices
x[idx] = np.dot(x[idx], R.T)
Expand Down
36 changes: 25 additions & 11 deletions testsuite/MDAnalysisTests/analysis/test_align.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import warnings
import os.path

import MDAnalysis
import MDAnalysis as mda
import MDAnalysis.analysis.align as align
import MDAnalysis.analysis.rms as rms
from MDAnalysis import SelectionError
Expand All @@ -35,7 +35,7 @@
import numpy as np
from nose.plugins.attrib import attr

from MDAnalysisTests.datafiles import PSF, DCD, FASTA
from MDAnalysisTests.datafiles import PSF, DCD, FASTA, ALIGN_BOUND, ALIGN_UNBOUND
from MDAnalysisTests import executable_not_found, parser_not_found, tempdir

# I want to catch all warnings in the tests. If this is not set at the start it
Expand Down Expand Up @@ -94,8 +94,8 @@ class TestAlign(TestCase):
@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.reference = MDAnalysis.Universe(PSF, DCD)
self.universe = mda.Universe(PSF, DCD)
self.reference = mda.Universe(PSF, DCD)
# output is always same as input (=DCD)
self.tempdir = tempdir.TempDir()
self.outfile = os.path.join(self.tempdir.name, 'align_test.dcd')
Expand Down Expand Up @@ -170,7 +170,7 @@ def test_rms_fit_trj(self):
self.reference.trajectory[-1]
align.rms_fit_trj(self.universe, self.reference, select="all",
filename=self.outfile, verbose=False)
fitted = MDAnalysis.Universe(PSF, self.outfile)
fitted = mda.Universe(PSF, self.outfile)
# RMSD against the reference frame
# calculated on Mac OS X x86 with MDA 0.7.2 r689
# VMD: 6.9378711
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_AlignTraj(self):
self.reference.trajectory[-1]
x = align.AlignTraj(self.universe, self.reference,
filename=self.outfile).run()
fitted = MDAnalysis.Universe(PSF, self.outfile)
fitted = mda.Universe(PSF, self.outfile)

rmsd_outfile = os.path.join(self.tempdir.name, 'rmsd')
x.save(rmsd_outfile)
Expand All @@ -220,7 +220,7 @@ def test_AlignTraj(self):
def test_AlignTraj_weighted(self):
x = align.AlignTraj(self.universe, self.reference,
filename=self.outfile, weights='mass').run()
fitted = MDAnalysis.Universe(PSF, self.outfile)
fitted = mda.Universe(PSF, self.outfile)
assert_almost_equal(x.rmsd[0], 0, decimal=3)
assert_almost_equal(x.rmsd[-1], 6.9033, decimal=3)

Expand All @@ -244,7 +244,7 @@ def test_AlignTraj_custom_weights(self):
def test_AlignTraj_custom_mass_weights(self):
x = align.AlignTraj(self.universe, self.reference,
filename=self.outfile, weights=self.reference.atoms.masses).run()
fitted = MDAnalysis.Universe(PSF, self.outfile)
fitted = mda.Universe(PSF, self.outfile)
assert_almost_equal(x.rmsd[0], 0, decimal=3)
assert_almost_equal(x.rmsd[-1], 6.9033, decimal=3)

Expand All @@ -259,7 +259,7 @@ def test_AlignTraj_weights_deprecated(self):
x = align.AlignTraj(self.universe, self.reference,
filename=self.outfile, mass_weighted=True).run()
assert_equal(len(warn), 1)
fitted = MDAnalysis.Universe(PSF, self.outfile)
fitted = mda.Universe(PSF, self.outfile)
assert_almost_equal(x.rmsd[0], 0, decimal=3)
assert_almost_equal(x.rmsd[-1], 6.9033, decimal=3)

Expand All @@ -272,7 +272,7 @@ def test_AlignTraj_partial_fit(self):
# fitting on a partial selection should still write the whole topology
align.AlignTraj(self.universe, self.reference, select='resid 1-20',
filename=self.outfile, weights='mass').run()
MDAnalysis.Universe(PSF, self.outfile)
mda.Universe(PSF, self.outfile)

def test_AlignTraj_in_memory(self):
self.reference.trajectory[-1]
Expand Down Expand Up @@ -313,6 +313,20 @@ def different_atoms():

assert_raises(SelectionError, different_atoms)

@staticmethod
def test_alignto_partial_universe():
u_bound = mda.Universe(ALIGN_BOUND)
u_free = mda.Universe(ALIGN_UNBOUND)
selection = 'segid B'

segB_bound = u_bound.select_atoms(selection)
segB_free = u_free.select_atoms(selection)
segB_free.translate(segB_bound.centroid() - segB_free.centroid())

align.alignto(u_free, u_bound, select=selection)
assert_array_almost_equal(segB_bound.positions, segB_free.positions, decimal=3)



class TestAlignmentProcessing(object):
def setUp(self):
Expand Down Expand Up @@ -351,7 +365,7 @@ def test_fasta2select_ClustalW(self):
err_msg="selection string has unexpected length")

def test_sequence_alignment():
u = MDAnalysis.Universe(PSF)
u = mda.Universe(PSF)
reference = u.atoms
mobile = u.select_atoms("resid 122-159")
aln = align.sequence_alignment(mobile, reference)
Expand Down
17 changes: 13 additions & 4 deletions testsuite/MDAnalysisTests/core/test_atomgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,25 @@ def test_translate(self):
assert_array_almost_equal(diff, disp, decimal=5)

def test_rotate(self):
# ensure that selection isn't centered at 0, 0, 0
self.u.atoms.translate((1, 1, 1))
self.coords += 1

# check identify does nothing
R = np.eye(3)
self.u.atoms.rotate(R)
assert_array_almost_equal(self.u.atoms.positions, self.coords)

vec = np.array([[1, 0, 0], [-1, 0, 0]])
axis = np.array([0, 0, 1])

# check default rotation center is at 0, 0, 0. Changing this center
# will break an unpredictable amount of old code.
ag = self.u.atoms[:2]
ag.positions = vec
ag.positions = np.array([[1, 0, 0], [-1, 0, 0]])
ag.rotate(transformations.rotation_matrix(1, [0, 0, 1])[:3, :3])
assert_array_almost_equal(ag.positions[0], [np.cos(1), np.sin(1), 0])

# check general rotation cases
vec = np.array([[1, 0, 0], [-1, 0, 0]])
axis = np.array([0, 0, 1])
for angle in np.linspace(0, np.pi):
R = transformations.rotation_matrix(angle, axis)
ag.positions = vec.copy()
Expand Down
Binary file not shown.
Binary file not shown.
5 changes: 5 additions & 0 deletions testsuite/MDAnalysisTests/datafiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
"AUX_XVG", "XVG_BAD_NCOL", #for testing .xvg auxiliary reader
"AUX_XVG_LOWF", "AUX_XVG_HIGHF",
"MMTF", "MMTF_gz",
"ALIGN_BOUND", # two component bound system
"ALIGN_UNBOUND", # two component unbound system
]

from pkg_resources import resource_filename
Expand Down Expand Up @@ -362,5 +364,8 @@
MMTF = resource_filename(__name__, 'data/173D.mmtf')
MMTF_gz = resource_filename(__name__, 'data/5KIH.mmtf.gz')

ALIGN_BOUND = resource_filename(__name__, 'data/analysis/align_bound.pdb.gz')
ALIGN_UNBOUND = resource_filename(__name__, 'data/analysis/align_unbound.pdb.gz')
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any other two protein datafiles we already have and could use for this test? I didn't find any skipping over the list.


# This should be the last line: clean up namespace
del resource_filename
2 changes: 1 addition & 1 deletion testsuite/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def dynamic_author_list():
'data/*.xml',
'data/coordinates/*',
'data/*xvg',
'data/*.mmtf', 'data/*.mmtf.gz',
'data/*.mmtf', 'data/*.mmtf.gz', 'data/analysis/*'
],
},
classifiers=CLASSIFIERS,
Expand Down