From 72bba20801e3eea5119bbeab18c5f197f732ae4b Mon Sep 17 00:00:00 2001 From: Bryan Robert Tester Date: Thu, 29 Aug 2019 11:41:50 +0100 Subject: [PATCH 1/4] added RotationMutator --- scanpointgenerator/mutators/__init__.py | 1 + .../mutators/rotationmutator.py | 74 +++++++ tests/test_mutators/test_rotationmutator.py | 197 ++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 scanpointgenerator/mutators/rotationmutator.py create mode 100644 tests/test_mutators/test_rotationmutator.py diff --git a/scanpointgenerator/mutators/__init__.py b/scanpointgenerator/mutators/__init__.py index c691e4d..beaf9da 100644 --- a/scanpointgenerator/mutators/__init__.py +++ b/scanpointgenerator/mutators/__init__.py @@ -14,3 +14,4 @@ ### from scanpointgenerator.mutators.randomoffsetmutator import RandomOffsetMutator +from scanpointgenerator.mutators.rotationmutator import RotationMutator \ No newline at end of file diff --git a/scanpointgenerator/mutators/rotationmutator.py b/scanpointgenerator/mutators/rotationmutator.py new file mode 100644 index 0000000..65859c2 --- /dev/null +++ b/scanpointgenerator/mutators/rotationmutator.py @@ -0,0 +1,74 @@ +### +# Copyright (c) 2019 Diamond Light Source Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Bryan Tester +# +### + +from annotypes import Anno, Union, Array, Sequence +from math import cos, sin, pi +from scanpointgenerator.core import Mutator, Point + +with Anno("Axes to apply rotation to, " + "in the order the offsets should be applied"): + AAxes = Array[str] +UAxes = Union[AAxes, Sequence[str], str] +with Anno("Centre of rotation"): + ACoR = Array[float] +UCoR = Union[ACoR, list] +with Anno("Angle by which to rotate points (in degrees)"): + ARotationAngle = float + + +@Mutator.register_subclass("scanpointgenerator:mutator/RotationMutator:1.0") +class RotationMutator(Mutator): + """Mutator to apply a rotation to the points of an ND + ScanPointGenerator""" + + def __init__(self, axes, angle, centreOfRotation): + # type: (UAxes, ARotationAngle, UCoR) -> None + self.angle = ARotationAngle(angle) + self.axes = AAxes(axes) + self.centreOfRotation = ACoR(centreOfRotation) + assert len(self.axes) == 2, "Can only rotate in the plane of a pair" +\ + "of orthogonal axes" + + def mutate(self, point, idx): + rotated = Point() + rotated.indexes = point.indexes + rotated.lower = point.lower.copy() + rotated.upper = point.upper.copy() + rotated.duration = point.duration + pos = point.positions + rotated.positions = pos.copy() + i = self.axes[0] + j = self.axes[1] + i_off = self.centreOfRotation[0] + j_off = self.centreOfRotation[1] + rad = pi*(self.angle/180.0) # convert degrees to radians + rotated.positions[i] = (cos(rad) * (pos[i] - i_off) - sin(rad) * (pos[j] - j_off)) + i_off + rotated.positions[j] = (cos(rad) * (pos[j] - j_off) + sin(rad) * (pos[i] - i_off)) + j_off + if (i in point.lower and i in point.upper) or (j in point.lower and j in point.upper): + i_low = pos[i] + i_up = pos[i] + j_low = pos[j] + j_up = pos[j] + if j in point.lower: + j_low = point.lower[j] + if j in point.upper: + j_up = point.upper[j] + if i in point.lower: + i_low = point.lower[i] + if i in point.upper: + i_up = point.upper[i] + rotated.upper[i] = (cos(rad) * (i_up - i_off) - sin(rad) * (j_up - j_off)) + i_off + rotated.upper[j] = (cos(rad) * (j_up - j_off) + sin(rad) * (i_up - i_off)) + j_off + rotated.lower[i] = (cos(rad) * (i_low - i_off) - sin(rad) * (j_low - j_off)) + i_off + rotated.lower[j] = (cos(rad) * (j_low - j_off) + sin(rad) * (i_low - i_off)) + j_off + return rotated diff --git a/tests/test_mutators/test_rotationmutator.py b/tests/test_mutators/test_rotationmutator.py new file mode 100644 index 0000000..25a32dc --- /dev/null +++ b/tests/test_mutators/test_rotationmutator.py @@ -0,0 +1,197 @@ +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import unittest + +from test_util import ScanPointGeneratorTest +from scanpointgenerator.compat import range_ +from scanpointgenerator.mutators import RotationMutator +from scanpointgenerator import Point + +from pkg_resources import require +require("mock") +from mock import MagicMock + + +class RandomOffsetMutatorTest(ScanPointGeneratorTest): + + def test_init(self): + m = RotationMutator(["x", "y"], 30, [0., 0.]) + self.assertEqual(30, m.angle) + + def test_init_fails_for_invalid_axes(self): + self.assertRaises(AssertionError, RotationMutator, ["x"], 30, [0.]) + self.assertRaises(AssertionError, RotationMutator, ["x", "y", "z"], 30, [0., 0., 0.]) + + def test_mutate_simple(self): + def point_gen(): + for j in range_(10): + for k in range_(10): + pt = Point() + pt.indexes = [j, k] + pt.positions = {"x": j/10., "y": k/10.} + pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} + pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} + yield pt + m = RotationMutator(["x", "y"], 30, [0., 0.]) + original = [p for p in point_gen()] + mutated = [m.mutate(p, i) for i, p in enumerate(point_gen())] + for o, m in zip(original, mutated): + op_x, mp_x = o.positions["x"], m.positions["x"] + op_y, mp_y = o.positions["y"], m.positions["y"] + ou_x, mu_x = o.upper["x"], m.upper["x"] + ou_y, mu_y = o.upper["y"], m.upper["y"] + ol_x, ml_x = o.lower["x"], m.lower["x"] + ol_y, ml_y = o.lower["y"], m.lower["y"] + # self.assertNotEqual(op_x, mp_x) + # self.assertNotEqual(op_y, mp_y) + self.assertTrue((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2) < 1e-12) + + # check distance between consecutive points is preserved + for i in range(len(original) - 1): + o_step = (original[i + 1].positions["x"] - original[i].positions["x"]) ** 2 + \ + (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 + m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ + (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 + self.assertTrue(abs(o_step - m_step) < 1e-12) + + def test_mutate_cor(self): + def point_gen(): + for j in range_(10): + for k in range_(10): + pt = Point() + pt.indexes = [j, k] + pt.positions = {"x": j/10., "y": k/10.} + pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} + pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} + yield pt + CoR = [2., 3.] + m = RotationMutator(["x", "y"], 30, CoR) + original = [p for p in point_gen()] + mutated = [m.mutate(p, i) for i, p in enumerate(point_gen())] + for o, m in zip(original, mutated): + op_x, mp_x = o.positions["x"], m.positions["x"] + op_y, mp_y = o.positions["y"], m.positions["y"] + ou_x, mu_x = o.upper["x"], m.upper["x"] + ou_y, mu_y = o.upper["y"], m.upper["y"] + ol_x, ml_x = o.lower["x"], m.lower["x"] + ol_y, ml_y = o.lower["y"], m.lower["y"] + # self.assertNotEqual(op_x, mp_x) + # self.assertNotEqual(op_y, mp_y) + self.assertTrue( + ((op_x - CoR[0]) ** 2 + (op_y - CoR[1]) ** 2) - ((mp_x - CoR[0]) ** 2 + (mp_y - CoR[1]) ** 2) < 1e-12) + + # check distance between consecutive points is preserved + for i in range(len(original) - 1): + o_step = (original[i + 1].positions["x"] - original[i].positions["x"]) ** 2 + \ + (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 + m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ + (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 + self.assertTrue(abs(o_step - m_step) < 1e-12) + + def test_mutate_90_degrees(self): + def point_gen(): + for j in range_(10): + for k in range_(10): + pt = Point() + pt.indexes = [j, k] + pt.positions = {"x": j/10., "y": k/10.} + pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} + pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} + yield pt + m = RotationMutator(["x", "y"], 90, [0., 0.]) + original = [p for p in point_gen()] + mutated = [m.mutate(p, i) for i, p in enumerate(point_gen())] + for o, m in zip(original, mutated): + op_x, mp_x = o.positions["x"], m.positions["x"] + op_y, mp_y = o.positions["y"], m.positions["y"] + ou_x, mu_x = o.upper["x"], m.upper["x"] + ou_y, mu_y = o.upper["y"], m.upper["y"] + ol_x, ml_x = o.lower["x"], m.lower["x"] + ol_y, ml_y = o.lower["y"], m.lower["y"] + # self.assertNotEqual(op_x, mp_x) + # self.assertNotEqual(op_y, mp_y) + self.assertTrue((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2) < 1e-12) + # rotate 90 degrees, mp_y = op_x, mp_x = -op_y + self.assertTrue(abs(op_x - mp_y) < 1e-12) + self.assertTrue(abs(op_y + mp_x) < 1e-12) + + # check distance between consecutive points is preserved + for i in range(len(original) - 1): + o_step = (original[i + 1].positions["x"] - original[i].positions["x"]) ** 2 + \ + (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 + m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ + (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 + self.assertTrue(abs(o_step - m_step) < 1e-12) + + def test_mutate_twice_opposite(self): + def point_gen(): + for j in range_(10): + for k in range_(10): + pt = Point() + pt.indexes = [j, k] + pt.positions = {"x": j/10., "y": k/10.} + pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} + pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} + yield pt + m1 = RotationMutator(["x", "y"], 30, [0., 0.]) + m2 = RotationMutator(["x", "y"], -30, [0., 0.]) + original = [p for p in point_gen()] + mutated1 = [m1.mutate(p, i) for i, p in enumerate(point_gen())] + mutated2 = [m2.mutate(p, i) for i, p in enumerate(mutated1)] + for o, m in zip(original, mutated2): + op_x, mp_x = o.positions["x"], m.positions["x"] + op_y, mp_y = o.positions["y"], m.positions["y"] + ou_x, mu_x = o.upper["x"], m.upper["x"] + ou_y, mu_y = o.upper["y"], m.upper["y"] + ol_x, ml_x = o.lower["x"], m.lower["x"] + ol_y, ml_y = o.lower["y"], m.lower["y"] + # should be equal within floating point error + self.assertTrue(abs(mp_x - op_x) < 1e-12) + self.assertTrue(abs(mu_x - ou_x) < 1e-12) + self.assertTrue(abs(ml_x - ol_x) < 1e-12) + self.assertTrue(abs(mp_y - op_y) < 1e-12) + self.assertTrue(abs(mu_y - ou_y) < 1e-12) + self.assertTrue(abs(ml_y - ol_y) < 1e-12) + + +class TestSerialisation(unittest.TestCase): + + def setUp(self): + self.l = MagicMock() + self.l_dict = MagicMock() + self.centreOfRotation = [0., 0.] + self.m = RotationMutator(["x", "y"], 45, self.centreOfRotation) + + def test_to_dict(self): + self.l.to_dict.return_value = self.l_dict + + expected_dict = dict() + expected_dict['typeid'] = "scanpointgenerator:mutator/RotationMutator:1.0" + expected_dict['angle'] = 45 + expected_dict['axes'] = ["x", "y"] + expected_dict['centreOfRotation'] = self.centreOfRotation + + d = self.m.to_dict() + + self.assertEqual(expected_dict, d) + + def test_from_dict(self): + + _dict = dict() + _dict['angle'] = 45 + _dict['axes'] = ["x", "y"] + _dict['centreOfRotation'] = self.centreOfRotation + + units_dict = dict() + units_dict['x'] = 'mm' + units_dict['y'] = 'mm' + + m = RotationMutator.from_dict(_dict) + + self.assertEqual(1, m.seed) + self.assertEqual(self.centreOfRotation, m.centreOfRotation) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From fcc4090824e5e07b55c5cb3d8a269165dc6bd771 Mon Sep 17 00:00:00 2001 From: Bryan Robert Tester Date: Thu, 29 Aug 2019 13:59:20 +0100 Subject: [PATCH 2/4] improved unit tests --- tests/test_mutators/test_rotationmutator.py | 61 +++++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/tests/test_mutators/test_rotationmutator.py b/tests/test_mutators/test_rotationmutator.py index 25a32dc..f00e191 100644 --- a/tests/test_mutators/test_rotationmutator.py +++ b/tests/test_mutators/test_rotationmutator.py @@ -12,6 +12,7 @@ require("mock") from mock import MagicMock +float_error_tolerance = 1e-12 class RandomOffsetMutatorTest(ScanPointGeneratorTest): @@ -30,6 +31,7 @@ def point_gen(): pt = Point() pt.indexes = [j, k] pt.positions = {"x": j/10., "y": k/10.} + # TODO: generate upper & lower appropriately pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} yield pt @@ -39,21 +41,29 @@ def point_gen(): for o, m in zip(original, mutated): op_x, mp_x = o.positions["x"], m.positions["x"] op_y, mp_y = o.positions["y"], m.positions["y"] + # TODO: test upper & lower appropriately...how should they be? ou_x, mu_x = o.upper["x"], m.upper["x"] ou_y, mu_y = o.upper["y"], m.upper["y"] ol_x, ml_x = o.lower["x"], m.lower["x"] ol_y, ml_y = o.lower["y"], m.lower["y"] # self.assertNotEqual(op_x, mp_x) # self.assertNotEqual(op_y, mp_y) - self.assertTrue((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2) < 1e-12) + self.assertTrue(abs((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2)) < float_error_tolerance) - # check distance between consecutive points is preserved for i in range(len(original) - 1): + # check distance between consecutive points is preserved o_step = (original[i + 1].positions["x"] - original[i].positions["x"]) ** 2 + \ (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 - self.assertTrue(abs(o_step - m_step) < 1e-12) + self.assertTrue(abs(o_step - m_step) < float_error_tolerance) + + # check angle between points preserved + o_dot = (original[i + 1].positions["x"] * original[i].positions["x"]) + \ + (original[i + 1].positions["y"] * original[i].positions["y"]) + m_dot = (mutated[i + 1].positions["x"] * mutated[i].positions["x"]) + \ + (mutated[i + 1].positions["y"] * mutated[i].positions["y"]) + self.assertTrue(abs(o_dot - m_dot) < float_error_tolerance) def test_mutate_cor(self): def point_gen(): @@ -69,6 +79,12 @@ def point_gen(): m = RotationMutator(["x", "y"], 30, CoR) original = [p for p in point_gen()] mutated = [m.mutate(p, i) for i, p in enumerate(point_gen())] + o_r = [] + m_r = [] + o_rel_x = [] + o_rel_y = [] + m_rel_x = [] + m_rel_y = [] for o, m in zip(original, mutated): op_x, mp_x = o.positions["x"], m.positions["x"] op_y, mp_y = o.positions["y"], m.positions["y"] @@ -78,16 +94,27 @@ def point_gen(): ol_y, ml_y = o.lower["y"], m.lower["y"] # self.assertNotEqual(op_x, mp_x) # self.assertNotEqual(op_y, mp_y) - self.assertTrue( - ((op_x - CoR[0]) ** 2 + (op_y - CoR[1]) ** 2) - ((mp_x - CoR[0]) ** 2 + (mp_y - CoR[1]) ** 2) < 1e-12) + o_rel_x += [op_x - CoR[0]] + o_rel_y += [op_y - CoR[1]] + m_rel_x += [mp_x - CoR[0]] + m_rel_y += [mp_y - CoR[1]] + o_r += [(op_x - CoR[0]) ** 2 + (op_y - CoR[1]) ** 2] + m_r += [(mp_x - CoR[0]) ** 2 + (mp_y - CoR[1]) ** 2] + + self.assertTrue(abs(m_r[-1] - o_r[-1]) < float_error_tolerance) - # check distance between consecutive points is preserved for i in range(len(original) - 1): + # check distance between consecutive points is preserved o_step = (original[i + 1].positions["x"] - original[i].positions["x"]) ** 2 + \ (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 - self.assertTrue(abs(o_step - m_step) < 1e-12) + self.assertTrue(abs(o_step - m_step) < float_error_tolerance) + + # check angle between points preserved + o_dot = (o_rel_x[i + 1] * o_rel_x[i]) + (o_rel_y[i + 1] * o_rel_y[i]) + m_dot = (m_rel_x[i + 1] * m_rel_x[i]) + (m_rel_y[i + 1] * m_rel_y[i]) + self.assertTrue(abs(o_dot - m_dot) < float_error_tolerance) def test_mutate_90_degrees(self): def point_gen(): @@ -111,10 +138,10 @@ def point_gen(): ol_y, ml_y = o.lower["y"], m.lower["y"] # self.assertNotEqual(op_x, mp_x) # self.assertNotEqual(op_y, mp_y) - self.assertTrue((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2) < 1e-12) + self.assertTrue(abs((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2)) < float_error_tolerance) # rotate 90 degrees, mp_y = op_x, mp_x = -op_y - self.assertTrue(abs(op_x - mp_y) < 1e-12) - self.assertTrue(abs(op_y + mp_x) < 1e-12) + self.assertTrue(abs(op_x - mp_y) < float_error_tolerance) + self.assertTrue(abs(op_y + mp_x) < float_error_tolerance) # check distance between consecutive points is preserved for i in range(len(original) - 1): @@ -122,7 +149,7 @@ def point_gen(): (original[i + 1].positions["y"] - original[i].positions["y"]) ** 2 m_step = (mutated[i + 1].positions["x"] - mutated[i].positions["x"]) ** 2 + \ (mutated[i + 1].positions["y"] - mutated[i].positions["y"]) ** 2 - self.assertTrue(abs(o_step - m_step) < 1e-12) + self.assertTrue(abs(o_step - m_step) < float_error_tolerance) def test_mutate_twice_opposite(self): def point_gen(): @@ -147,12 +174,12 @@ def point_gen(): ol_x, ml_x = o.lower["x"], m.lower["x"] ol_y, ml_y = o.lower["y"], m.lower["y"] # should be equal within floating point error - self.assertTrue(abs(mp_x - op_x) < 1e-12) - self.assertTrue(abs(mu_x - ou_x) < 1e-12) - self.assertTrue(abs(ml_x - ol_x) < 1e-12) - self.assertTrue(abs(mp_y - op_y) < 1e-12) - self.assertTrue(abs(mu_y - ou_y) < 1e-12) - self.assertTrue(abs(ml_y - ol_y) < 1e-12) + self.assertTrue(abs(mp_x - op_x) < float_error_tolerance) + self.assertTrue(abs(mu_x - ou_x) < float_error_tolerance) + self.assertTrue(abs(ml_x - ol_x) < float_error_tolerance) + self.assertTrue(abs(mp_y - op_y) < float_error_tolerance) + self.assertTrue(abs(mu_y - ou_y) < float_error_tolerance) + self.assertTrue(abs(ml_y - ol_y) < float_error_tolerance) class TestSerialisation(unittest.TestCase): From ab57e6658e731bf7af5d725308fab4f8a1cdc2be Mon Sep 17 00:00:00 2001 From: Bryan Robert Tester Date: Thu, 29 Aug 2019 16:13:48 +0100 Subject: [PATCH 3/4] fixed unit tests --- tests/test_mutators/test_rotationmutator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mutators/test_rotationmutator.py b/tests/test_mutators/test_rotationmutator.py index f00e191..a8c45ba 100644 --- a/tests/test_mutators/test_rotationmutator.py +++ b/tests/test_mutators/test_rotationmutator.py @@ -216,7 +216,7 @@ def test_from_dict(self): m = RotationMutator.from_dict(_dict) - self.assertEqual(1, m.seed) + self.assertEqual(45, m.angle) self.assertEqual(self.centreOfRotation, m.centreOfRotation) From 9696aa271141270e10faaa2696c7d55237fe31a3 Mon Sep 17 00:00:00 2001 From: Bryan Robert Tester Date: Mon, 23 Sep 2019 12:24:11 +0100 Subject: [PATCH 4/4] code tweaks --- .../mutators/rotationmutator.py | 18 +++--- tests/test_mutators/test_rotationmutator.py | 59 ++++++++++--------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/scanpointgenerator/mutators/rotationmutator.py b/scanpointgenerator/mutators/rotationmutator.py index 65859c2..039378c 100644 --- a/scanpointgenerator/mutators/rotationmutator.py +++ b/scanpointgenerator/mutators/rotationmutator.py @@ -21,7 +21,7 @@ UAxes = Union[AAxes, Sequence[str], str] with Anno("Centre of rotation"): ACoR = Array[float] -UCoR = Union[ACoR, list] +UCoR = Union[ACoR, Sequence[float]] with Anno("Angle by which to rotate points (in degrees)"): ARotationAngle = float @@ -36,8 +36,9 @@ def __init__(self, axes, angle, centreOfRotation): self.angle = ARotationAngle(angle) self.axes = AAxes(axes) self.centreOfRotation = ACoR(centreOfRotation) - assert len(self.axes) == 2, "Can only rotate in the plane of a pair" +\ - "of orthogonal axes" + msg = "Can only rotate in the plane of a pair of orthogonal axes" + assert len(self.axes) == 2, msg + assert len(self.centreOfRotation) == 2, msg def mutate(self, point, idx): rotated = Point() @@ -52,13 +53,16 @@ def mutate(self, point, idx): i_off = self.centreOfRotation[0] j_off = self.centreOfRotation[1] rad = pi*(self.angle/180.0) # convert degrees to radians - rotated.positions[i] = (cos(rad) * (pos[i] - i_off) - sin(rad) * (pos[j] - j_off)) + i_off - rotated.positions[j] = (cos(rad) * (pos[j] - j_off) + sin(rad) * (pos[i] - i_off)) + j_off - if (i in point.lower and i in point.upper) or (j in point.lower and j in point.upper): + rotated.positions[i] = (cos(rad) * (pos[i] - i_off) + - sin(rad) * (pos[j] - j_off)) + i_off + rotated.positions[j] = (cos(rad) * (pos[j] - j_off) + + sin(rad) * (pos[i] - i_off)) + j_off + if (i in point.lower and i in point.upper)\ + or (j in point.lower and j in point.upper): i_low = pos[i] i_up = pos[i] - j_low = pos[j] j_up = pos[j] + j_low = pos[j] if j in point.lower: j_low = point.lower[j] if j in point.upper: diff --git a/tests/test_mutators/test_rotationmutator.py b/tests/test_mutators/test_rotationmutator.py index a8c45ba..853ee6a 100644 --- a/tests/test_mutators/test_rotationmutator.py +++ b/tests/test_mutators/test_rotationmutator.py @@ -14,7 +14,24 @@ float_error_tolerance = 1e-12 -class RandomOffsetMutatorTest(ScanPointGeneratorTest): +def make_point(j,k): + pt = Point() + pt.indexes = [j, k] + if k == 9: + pt.positions = {"x": j / 10., "y": k / 10.} + pt.lower = {"x": j / 10., "y": (k - 0.5) / 10.} + pt.upper = {"x": (j + 0.5) / 10., "y": 0.45} + elif k == 0: + pt.positions = {"x": j / 10., "y": k / 10.} + pt.lower = {"x": (j - 0.5) / 10., "y": 0.45} + pt.upper = {"x": j / 10., "y": (k + 0.5) / 10.} + else: + pt.positions = {"x": j / 10., "y": k / 10.} + pt.lower = {"x": j / 10., "y": (k - 0.5) / 10.} + pt.upper = {"x": j / 10., "y": (k + 0.5) / 10.} + return pt + +class RotationMutatorTest(ScanPointGeneratorTest): def test_init(self): m = RotationMutator(["x", "y"], 30, [0., 0.]) @@ -28,12 +45,7 @@ def test_mutate_simple(self): def point_gen(): for j in range_(10): for k in range_(10): - pt = Point() - pt.indexes = [j, k] - pt.positions = {"x": j/10., "y": k/10.} - # TODO: generate upper & lower appropriately - pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} - pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} + pt = make_point(j, k) yield pt m = RotationMutator(["x", "y"], 30, [0., 0.]) original = [p for p in point_gen()] @@ -41,13 +53,10 @@ def point_gen(): for o, m in zip(original, mutated): op_x, mp_x = o.positions["x"], m.positions["x"] op_y, mp_y = o.positions["y"], m.positions["y"] - # TODO: test upper & lower appropriately...how should they be? ou_x, mu_x = o.upper["x"], m.upper["x"] ou_y, mu_y = o.upper["y"], m.upper["y"] ol_x, ml_x = o.lower["x"], m.lower["x"] ol_y, ml_y = o.lower["y"], m.lower["y"] - # self.assertNotEqual(op_x, mp_x) - # self.assertNotEqual(op_y, mp_y) self.assertTrue(abs((op_x**2 + op_y**2) - (mp_x**2 + mp_y**2)) < float_error_tolerance) for i in range(len(original) - 1): @@ -65,16 +74,18 @@ def point_gen(): (mutated[i + 1].positions["y"] * mutated[i].positions["y"]) self.assertTrue(abs(o_dot - m_dot) < float_error_tolerance) + # check bounds still correct + self.assertEqual(original[i + 1].lower["x"], original[i].upper["x"]) + self.assertEqual(original[i + 1].lower["y"], original[i].upper["y"]) + self.assertEqual(mutated[i + 1].lower["x"], mutated[i].upper["x"]) + self.assertEqual(mutated[i + 1].lower["y"], mutated[i].upper["y"]) + def test_mutate_cor(self): def point_gen(): for j in range_(10): for k in range_(10): - pt = Point() - pt.indexes = [j, k] - pt.positions = {"x": j/10., "y": k/10.} - pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} - pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} - yield pt + pt = make_point(j, k) + yield pt CoR = [2., 3.] m = RotationMutator(["x", "y"], 30, CoR) original = [p for p in point_gen()] @@ -120,12 +131,8 @@ def test_mutate_90_degrees(self): def point_gen(): for j in range_(10): for k in range_(10): - pt = Point() - pt.indexes = [j, k] - pt.positions = {"x": j/10., "y": k/10.} - pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} - pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} - yield pt + pt = make_point(j, k) + yield pt m = RotationMutator(["x", "y"], 90, [0., 0.]) original = [p for p in point_gen()] mutated = [m.mutate(p, i) for i, p in enumerate(point_gen())] @@ -155,12 +162,8 @@ def test_mutate_twice_opposite(self): def point_gen(): for j in range_(10): for k in range_(10): - pt = Point() - pt.indexes = [j, k] - pt.positions = {"x": j/10., "y": k/10.} - pt.lower = {"x": (j-0.5)/10., "y": (k-0.5)/10.} - pt.upper = {"x": (j+0.5)/10., "y": (k+0.5)/10.} - yield pt + pt = make_point(j, k) + yield pt m1 = RotationMutator(["x", "y"], 30, [0., 0.]) m2 = RotationMutator(["x", "y"], -30, [0., 0.]) original = [p for p in point_gen()]