From 1e9e227e20aaa5041b06620a307614f4ff21e884 Mon Sep 17 00:00:00 2001 From: Sam Ireland Date: Wed, 1 Nov 2017 13:44:50 +0000 Subject: [PATCH] Remake transformation and drop matrices/geometrica dependencies --- atomium/structures/atoms.py | 23 ++++++++++ atomium/structures/molecules.py | 21 ++++------ requirements.txt | 4 +- tests/integration/test_structures.py | 3 +- .../structure_tests/test_atomic_structures.py | 42 +++++-------------- tests/unit/structure_tests/test_atoms.py | 29 +++++++++++++ 6 files changed, 74 insertions(+), 48 deletions(-) diff --git a/atomium/structures/atoms.py b/atomium/structures/atoms.py index df609273..fc020743 100644 --- a/atomium/structures/atoms.py +++ b/atomium/structures/atoms.py @@ -141,6 +141,29 @@ def location(self): return (self._x, self._y, self._z) + def translate(self, dx, dy, dz): + """Translates an atom in 3D space. + + :param float dx: The distance to move in the x direction. + :param float dy: The distance to move in the y direction. + :param float dz: The distance to move in the z direction.""" + + self._x += dx + self._y += dy + self._z += dz + + + def rotate(self, angle, axis): + """Rotates an atom in 3D space. + + :param float angle: Angle in radians. + :param str axis: The axis to rotate around.""" + + vector = Vector(self._x, self._y, self._z) + vector.rotate(angle, axis) + self._x, self._y, self._z = vector.values() + + def atom_id(self): """Returns the atom's unique integer ID. diff --git a/atomium/structures/molecules.py b/atomium/structures/molecules.py index 195c67da..1a345df8 100644 --- a/atomium/structures/molecules.py +++ b/atomium/structures/molecules.py @@ -3,7 +3,6 @@ from collections import Counter import weakref from math import sqrt -from geometrica import translate, rotate from .atoms import Atom class AtomicStructure: @@ -125,7 +124,7 @@ def pairwise_atoms(self): the number of returned pairs will be a triangle number. :rtype: ``list``""" - + atoms = list(self.atoms()) for a_index in range(len(atoms) - 1): for o_index in range(a_index + 1, len(atoms)): @@ -166,23 +165,19 @@ def translate(self, dx, dy, dz): :param Number dy: The distance to move in the y direction. :param Number dz: The distance to move in the z direction.""" - atoms = list(self.atoms()) - points = translate(atoms, dx, dy, dz) - for index, atom in enumerate(atoms): - atom._x, atom._y, atom._z = points[index] + for atom in self.atoms(): + atom.translate(dx, dy, dz) - def rotate(self, axis, angle): + def rotate(self, angle, axis): """Rotates the structure about an axis, updating all atom coordinates accordingly. - :param str axis: The axis to rotate around. Can only be 'x', 'y' or 'z'. - :param Number angle: The angle in degrees. Rotation is right handed.""" + :param Number angle: The angle in radians. + :param str axis: The axis to rotate around. Can only be 'x', 'y' or 'z'.""" - atoms = list(self.atoms()) - points = rotate(atoms, axis, angle) - for index, atom in enumerate(atoms): - atom._x, atom._y, atom._z = points[index] + for atom in self.atoms(): + atom.rotate(angle, axis) def center_of_mass(self): diff --git a/requirements.txt b/requirements.txt index e5a6b7f1..7109db6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -geometrica -matrices -points +points==0.3 requests python-coveralls diff --git a/tests/integration/test_structures.py b/tests/integration/test_structures.py index f9ed916b..98bb38cb 100644 --- a/tests/integration/test_structures.py +++ b/tests/integration/test_structures.py @@ -1,3 +1,4 @@ +import math from tests.integration.base import IntegratedTest from atomium.structures import Model, Atom, Residue, Chain, Molecule import atomium @@ -56,7 +57,7 @@ def test_can_manipulate_model(self): model.translate(-12, -11.5, -1.5) self.assertEqual((atom1.x(), atom1.y(), atom1.z()), (0, 0, 0)) self.assertEqual((atom2.x(), atom2.y(), atom2.z()), (0.5, -1.5, 0.5)) - model.rotate("x", 180) + model.rotate(math.pi, "x") self.assertEqual((atom1.x(), atom1.y(), atom1.z()), (0, 0, 0)) # Atoms can be removed diff --git a/tests/unit/structure_tests/test_atomic_structures.py b/tests/unit/structure_tests/test_atomic_structures.py index b8403ce8..c3c114fa 100644 --- a/tests/unit/structure_tests/test_atomic_structures.py +++ b/tests/unit/structure_tests/test_atomic_structures.py @@ -243,44 +243,24 @@ def test_can_get_formula(self, mock_atoms): class AtomicStructureTranslationTests(AtomicStructureTest): @patch("atomium.structures.molecules.AtomicStructure.atoms") - @patch("atomium.structures.molecules.translate") - def test_translation_uses_geometrica(self, mock_translate, mock_atoms): - mock_atoms.return_value = [self.atoms[0]] + [self.atoms[-1]] - structure = AtomicStructure(self.atom1, self.atom3) - mock_translate.return_value = [(6, 6, 1), (9, 9, 4)] - self.atom1._x, self.atom1._y, self.atom1._z = 1, 2, 3 - self.atom3._x, self.atom3._y, self.atom3._z = 4, 5, 6 + def test_structure_translation(self, mock_atoms): + mock_atoms.return_value = set(self.atoms) + structure = AtomicStructure(self.atom1, self.atom2, self.atom3) structure.translate(5, 4, -2) - mock_translate.assert_called_with([self.atoms[0]] + [self.atoms[-1]], 5, 4, -2) - self.assertEqual( - set([ - (self.atom1._x, self.atom1._y, self.atom1._z), - (self.atom3._x, self.atom3._y, self.atom3._z) - ]), - set([(6, 6, 1), (9, 9, 4)]) - ) + for atom in self.atoms: + atom.translate.assert_called_with(5, 4, -2) class AtomicStructureRotationTests(AtomicStructureTest): @patch("atomium.structures.molecules.AtomicStructure.atoms") - @patch("atomium.structures.molecules.rotate") - def test_rotation_uses_geometrica(self, mock_rotate, mock_atoms): - mock_atoms.return_value = [self.atoms[0]] + [self.atoms[-1]] - structure = AtomicStructure(self.atom1, self.atom3) - mock_rotate.return_value = [(6, 6, 1), (9, 9, 4)] - self.atom1._x, self.atom1._y, self.atom1._z = 1, 2, 3 - self.atom3._x, self.atom3._y, self.atom3._z = 4, 5, 6 - structure.rotate("x", 90) - mock_rotate.assert_called_with([self.atoms[0]] + [self.atoms[-1]], "x", 90) - self.assertEqual( - set([ - (self.atom1._x, self.atom1._y, self.atom1._z), - (self.atom3._x, self.atom3._y, self.atom3._z) - ]), - set([(6, 6, 1), (9, 9, 4)]) - ) + def test_structure_rotation(self, mock_atoms): + mock_atoms.return_value = set(self.atoms) + structure = AtomicStructure(self.atom1, self.atom2, self.atom3) + structure.rotate(0.1, "z") + for atom in self.atoms: + atom.rotate.assert_called_with(0.1, "z") diff --git a/tests/unit/structure_tests/test_atoms.py b/tests/unit/structure_tests/test_atoms.py index 2256bb4d..094017c5 100644 --- a/tests/unit/structure_tests/test_atoms.py +++ b/tests/unit/structure_tests/test_atoms.py @@ -210,6 +210,35 @@ def test_atom_location(self): +class AtomTranslationTests(TestCase): + + def test_can_translate_atoms(self): + atom = Atom("C", 20, 30, 50) + atom.translate(5, -4, 12) + self.assertEqual(atom._x, 25) + self.assertEqual(atom._y, 26) + self.assertEqual(atom._z, 62) + + + +class AtomRotationTEsts(TestCase): + + @patch("atomium.structures.atoms.Vector") + def test_can_rotate_atoms(self, mock_vector): + v = Mock() + mock_vector.return_value = v + v.values.return_value = (10, 20, 30) + atom = Atom("C", 20, 30, 50) + atom.rotate(0.5, "y") + mock_vector.assert_called_with(20, 30, 50) + v.rotate.assert_called_with(0.5, "y") + self.assertEqual(atom._x, 10) + self.assertEqual(atom._y, 20) + self.assertEqual(atom._z, 30) + + + + class AtomIdTests(TestCase): def test_id_property(self):