From 34fa092aa85275311608a11c123fd23ae5d8eeb5 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 12 Sep 2018 16:39:33 -0400 Subject: [PATCH 01/48] added readme --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..4468111e9 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Rotations From de2cd127829ea30831f42b94fa48202486e7bba0 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 12 Sep 2018 16:58:16 -0400 Subject: [PATCH 02/48] added in most of the code --- rotations.py | 216 +++++++++++++++++++++++++++++++++++ vector_calculations.py | 251 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 467 insertions(+) create mode 100644 rotations.py create mode 100644 vector_calculations.py diff --git a/rotations.py b/rotations.py new file mode 100644 index 000000000..1ba64b67e --- /dev/null +++ b/rotations.py @@ -0,0 +1,216 @@ +""" +A set of rotaiton utilites +""" + +from __future__ import (division, print_function, absolute_import, + unicode_literals) +import numpy as np +from vector_calculations import * + + +__all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', + 'rotation_matrices_from_vectors', 'random_perpendicular_directions'] +__author__ = ['Duncan Campbell', 'Andrew Hearin'] + + +def rotate_vector_collection(rotation_matrices, vectors, optimize=False): + r""" + Given a collection of rotation matrices and a collection of 3d vectors, + apply each matrix to rotate the corresponding vector. + + Parameters + ---------- + rotation_matrices : ndarray + Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices. + If an array of shape (3, 3) is passed, all the vectors + are rotated using the same rotation matrix. + + vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Examples + -------- + In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. + We'll use the `rotation_matrices_from_vectors` function to generate the + rotation matrices that rotate each `v0` into the corresponding `v1`. + Then we'll use the `rotate_vector_collection` function to apply each + rotation, and verify that we recover each of the `v1`. + + >>> npts = int(1e4) + >>> v0 = normalized_vectors(np.random.random((npts, 3))) + >>> v1 = normalized_vectors(np.random.random((npts, 3))) + >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) + >>> v2 = rotate_vector_collection(rotation_matrices, v0) + >>> assert np.allclose(v1, v2) + """ + + # apply same rotation matrix to all vectors + if np.shape(rotation_matrices) == (3, 3): + return np.dot(rotation_matrices, vectors.T).T + # rotate each vector by associated rotation matrix + else: + try: + return np.einsum('ijk,ik->ij', rotation_matrices, vectors, optimize=optimize) + except TypeError: + return np.einsum('ijk,ik->ij', rotation_matrices, vectors) + + +def random_rotation(vectors): + r""" + Apply a random rotation to a set of 3d vectors. + + Parameters + ---------- + vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + """ + + ran_angle = np.random.random(size=1)*(np.pi) + ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + + return rotate_vector_collection(ran_rot, vectors) + + +def rotation_matrices_from_angles(angles, directions): + r""" + Calculate a collection of rotation matrices defined by + an input collection of rotation angles and rotation axes. + + Parameters + ---------- + angles : ndarray + Numpy array of shape (npts, ) storing a collection of rotation angles + + directions : ndarray + Numpy array of shape (npts, 3) storing a collection of rotation axes in 3d + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices + + Examples + -------- + >>> npts = int(1e4) + >>> angles = np.random.uniform(-np.pi/2., np.pi/2., npts) + >>> directions = np.random.random((npts, 3)) + >>> rotation_matrices = rotation_matrices_from_angles(angles, directions) + + Notes + ----- + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 3d vectors + + """ + directions = normalized_vectors(directions) + angles = np.atleast_1d(angles) + npts = directions.shape[0] + + sina = np.sin(angles) + cosa = np.cos(angles) + + R1 = np.zeros((npts, 3, 3)) + R1[:, 0, 0] = cosa + R1[:, 1, 1] = cosa + R1[:, 2, 2] = cosa + + R2 = directions[..., None] * directions[:, None, :] + R2 = R2*np.repeat(1.-cosa, 9).reshape((npts, 3, 3)) + + directions *= sina.reshape((npts, 1)) + R3 = np.zeros((npts, 3, 3)) + R3[:, [1, 2, 0], [2, 0, 1]] -= directions + R3[:, [2, 0, 1], [1, 2, 0]] += directions + + return R1 + R2 + R3 + + +def rotation_matrices_from_vectors(v0, v1): + r""" + Calculate a collection of rotation matrices defined by the unique + transformation rotating v1 into v2 about the mutually perpendicular axis. + + Parameters + ---------- + v0 : ndarray + Numpy array of shape (npts, 3) storing a collection of initial vector orientations. + + Note that the normalization of `v0` will be ignored. + + v1 : ndarray + Numpy array of shape (npts, 3) storing a collection of final vectors. + + Note that the normalization of `v1` will be ignored. + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 3, 3) rotating each v0 into the corresponding v1 + + Examples + -------- + >>> npts = int(1e4) + >>> v0 = np.random.random((npts, 3)) + >>> v1 = np.random.random((npts, 3)) + >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) + + Notes + ----- + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 3d vectors + + """ + v0 = normalized_vectors(v0) + v1 = normalized_vectors(v1) + directions = vectors_normal_to_planes(v0, v1) + angles = angles_between_list_of_vectors(v0, v1) + + return rotation_matrices_from_angles(angles, directions) + + +def random_perpendicular_directions(v, seed=None): + r""" + Given an input list of 3d vectors, v, return a list of 3d vectors + such that each returned vector has unit-length and is + orthogonal to the corresponding vector in v. + + Parameters + ---------- + v : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + seed : int, optional + Random number seed used to choose a random orthogonal direction + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 3) + """ + v = np.atleast_2d(v) + npts = v.shape[0] + with NumpyRNGContext(seed): + w = np.random.random((npts, 3)) + + vnorms = elementwise_norm(v).reshape((npts, 1)) + wnorms = elementwise_norm(w).reshape((npts, 1)) + + e_v = v/vnorms + e_w = w/wnorms + + v_dot_w = elementwise_dot(e_v, e_w).reshape((npts, 1)) + + e_v_perp = e_w - v_dot_w*e_v + e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) + return e_v_perp/e_v_perp_norm + diff --git a/vector_calculations.py b/vector_calculations.py new file mode 100644 index 000000000..043f1db5d --- /dev/null +++ b/vector_calculations.py @@ -0,0 +1,251 @@ +""" +A set of vector calculations to aid in rotation calculations +""" + +from __future__ import (division, print_function, absolute_import, + unicode_literals) +import numpy as np +from rotations import * + + +__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors', + 'angles_between_list_of_vectors', 'vectors_normal_to_planes', + 'vectors_between_list_of_vectors', 'project_onto_plane'] +__author__ = ['Duncan Campbell', 'Andrew Hearin'] + + +def vectors_between_list_of_vectors(x, y, p): + r""" + Starting from two input lists of vectors, return a list of unit-vectors + that lie in the same plane as the corresponding input vectors, + and where the input `p` controls the angle between + the returned vs. input vectors. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `x` will be ignored. + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `y` will be ignored. + + p : ndarray + Numpy array of shape (npts, ) storing values in the closed interval [0, 1]. + For values of `p` equal to zero, the returned vectors will be + exactly aligned with the input `x`; when `p` equals unity, the returned + vectors will be aligned with `y`. + + Returns + ------- + v : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d unit-vectors + lying in the plane spanned by `x` and `y`. The angle between `v` and `x` + will be equal to :math:`p*\theta_{\rm xy}`. + + Examples + -------- + >>> npts = int(1e4) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + >>> p = np.random.uniform(0, 1, npts) + >>> v = vectors_between_list_of_vectors(x, y, p) + >>> angles_xy = angles_between_list_of_vectors(x, y) + >>> angles_xp = angles_between_list_of_vectors(x, v) + >>> assert np.allclose(angles_xy*p, angles_xp) + """ + assert np.all(p >= 0), "All values of p must be non-negative" + assert np.all(p <= 1), "No value of p can exceed unity" + + z = vectors_normal_to_planes(x, y) + theta = angles_between_list_of_vectors(x, y) + angles = p*theta + rotation_matrices = rotation_matrices_from_angles(angles, z) + return normalized_vectors(rotate_vector_collection(rotation_matrices, x)) + + +def vectors_normal_to_planes(x, y): + r""" Given a collection of 3d vectors x and y, + return a collection of 3d unit-vectors that are orthogonal to x and y. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `x` will be ignored. + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `y` will be ignored. + + Returns + ------- + z : ndarray + Numpy array of shape (npts, 3). Each 3d vector in z will be orthogonal + to the corresponding vector in x and y. + + Examples + -------- + >>> npts = int(1e4) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + >>> normed_z = angles_between_list_of_vectors(x, y) + + """ + return normalized_vectors(np.cross(x, y)) + + +def angles_between_list_of_vectors(v0, v1, tol=1e-3): + r""" Calculate the angle between a collection of 3d vectors + + Parameters + ---------- + v0 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `v0` will be ignored. + + v1 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `v1` will be ignored. + + tol : float, optional + Acceptable numerical error for errors in angle. + This variable is only used to round off numerical noise that otherwise + causes exceptions to be raised by the inverse cosine function. + Default is 0.001. + + Returns + ------- + angles : ndarray + Numpy array of shape (npts, ) storing the angles between each pair of + corresponding points in v0 and v1. + + Returned values are in units of radians spanning [0, pi]. + + Examples + -------- + >>> npts = int(1e4) + >>> v0 = np.random.random((npts, 3)) + >>> v1 = np.random.random((npts, 3)) + >>> angles = angles_between_list_of_vectors(v0, v1) + """ + dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) + + # Protect against tiny numerical excesses beyond the range [-1 ,1] + mask1 = (dot > 1) & (dot < 1 + tol) + dot = np.where(mask1, 1., dot) + mask2 = (dot < -1) & (dot > -1 - tol) + dot = np.where(mask2, -1., dot) + + return np.arccos(dot) + + +def normalized_vectors(vectors): + r""" Return a unit-vector for each 3d vector in the input list of 3d points. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + normed_x : ndarray + Numpy array of shape (npts, 3) + + Examples + -------- + >>> npts = int(1e3) + >>> x = np.random.random((npts, 3)) + >>> normed_x = normalized_vectors(x) + """ + vectors = np.atleast_2d(vectors) + npts = vectors.shape[0] + return vectors/elementwise_norm(vectors).reshape((npts, -1)) + + +def elementwise_norm(x): + r""" Calculate the normalization of each element in a list of 3d points. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + result : ndarray + Numpy array of shape (npts, ) storing the norm of each 3d point in x. + + Examples + -------- + >>> npts = int(1e3) + >>> x = np.random.random((npts, 3)) + >>> norms = elementwise_norm(x) + """ + x = np.atleast_2d(x) + return np.sqrt(np.sum(x**2, axis=1)) + + +def elementwise_dot(x, y): + r""" Calculate the dot product between + each pair of elements in two input lists of 3d points. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + result : ndarray + Numpy array of shape (npts, ) storing the dot product between each + pair of corresponding points in x and y. + + Examples + -------- + >>> npts = int(1e3) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + >>> dots = elementwise_dot(x, y) + """ + x = np.atleast_2d(x) + y = np.atleast_2d(y) + return np.sum(x*y, axis=1) + + +def project_onto_plane(x1, x2): + r""" + Given a collection of 3D vectors, x1 and x2, project each vector + in x1 onto the plane normal to the corresponding vector x2 + + Parameters + ---------- + x1 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + x2 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + """ + + n = normalized_vectors(x2) + d = elementwise_dot(x1,n) + + return x - d[:,np.newaxis]*n + From 817e4ca3ab834ab7236c950f3c7ab5cfdca7b157 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 12:43:57 -0400 Subject: [PATCH 03/48] seperated 2 and 3d operations --- __init__.py | 0 rotations2d.py | 248 +++++++++++++++++++++++++++++++++ rotations.py => rotations3d.py | 67 ++++++++- tests/__init__.py | 0 tests/test_2d.py | 23 +++ tests/test_3d.py | 22 +++ 6 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 __init__.py create mode 100644 rotations2d.py rename rotations.py => rotations3d.py (79%) create mode 100644 tests/__init__.py create mode 100644 tests/test_2d.py create mode 100644 tests/test_3d.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rotations2d.py b/rotations2d.py new file mode 100644 index 000000000..3a31bbc68 --- /dev/null +++ b/rotations2d.py @@ -0,0 +1,248 @@ +r""" +A set of rotation utilites for manipuklating 3D vectors +""" + +from __future__ import (division, print_function, absolute_import, + unicode_literals) +import numpy as np +from .vector_calculations import * + + +__all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', + 'rotation_matrices_from_vectors', 'random_perpendicular_directions'] +__author__ = ['Duncan Campbell', 'Andrew Hearin'] + + +def rotate_vector_collection(rotation_matrices, vectors, optimize=False): + r""" + Given a collection of rotation matrices and a collection of 2d vectors, + apply each matrix to rotate the corresponding vector. + + Parameters + ---------- + rotation_matrices : ndarray + Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices. + If an array of shape (2, 2) is passed, all the vectors + are rotated using the same rotation matrix. + + vectors : ndarray + Numpy array of shape (npts, 2) storing a collection of 3d vectors + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 2) storing a collection of 3d vectors + + Examples + -------- + In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. + We'll use the `rotation_matrices_from_vectors` function to generate the + rotation matrices that rotate each `v0` into the corresponding `v1`. + Then we'll use the `rotate_vector_collection` function to apply each + rotation, and verify that we recover each of the `v1`. + + >>> npts = int(1e4) + >>> v0 = normalized_vectors(np.random.random((npts, 2))) + >>> v1 = normalized_vectors(np.random.random((npts, 2))) + >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) + >>> v2 = rotate_vector_collection(rotation_matrices, v0) + >>> assert np.allclose(v1, v2) + """ + + # apply same rotation matrix to all vectors + if np.shape(rotation_matrices) == (2, 2): + return np.dot(rotation_matrices, vectors.T).T + # rotate each vector by associated rotation matrix + else: + try: + return np.einsum('ijk,ik->ij', rotation_matrices, vectors, optimize=optimize) + except TypeError: + return np.einsum('ijk,ik->ij', rotation_matrices, vectors) + + +def random_rotation(vectors): + r""" + Apply a random rotation to a set of 3d vectors. + + Parameters + ---------- + vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + """ + + ran_angle = np.random.random(size=1)*(np.pi) + ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + + return rotate_vector_collection(ran_rot, vectors) + + +def rotation_matrices_from_angles(angles): + r""" + Calculate a collection of rotation matrices defined by + an input collection of rotation angles and rotation axes. + + Parameters + ---------- + angles : ndarray + Numpy array of shape (npts, ) storing a collection of rotation angles + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices + + Examples + -------- + >>> npts = int(1e4) + >>> angles = np.random.uniform(-np.pi/2., np.pi/2., npts) + >>> rotation_matrices = rotation_matrices_from_angles(angles, directions) + + Notes + ----- + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 2d vectors + """ + + angles = np.atleast_1d(angles) + npts = len(angles) + + sina = np.sin(angles) + cosa = np.cos(angles) + + R = np.zeros((npts, 2, 2)) + R[:, 0, 0] = cosa + R[:, 1, 1] = cosa + + R[:, 0, 1] = sina + R[:, 1, 0] = -sina + + return R + + +def rotation_matrices_from_vectors(v0, v1): + r""" + Calculate a collection of rotation matrices defined by the unique + transformation rotating v1 into v2 about the mutually perpendicular axis. + + Parameters + ---------- + v0 : ndarray + Numpy array of shape (npts, 2) storing a collection of initial vector orientations. + + Note that the normalization of `v0` will be ignored. + + v1 : ndarray + Numpy array of shape (npts, 2) storing a collection of final vectors. + + Note that the normalization of `v1` will be ignored. + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 2, 2) rotating each v0 into the corresponding v1 + + Examples + -------- + >>> npts = int(1e4) + >>> v0 = np.random.random((npts, 2)) + >>> v1 = np.random.random((npts, 2)) + >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) + + Notes + ----- + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 3d vectors + + """ + v0 = normalized_vectors(v0) + v1 = normalized_vectors(v1) + angles = angles_between_list_of_vectors(v0, v1) + + # where angles are 0.0, replace directions with v0 + mask = (angles==0.0) + return rotation_matrices_from_angles(angles) + + +def random_perpendicular_directions(v, seed=None): + r""" + Given an input list of 2d vectors, v, return a list of 2d vectors + such that each returned vector has unit-length and is + orthogonal to the corresponding vector in v. + + Parameters + ---------- + v : ndarray + Numpy array of shape (npts, 2) storing a collection of 2d vectors + seed : int, optional + Random number seed used to choose a random orthogonal direction + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 2) + """ + v = np.atleast_2d(v) + npts = v.shape[0] + with NumpyRNGContext(seed): + w = np.random.random((npts, 2)) + + vnorms = elementwise_norm(v).reshape((npts, 1)) + wnorms = elementwise_norm(w).reshape((npts, 1)) + + e_v = v/vnorms + e_w = w/wnorms + + v_dot_w = elementwise_dot(e_v, e_w).reshape((npts, 1)) + + e_v_perp = e_w - v_dot_w*e_v + e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) + return e_v_perp/e_v_perp_norm + + +def rotation2d(ux, uy): + """ + Calculate a collection of rotation matrices defined by an input collection + of basis vectors. + + Parameters + ---------- + ux : array_like + Numpy array of shape (npts, 2) storing a collection of unit vexctors + + uy : array_like + Numpy array of shape (npts, 2) storing a collection of unit vexctors + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices + """ + + N = np.shape(ux)[0] + + # assume initial unit vectors are the standard ones + ex = np.array([1.0, 0.0]*N).reshape(N, 3) + ey = np.array([0.0, 1.0]*N).reshape(N, 3) + + ux = normalized_vectors(ux) + uy = normalized_vectors(uy) + + r_11 = elementwise_dot(ex, ux) + r_12 = elementwise_dot(ex, uy) + + r_21 = elementwise_dot(ey, ux) + r_22 = elementwise_dot(ey, uy) + + r = np.zeros((N, 2, 2)) + r[:,0,0] = r_11 + r[:,0,1] = r_12 + r[:,1,0] = r_21 + r[:,1,1] = r_22 + + return r diff --git a/rotations.py b/rotations3d.py similarity index 79% rename from rotations.py rename to rotations3d.py index 1ba64b67e..f2d586837 100644 --- a/rotations.py +++ b/rotations3d.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from vector_calculations import * +from .vector_calculations import * __all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', @@ -176,6 +176,12 @@ def rotation_matrices_from_vectors(v0, v1): directions = vectors_normal_to_planes(v0, v1) angles = angles_between_list_of_vectors(v0, v1) + # where angles are 0.0, replace directions with v0 + mask_a = (np.isnan(directions[:,0]) | np.isnan(directions[:,1]) | np.isnan(directions[:,2])) + mask_b = (angles==0.0) + mask = mask_a | mask_b + directions[mask] = v0[mask] + return rotation_matrices_from_angles(angles, directions) @@ -214,3 +220,62 @@ def random_perpendicular_directions(v, seed=None): e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) return e_v_perp/e_v_perp_norm + +def rotation3d(ux, uy, uz): + """ + Calculate a collection of rotation matrices defined by an input collection + of basis vectors. + + Parameters + ---------- + ux : array_like + Numpy array of shape (npts, 3) storing a collection of unit vexctors + + uy : array_like + Numpy array of shape (npts, 3) storing a collection of unit vexctors + + uz : array_like + Numpy array of shape (npts, 3) storing a collection of unit vexctors + + Returns + ------- + matrices : ndarray + Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices + """ + + N = np.shape(ux)[0] + + # assume initial unit vectors are the standard ones + ex = np.array([1.0, 0.0, 0.0]*N).reshape(N, 3) + ey = np.array([0.0, 1.0, 0.0]*N).reshape(N, 3) + ez = np.array([0.0, 0.0, 1.0]*N).reshape(N, 3) + + ux = normalized_vectors(ux) + uy = normalized_vectors(uy) + uz = normalized_vectors(uz) + + r_11 = elementwise_dot(ex, ux) + r_12 = elementwise_dot(ex, uy) + r_13 = elementwise_dot(ex, uz) + + r_21 = elementwise_dot(ey, ux) + r_22 = elementwise_dot(ey, uy) + r_23 = elementwise_dot(ey, uz) + + r_31 = elementwise_dot(ez, ux) + r_32 = elementwise_dot(ez, uy) + r_33 = elementwise_dot(ez, uz) + + r = np.zeros((N, 3, 3)) + r[:,0,0] = r_11 + r[:,0,1] = r_12 + r[:,0,2] = r_13 + r[:,1,0] = r_21 + r[:,1,1] = r_22 + r[:,1,2] = r_23 + r[:,2,0] = r_31 + r[:,2,1] = r_32 + r[:,2,2] = r_33 + + return r + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_2d.py b/tests/test_2d.py new file mode 100644 index 000000000..bb84bf5b9 --- /dev/null +++ b/tests/test_2d.py @@ -0,0 +1,23 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext + +from ..rotations2d import * + +__all__ = ('test_rotation_matrices_from_vectors', ) + +fixed_seed = 43 + + +def test_rotation_matrices_from_vectors(): + """ + test to make sure null rotations return identiy matrix + """ + + N = 1000 + v1 = np.random.random((N,2)) + + rot_m = rotation_matrices_from_vectors(v1,v1) + + assert np.all(~np.isnan(rot_m)) \ No newline at end of file diff --git a/tests/test_3d.py b/tests/test_3d.py new file mode 100644 index 000000000..d62ecb407 --- /dev/null +++ b/tests/test_3d.py @@ -0,0 +1,22 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext + +from ..rotations3d import * + +__all__ = ('test_rotation_matrices_from_vectors', ) + +fixed_seed = 43 + + +def test_rotation_matrices_from_vectors(): + """ + test to make sure null rotations return identiy matrix + """ + + N = 1000 + v1 = np.random.random((N,3)) + + rot_m = rotation_matrices_from_vectors(v1,v1) + assert np.all(~np.isnan(rot_m)) \ No newline at end of file From b887c284ac436d4d7287dd4d0ec356c047f70d43 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 12:44:37 -0400 Subject: [PATCH 04/48] add ginore file --- .gitignore | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..624f96c2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Latex # +######### +*.log +*.aux +*.blg +*.bbl +*.synctex.gz + +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so +*.pyc + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite +*.ipynb_checkpoints + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file From 5135447312617c6960522850995e40a67b3a004f Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 12:45:06 -0400 Subject: [PATCH 05/48] fixed numpy error --- vector_calculations.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector_calculations.py b/vector_calculations.py index 043f1db5d..f9c3f2567 100644 --- a/vector_calculations.py +++ b/vector_calculations.py @@ -168,7 +168,9 @@ def normalized_vectors(vectors): """ vectors = np.atleast_2d(vectors) npts = vectors.shape[0] - return vectors/elementwise_norm(vectors).reshape((npts, -1)) + + with np.errstate(divide='ignore', invalid='ignore'): + return vectors/elementwise_norm(vectors).reshape((npts, -1)) def elementwise_norm(x): From c79a696765beba2d18e55c7e45186f661a1475e6 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 12:46:08 -0400 Subject: [PATCH 06/48] ignoring pytest cahce --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 624f96c2e..354ac245d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ *.sql *.sqlite *.ipynb_checkpoints +*.pytest_cache # OS generated files # ###################### From 301fcda2e79cd79f27bd400be53bb2603aebda23 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 12:52:33 -0400 Subject: [PATCH 07/48] updated readme --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 4468111e9..574a78269 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ # Rotations + +This package contains functions to rotate collections of 2D and 3D vectors. + +Some of the functionality of this package is taken from the [Halotools](https://halotools.readthedocs.io/en/latest/) utilities sudmodule, and reproduced here for convenience. + +## Requirements + +In order to use the functions in this package, you will need the following python packages installed: + +* numpy +* astropy + + +## Installation + +Place this directory in your PYTHONPATH. The variuous functions can then be imported as, e.g.: + +``` +from rotations.rotations3d import rotate_vector_collection +``` + + +contact: +duncanc@andrew.cmu.edu \ No newline at end of file From bda308d7ca956d2fabc21bd0dbd64757ec42ff4d Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 15:58:35 -0400 Subject: [PATCH 08/48] seperated mc functions --- mc_functions.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ rotations2d.py | 58 ------------------------ rotations3d.py | 60 +------------------------ 3 files changed, 115 insertions(+), 117 deletions(-) create mode 100644 mc_functions.py diff --git a/mc_functions.py b/mc_functions.py new file mode 100644 index 000000000..acd70c9a9 --- /dev/null +++ b/mc_functions.py @@ -0,0 +1,114 @@ +r""" +A set of rotation utilites that apply monte carlo +roations to collections of 2D and 3D vectors +""" + + +from __future__ import (division, print_function, absolute_import, + unicode_literals) +import numpy as np +from astropy.utils.misc import NumpyRNGContext +from .vector_calculations import * + + +__all__=['random_rotation_3d', 'random_rotation_2d', + 'random_perpendicular_directions'] +__author__ = ['Duncan Campbell'] + + +def random_rotation_3d(vectors, seed=None): + r""" + Apply a random rotation to a set of 3d vectors. + + Parameters + ---------- + vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + seed : int, optional + Random number seed + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + """ + + ran_angle = np.random.random(size=1)*(np.pi) + + with NumpyRNGContext(seed): + ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 + + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + + return rotate_vector_collection(ran_rot, vectors) + + +def random_rotation_2d(vectors, seed=None): + r""" + Apply a random rotation to a set of 3d vectors. + + Parameters + ---------- + vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + seed : int, optional + Random number seed + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + """ + + ran_angle = np.random.random(size=1)*(np.pi) + + with NumpyRNGContext(seed): + ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 + + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + + return rotate_vector_collection(ran_rot, vectors) + + +def random_perpendicular_directions(v, seed=None): + r""" + Given an input list of 3d vectors, v, return a list of 3d vectors + such that each returned vector has unit-length and is + orthogonal to the corresponding vector in v. + + Parameters + ---------- + v : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + seed : int, optional + Random number seed used to choose a random orthogonal direction + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 3) + """ + + v = np.atleast_2d(v) + npts = v.shape[0] + + with NumpyRNGContext(seed): + w = np.random.random((npts, 3)) + + vnorms = elementwise_norm(v).reshape((npts, 1)) + wnorms = elementwise_norm(w).reshape((npts, 1)) + + e_v = v/vnorms + e_w = w/wnorms + + v_dot_w = elementwise_dot(e_v, e_w).reshape((npts, 1)) + + e_v_perp = e_w - v_dot_w*e_v + e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) + + return e_v_perp/e_v_perp_norm + + diff --git a/rotations2d.py b/rotations2d.py index 3a31bbc68..be8eb3ae8 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -60,28 +60,6 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): return np.einsum('ijk,ik->ij', rotation_matrices, vectors) -def random_rotation(vectors): - r""" - Apply a random rotation to a set of 3d vectors. - - Parameters - ---------- - vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Returns - ------- - rotated_vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - """ - - ran_angle = np.random.random(size=1)*(np.pi) - ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) - - return rotate_vector_collection(ran_rot, vectors) - - def rotation_matrices_from_angles(angles): r""" Calculate a collection of rotation matrices defined by @@ -169,42 +147,6 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles) -def random_perpendicular_directions(v, seed=None): - r""" - Given an input list of 2d vectors, v, return a list of 2d vectors - such that each returned vector has unit-length and is - orthogonal to the corresponding vector in v. - - Parameters - ---------- - v : ndarray - Numpy array of shape (npts, 2) storing a collection of 2d vectors - seed : int, optional - Random number seed used to choose a random orthogonal direction - - Returns - ------- - result : ndarray - Numpy array of shape (npts, 2) - """ - v = np.atleast_2d(v) - npts = v.shape[0] - with NumpyRNGContext(seed): - w = np.random.random((npts, 2)) - - vnorms = elementwise_norm(v).reshape((npts, 1)) - wnorms = elementwise_norm(w).reshape((npts, 1)) - - e_v = v/vnorms - e_w = w/wnorms - - v_dot_w = elementwise_dot(e_v, e_w).reshape((npts, 1)) - - e_v_perp = e_w - v_dot_w*e_v - e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) - return e_v_perp/e_v_perp_norm - - def rotation2d(ux, uy): """ Calculate a collection of rotation matrices defined by an input collection diff --git a/rotations3d.py b/rotations3d.py index f2d586837..cd666c2fc 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -9,7 +9,7 @@ __all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', - 'rotation_matrices_from_vectors', 'random_perpendicular_directions'] + 'rotation_matrices_from_vectors'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -60,28 +60,6 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): return np.einsum('ijk,ik->ij', rotation_matrices, vectors) -def random_rotation(vectors): - r""" - Apply a random rotation to a set of 3d vectors. - - Parameters - ---------- - vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Returns - ------- - rotated_vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - """ - - ran_angle = np.random.random(size=1)*(np.pi) - ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) - - return rotate_vector_collection(ran_rot, vectors) - - def rotation_matrices_from_angles(angles, directions): r""" Calculate a collection of rotation matrices defined by @@ -185,42 +163,6 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles, directions) -def random_perpendicular_directions(v, seed=None): - r""" - Given an input list of 3d vectors, v, return a list of 3d vectors - such that each returned vector has unit-length and is - orthogonal to the corresponding vector in v. - - Parameters - ---------- - v : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - seed : int, optional - Random number seed used to choose a random orthogonal direction - - Returns - ------- - result : ndarray - Numpy array of shape (npts, 3) - """ - v = np.atleast_2d(v) - npts = v.shape[0] - with NumpyRNGContext(seed): - w = np.random.random((npts, 3)) - - vnorms = elementwise_norm(v).reshape((npts, 1)) - wnorms = elementwise_norm(w).reshape((npts, 1)) - - e_v = v/vnorms - e_w = w/wnorms - - v_dot_w = elementwise_dot(e_v, e_w).reshape((npts, 1)) - - e_v_perp = e_w - v_dot_w*e_v - e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) - return e_v_perp/e_v_perp_norm - - def rotation3d(ux, uy, uz): """ Calculate a collection of rotation matrices defined by an input collection From 851a27331e03ae141a93c90b170de834c68f1014 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 16:00:15 -0400 Subject: [PATCH 09/48] renamed mc file --- mc_functions.py => mcrotations.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mc_functions.py => mcrotations.py (100%) diff --git a/mc_functions.py b/mcrotations.py similarity index 100% rename from mc_functions.py rename to mcrotations.py From e566f33b69578da87187a4099bf39c08d68d109b Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 16:10:24 -0400 Subject: [PATCH 10/48] reorganized --- rotations3d.py | 122 +++++++++++++++++- vector_calculations.py => vector_utilities.py | 118 +---------------- 2 files changed, 120 insertions(+), 120 deletions(-) rename vector_calculations.py => vector_utilities.py (50%) diff --git a/rotations3d.py b/rotations3d.py index cd666c2fc..c3e8501fb 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -8,8 +8,9 @@ from .vector_calculations import * -__all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', - 'rotation_matrices_from_vectors'] +__all__=['rotate_vector_collection', + 'rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', + 'vectors_between_list_of_vectors', 'vectors_normal_to_planes', ' project_onto_plane'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -163,10 +164,9 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles, directions) -def rotation3d(ux, uy, uz): +def rotation_matrices_from_basis(ux, uy, uz): """ - Calculate a collection of rotation matrices defined by an input collection - of basis vectors. + Calculate a collection of rotation matrices defined by a set of basis vectors Parameters ---------- @@ -221,3 +221,115 @@ def rotation3d(ux, uy, uz): return r + +def vectors_between_list_of_vectors(x, y, p): + r""" + Starting from two input lists of vectors, return a list of unit-vectors + that lie in the same plane as the corresponding input vectors, + and where the input `p` controls the angle between + the returned vs. input vectors. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `x` will be ignored. + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `y` will be ignored. + + p : ndarray + Numpy array of shape (npts, ) storing values in the closed interval [0, 1]. + For values of `p` equal to zero, the returned vectors will be + exactly aligned with the input `x`; when `p` equals unity, the returned + vectors will be aligned with `y`. + + Returns + ------- + v : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d unit-vectors + lying in the plane spanned by `x` and `y`. The angle between `v` and `x` + will be equal to :math:`p*\theta_{\rm xy}`. + + Examples + -------- + >>> npts = int(1e4) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + >>> p = np.random.uniform(0, 1, npts) + >>> v = vectors_between_list_of_vectors(x, y, p) + >>> angles_xy = angles_between_list_of_vectors(x, y) + >>> angles_xp = angles_between_list_of_vectors(x, v) + >>> assert np.allclose(angles_xy*p, angles_xp) + """ + assert np.all(p >= 0), "All values of p must be non-negative" + assert np.all(p <= 1), "No value of p can exceed unity" + + z = vectors_normal_to_planes(x, y) + theta = angles_between_list_of_vectors(x, y) + angles = p*theta + rotation_matrices = rotation_matrices_from_angles(angles, z) + return normalized_vectors(rotate_vector_collection(rotation_matrices, x)) + + +def vectors_normal_to_planes(x, y): + r""" Given a collection of 3d vectors x and y, + return a collection of 3d unit-vectors that are orthogonal to x and y. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `x` will be ignored. + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `y` will be ignored. + + Returns + ------- + z : ndarray + Numpy array of shape (npts, 3). Each 3d vector in z will be orthogonal + to the corresponding vector in x and y. + + Examples + -------- + >>> npts = int(1e4) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + >>> normed_z = angles_between_list_of_vectors(x, y) + + """ + return normalized_vectors(np.cross(x, y)) + + +def project_onto_plane(x1, x2): + r""" + Given a collection of 3D vectors, x1 and x2, project each vector + in x1 onto the plane normal to the corresponding vector x2 + + Parameters + ---------- + x1 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + x2 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + """ + + n = normalized_vectors(x2) + d = elementwise_dot(x1,n) + + return x - d[:,np.newaxis]*n + diff --git a/vector_calculations.py b/vector_utilities.py similarity index 50% rename from vector_calculations.py rename to vector_utilities.py index f9c3f2567..67dad1e45 100644 --- a/vector_calculations.py +++ b/vector_utilities.py @@ -1,4 +1,4 @@ -""" +r""" A set of vector calculations to aid in rotation calculations """ @@ -8,98 +8,11 @@ from rotations import * -__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors', - 'angles_between_list_of_vectors', 'vectors_normal_to_planes', - 'vectors_between_list_of_vectors', 'project_onto_plane'] +__all__=['elementwise_dot', 'elementwise_norm', + 'normalized_vectors', 'angles_between_list_of_vectors'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] -def vectors_between_list_of_vectors(x, y, p): - r""" - Starting from two input lists of vectors, return a list of unit-vectors - that lie in the same plane as the corresponding input vectors, - and where the input `p` controls the angle between - the returned vs. input vectors. - - Parameters - ---------- - x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `x` will be ignored. - - y : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `y` will be ignored. - - p : ndarray - Numpy array of shape (npts, ) storing values in the closed interval [0, 1]. - For values of `p` equal to zero, the returned vectors will be - exactly aligned with the input `x`; when `p` equals unity, the returned - vectors will be aligned with `y`. - - Returns - ------- - v : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d unit-vectors - lying in the plane spanned by `x` and `y`. The angle between `v` and `x` - will be equal to :math:`p*\theta_{\rm xy}`. - - Examples - -------- - >>> npts = int(1e4) - >>> x = np.random.random((npts, 3)) - >>> y = np.random.random((npts, 3)) - >>> p = np.random.uniform(0, 1, npts) - >>> v = vectors_between_list_of_vectors(x, y, p) - >>> angles_xy = angles_between_list_of_vectors(x, y) - >>> angles_xp = angles_between_list_of_vectors(x, v) - >>> assert np.allclose(angles_xy*p, angles_xp) - """ - assert np.all(p >= 0), "All values of p must be non-negative" - assert np.all(p <= 1), "No value of p can exceed unity" - - z = vectors_normal_to_planes(x, y) - theta = angles_between_list_of_vectors(x, y) - angles = p*theta - rotation_matrices = rotation_matrices_from_angles(angles, z) - return normalized_vectors(rotate_vector_collection(rotation_matrices, x)) - - -def vectors_normal_to_planes(x, y): - r""" Given a collection of 3d vectors x and y, - return a collection of 3d unit-vectors that are orthogonal to x and y. - - Parameters - ---------- - x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `x` will be ignored. - - y : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `y` will be ignored. - - Returns - ------- - z : ndarray - Numpy array of shape (npts, 3). Each 3d vector in z will be orthogonal - to the corresponding vector in x and y. - - Examples - -------- - >>> npts = int(1e4) - >>> x = np.random.random((npts, 3)) - >>> y = np.random.random((npts, 3)) - >>> normed_z = angles_between_list_of_vectors(x, y) - - """ - return normalized_vectors(np.cross(x, y)) - - def angles_between_list_of_vectors(v0, v1, tol=1e-3): r""" Calculate the angle between a collection of 3d vectors @@ -226,28 +139,3 @@ def elementwise_dot(x, y): return np.sum(x*y, axis=1) -def project_onto_plane(x1, x2): - r""" - Given a collection of 3D vectors, x1 and x2, project each vector - in x1 onto the plane normal to the corresponding vector x2 - - Parameters - ---------- - x1 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - x2 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - Returns - ------- - result : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - """ - - n = normalized_vectors(x2) - d = elementwise_dot(x1,n) - - return x - d[:,np.newaxis]*n - From f07bf996a2519bd9c176cf43759aef2060b63227 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 16:13:27 -0400 Subject: [PATCH 11/48] updated readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 574a78269..4c46d7558 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ This package contains functions to rotate collections of 2D and 3D vectors. Some of the functionality of this package is taken from the [Halotools](https://halotools.readthedocs.io/en/latest/) utilities sudmodule, and reproduced here for convenience. + +## Description + +This package contains tools to perform: + +* rotations of 3D vectors, +* rotations of 2D vectors, +* and monte carlo rotations of 2D and 3D vectors. + + ## Requirements In order to use the functions in this package, you will need the following python packages installed: From 58b3a4af4f5d4688e218e12c79786ec9a41cb09c Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 16:18:20 -0400 Subject: [PATCH 12/48] fixed bugs --- rotations2d.py | 12 ++++++----- rotations3d.py | 2 +- vector_utilities.py | 52 +-------------------------------------------- 3 files changed, 9 insertions(+), 57 deletions(-) diff --git a/rotations2d.py b/rotations2d.py index be8eb3ae8..94be8ab48 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -1,15 +1,17 @@ r""" -A set of rotation utilites for manipuklating 3D vectors +A set of rotation utilites for manipuklating 2D vectors """ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_calculations import * +from .vector_utilities import elementwise_dot, elementwise_norm, normalized_vectors -__all__=['rotate_vector_collection', 'random_rotation', 'rotation_matrices_from_angles', - 'rotation_matrices_from_vectors', 'random_perpendicular_directions'] +__all__=['rotate_vector_collection', + 'rotation_matrices_from_angles', + 'rotation_matrices_from_vectors', + 'rotation_matrices_from_basis'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -147,7 +149,7 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles) -def rotation2d(ux, uy): +def rotation_matrices_from_basis(ux, uy): """ Calculate a collection of rotation matrices defined by an input collection of basis vectors. diff --git a/rotations3d.py b/rotations3d.py index c3e8501fb..b362b7f47 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_calculations import * +from .vector_utilities import elementwise_dot, elementwise_norm, normalized_vectors __all__=['rotate_vector_collection', diff --git a/vector_utilities.py b/vector_utilities.py index 67dad1e45..a8748f992 100644 --- a/vector_utilities.py +++ b/vector_utilities.py @@ -5,61 +5,11 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from rotations import * - -__all__=['elementwise_dot', 'elementwise_norm', - 'normalized_vectors', 'angles_between_list_of_vectors'] +__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] -def angles_between_list_of_vectors(v0, v1, tol=1e-3): - r""" Calculate the angle between a collection of 3d vectors - - Parameters - ---------- - v0 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `v0` will be ignored. - - v1 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `v1` will be ignored. - - tol : float, optional - Acceptable numerical error for errors in angle. - This variable is only used to round off numerical noise that otherwise - causes exceptions to be raised by the inverse cosine function. - Default is 0.001. - - Returns - ------- - angles : ndarray - Numpy array of shape (npts, ) storing the angles between each pair of - corresponding points in v0 and v1. - - Returned values are in units of radians spanning [0, pi]. - - Examples - -------- - >>> npts = int(1e4) - >>> v0 = np.random.random((npts, 3)) - >>> v1 = np.random.random((npts, 3)) - >>> angles = angles_between_list_of_vectors(v0, v1) - """ - dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) - - # Protect against tiny numerical excesses beyond the range [-1 ,1] - mask1 = (dot > 1) & (dot < 1 + tol) - dot = np.where(mask1, 1., dot) - mask2 = (dot < -1) & (dot > -1 - tol) - dot = np.where(mask2, -1., dot) - - return np.arccos(dot) - - def normalized_vectors(vectors): r""" Return a unit-vector for each 3d vector in the input list of 3d points. From ec470b496aae6152edceba385c2e938680f211c2 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 21 Sep 2018 16:23:09 -0400 Subject: [PATCH 13/48] fixed bug --- rotations2d.py | 3 ++- rotations3d.py | 5 +++-- vector_utilities.py | 49 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/rotations2d.py b/rotations2d.py index 94be8ab48..fd8a8c61a 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -5,7 +5,8 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import elementwise_dot, elementwise_norm, normalized_vectors +from .vector_utilities import (elementwise_dot, elementwise_norm, + normalized_vectors, angles_between_list_of_vectors) __all__=['rotate_vector_collection', diff --git a/rotations3d.py b/rotations3d.py index b362b7f47..b1a30efcc 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -5,12 +5,13 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import elementwise_dot, elementwise_norm, normalized_vectors +from .vector_utilities import (elementwise_dot, elementwise_norm, + normalized_vectors, angles_between_list_of_vectors) __all__=['rotate_vector_collection', 'rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', - 'vectors_between_list_of_vectors', 'vectors_normal_to_planes', ' project_onto_plane'] + 'vectors_between_list_of_vectors', 'vectors_normal_to_planes', 'project_onto_plane'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] diff --git a/vector_utilities.py b/vector_utilities.py index a8748f992..abdc07542 100644 --- a/vector_utilities.py +++ b/vector_utilities.py @@ -6,7 +6,8 @@ unicode_literals) import numpy as np -__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors'] +__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors', + 'angles_between_list_of_vectors'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -89,3 +90,49 @@ def elementwise_dot(x, y): return np.sum(x*y, axis=1) +def angles_between_list_of_vectors(v0, v1, tol=1e-3): + r""" Calculate the angle between a collection of 3d vectors + + Parameters + ---------- + v0 : ndarray + Numpy array of shape (npts, ndim) storing a collection of ndim-D vectors + Note that the normalization of `v0` will be ignored. + + v1 : ndarray + Numpy array of shape (npts, ndim) storing a collection of ndim-D vectors + Note that the normalization of `v1` will be ignored. + + tol : float, optional + Acceptable numerical error for errors in angle. + This variable is only used to round off numerical noise that otherwise + causes exceptions to be raised by the inverse cosine function. + Default is 0.001. + + Returns + ------- + angles : ndarray + Numpy array of shape (npts, ) storing the angles between each pair of + corresponding points in v0 and v1. + + Returned values are in units of radians spanning [0, pi]. + + Examples + -------- + >>> npts = int(1e4) + >>> v0 = np.random.random((npts, 3)) + >>> v1 = np.random.random((npts, 3)) + >>> angles = angles_between_list_of_vectors(v0, v1) + """ + + dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) + + # Protect against tiny numerical excesses beyond the range [-1 ,1] + mask1 = (dot > 1) & (dot < 1 + tol) + dot = np.where(mask1, 1., dot) + mask2 = (dot < -1) & (dot > -1 - tol) + dot = np.where(mask2, -1., dot) + + return np.arccos(dot) + + From 9cfb0a990f3349a137f9115d867a8156e7277374 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:31:39 -0400 Subject: [PATCH 14/48] added tests --- tests/test_2d.py | 45 +++++++++++-- tests/test_3d.py | 48 ++++++++++++-- tests/test_utilities.py | 143 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 tests/test_utilities.py diff --git a/tests/test_2d.py b/tests/test_2d.py index bb84bf5b9..acc1ed01f 100644 --- a/tests/test_2d.py +++ b/tests/test_2d.py @@ -5,7 +5,9 @@ from ..rotations2d import * -__all__ = ('test_rotation_matrices_from_vectors', ) +__all__ = ('test_rotation_matrices_from_vectors', + 'test_rotation_matrices_from_angles', + 'test_rotation_matrices_from_basis' ) fixed_seed = 43 @@ -15,9 +17,42 @@ def test_rotation_matrices_from_vectors(): test to make sure null rotations return identiy matrix """ - N = 1000 - v1 = np.random.random((N,2)) + npts = 1000 + ndim = 2 + v1 = np.random.random((npts,ndim)) rot_m = rotation_matrices_from_vectors(v1,v1) - - assert np.all(~np.isnan(rot_m)) \ No newline at end of file + + assert np.all(~np.isnan(rot_m)) + + +def test_rotation_matrices_from_angles(): + """ + test to make sure null rotations return identiy matrix + """ + + npts = 1000 + ndim = 2 + + rot_m = rotation_matrices_from_angles(np.zeros(npts)) + + assert np.all(~np.isnan(rot_m)) + + +def test_rotation_matrices_from_basis(): + """ + test to make sure null rotations return identiy matrix + """ + + npts = 1000 + ndim = 2 + + ux = np.zeros((npts,ndim)) + ux[:,0] = 1.0 + uy = np.zeros((npts,ndim)) + uy[:,1] = 1.0 + + rot_m = rotation_matrices_from_basis(ux, uy) + + assert np.all(~np.isnan(rot_m)) + diff --git a/tests/test_3d.py b/tests/test_3d.py index d62ecb407..2d75c7d9b 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -5,7 +5,9 @@ from ..rotations3d import * -__all__ = ('test_rotation_matrices_from_vectors', ) +__all__ = ('test_rotation_matrices_from_vectors', + 'test_rotation_matrices_from_angles', + 'test_rotation_matrices_from_basis' ) fixed_seed = 43 @@ -15,8 +17,46 @@ def test_rotation_matrices_from_vectors(): test to make sure null rotations return identiy matrix """ - N = 1000 - v1 = np.random.random((N,3)) + npts = 1000 + ndim = 3 + v1 = np.random.random((npts,ndim)) rot_m = rotation_matrices_from_vectors(v1,v1) - assert np.all(~np.isnan(rot_m)) \ No newline at end of file + + assert np.all(~np.isnan(rot_m)) + + +def test_rotation_matrices_from_angles(): + """ + test to make sure null rotations return identiy matrix + """ + + npts = 1000 + ndim = 3 + + uz = np.zeros((npts,ndim)) + uz[:,2] = 1.0 + + rot_m = rotation_matrices_from_angles(np.zeros(npts), uz) + + assert np.all(~np.isnan(rot_m)) + + +def test_rotation_matrices_from_basis(): + """ + test to make sure null rotations return identiy matrix + """ + + npts = 1000 + ndim = 3 + + ux = np.zeros((npts,ndim)) + ux[:,0] = 1.0 + uy = np.zeros((npts,ndim)) + uy[:,1] = 1.0 + uz = np.zeros((npts,ndim)) + uz[:,2] = 1.0 + + rot_m = rotation_matrices_from_basis(ux, uy, uz) + + assert np.all(~np.isnan(rot_m)) diff --git a/tests/test_utilities.py b/tests/test_utilities.py new file mode 100644 index 000000000..52710470d --- /dev/null +++ b/tests/test_utilities.py @@ -0,0 +1,143 @@ +""" +test suite for vector_utilities.py +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext + +from ..vector_utilities import * +from ..rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from ..rotations2d import rotate_vector_collection as rotate_vector_collection_2d +from ..rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from ..rotations3d import rotate_vector_collection as rotate_vector_collection_3d +from ..rotations3d import vectors_normal_to_planes + +__all__ = ('test_normalized_vectors', 'test_elementwise_norm', 'test_elementwise_dot', + 'test_angles_between_list_of_vectors') + +fixed_seed = 43 + + +def test_normalized_vectors(): + """ + test that normalized square vectors sum to 1 + """ + + # 2D vectors + npts = 100 + ndim = 2 + v1 = np.random.random((npts,ndim)) + + result = normalized_vectors(v1) + + assert np.allclose(np.sum(result**2, axis=-1), np.ones(npts)) + + # 3D vectors + npts = 100 + ndim = 3 + v1 = np.random.random((npts,ndim)) + + result = normalized_vectors(v1) + + assert np.allclose(np.sum(result**2, axis=-1), np.ones(npts)) + + +def test_elementwise_norm(): + """ + test that normalized vectors norm are 1 + """ + + # 2D vectors + npts = 100 + ndim = 2 + v1 = normalized_vectors(np.random.random((npts,ndim))) + + result = elementwise_norm(v1) + + assert np.allclose(result, np.ones(npts)) + + # 3D vectors + npts = 100 + ndim = 3 + v1 = normalized_vectors(np.random.random((npts,ndim))) + + result = elementwise_norm(v1) + + assert np.allclose(result, np.ones(npts)) + + +def test_elementwise_dot(): + """ + test that perpendicular vectors' dot product is 0.0 + test that parallel vectors' dot product are 1.0 + """ + + # 2D vectors + npts = 100 + ndim = 2 + v1 = normalized_vectors(np.random.random((npts,ndim))) + + # get a set of perpendicular vectors + rot = rotation_matrices_from_angles_2d(np.ones(npts)*np.pi/2.0) + v2 = rotate_vector_collection_2d(rot, v1) + + # assert the dot products are all zero + assert np.allclose(elementwise_dot(v1,v2),np.zeros(npts)) + # assert the dot products are all one + assert np.allclose(elementwise_dot(v1,v1),np.ones(npts)) + + + # 3D vectors + npts = 100 + ndim = 3 + v1 = normalized_vectors(np.random.random((npts,ndim))) + v2 = np.random.random((npts,ndim)) + v3 = vectors_normal_to_planes(v1, v2) + + # get a set of vectors rotated by 90 degrees + rot = rotation_matrices_from_angles_3d(np.ones(npts)*np.pi/2.0, v3) + v4 = rotate_vector_collection_2d(rot, v1) + + # assert the dot products are all zero + assert np.allclose(elementwise_dot(v1,v4),np.zeros(npts)) + # assert the dot products are all one + assert np.allclose(elementwise_dot(v1,v1),np.ones(npts)) + + +def test_angles_between_list_of_vectors(): + """ + rotate a vector collection by a random angle and make sure the resulting angles + between the stes of vectors are consistent + """ + + # 2D vectors + npts = 100 + ndim = 2 + v1 = np.random.random((npts,ndim)) + + # get a set of vectors rotated by random angles + angles = np.random.uniform(-np.pi/2.0, np.pi/2.0, npts) + rot = rotation_matrices_from_angles_2d(angles) + v2 = rotate_vector_collection_2d(rot, v1) + + # assert the dot products are all zero + assert np.allclose(angles_between_list_of_vectors(v1,v2),np.fabs(angles)) + + + # 3D vectors + npts = 100 + ndim = 3 + v1 = np.random.random((npts,ndim)) + v2 = np.random.random((npts,ndim)) + v3 = vectors_normal_to_planes(v1, v2) + + # get a set of vectors rotated by random angles + angles = np.random.uniform(-np.pi/2.0, np.pi/2.0, npts) + rot = rotation_matrices_from_angles_3d(angles, v3) + v4 = rotate_vector_collection_2d(rot, v1) + + # assert the dot products are all zero + assert np.allclose(angles_between_list_of_vectors(v1,v4),np.fabs(angles)) + + + + From d543292e0d1550ba3091301725f214b175c14d18 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:32:07 -0400 Subject: [PATCH 15/48] fixed typos in doc strings --- rotations2d.py | 38 ++++++++++++++++++------------------ rotations3d.py | 28 +++++++++++++-------------- vector_utilities.py | 47 +++++++++++++++++++++++++++------------------ 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/rotations2d.py b/rotations2d.py index fd8a8c61a..29e68ff87 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -1,5 +1,5 @@ r""" -A set of rotation utilites for manipuklating 2D vectors +A set of rotation utilites for manipulating 2-dimensional vectors """ from __future__ import (division, print_function, absolute_import, @@ -17,25 +17,25 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): - r""" + r""" Given a collection of rotation matrices and a collection of 2d vectors, apply each matrix to rotate the corresponding vector. - + Parameters ---------- rotation_matrices : ndarray Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices. If an array of shape (2, 2) is passed, all the vectors are rotated using the same rotation matrix. - + vectors : ndarray Numpy array of shape (npts, 2) storing a collection of 3d vectors - + Returns ------- rotated_vectors : ndarray Numpy array of shape (npts, 2) storing a collection of 3d vectors - + Examples -------- In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. @@ -43,7 +43,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): rotation matrices that rotate each `v0` into the corresponding `v1`. Then we'll use the `rotate_vector_collection` function to apply each rotation, and verify that we recover each of the `v1`. - + >>> npts = int(1e4) >>> v0 = normalized_vectors(np.random.random((npts, 2))) >>> v1 = normalized_vectors(np.random.random((npts, 2))) @@ -64,7 +64,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): def rotation_matrices_from_angles(angles): - r""" + r""" Calculate a collection of rotation matrices defined by an input collection of rotation angles and rotation axes. @@ -102,14 +102,14 @@ def rotation_matrices_from_angles(angles): R[:, 0, 1] = sina R[:, 1, 0] = -sina - + return R def rotation_matrices_from_vectors(v0, v1): - r""" + r""" Calculate a collection of rotation matrices defined by the unique - transformation rotating v1 into v2 about the mutually perpendicular axis. + transformation rotating v1 into v2. Parameters ---------- @@ -138,7 +138,7 @@ def rotation_matrices_from_vectors(v0, v1): Notes ----- The function `rotate_vector_collection` can be used to efficiently - apply the returned collection of matrices to a collection of 3d vectors + apply the returned collection of matrices to a collection of 2d vectors """ v0 = normalized_vectors(v0) @@ -154,15 +154,15 @@ def rotation_matrices_from_basis(ux, uy): """ Calculate a collection of rotation matrices defined by an input collection of basis vectors. - + Parameters ---------- ux : array_like - Numpy array of shape (npts, 2) storing a collection of unit vexctors - + Numpy array of shape (npts, 2) storing a collection of unit vectors + uy : array_like - Numpy array of shape (npts, 2) storing a collection of unit vexctors - + Numpy array of shape (npts, 2) storing a collection of unit vectors + Returns ------- matrices : ndarray @@ -172,8 +172,8 @@ def rotation_matrices_from_basis(ux, uy): N = np.shape(ux)[0] # assume initial unit vectors are the standard ones - ex = np.array([1.0, 0.0]*N).reshape(N, 3) - ey = np.array([0.0, 1.0]*N).reshape(N, 3) + ex = np.array([1.0, 0.0]*N).reshape(N, 2) + ey = np.array([0.0, 1.0]*N).reshape(N, 2) ux = normalized_vectors(ux) uy = normalized_vectors(uy) diff --git a/rotations3d.py b/rotations3d.py index b1a30efcc..aef2f2986 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -1,5 +1,5 @@ """ -A set of rotaiton utilites +A set of rotation utilites for manipulating 3-dimensional vectors """ from __future__ import (division, print_function, absolute_import, @@ -16,25 +16,25 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): - r""" + r""" Given a collection of rotation matrices and a collection of 3d vectors, apply each matrix to rotate the corresponding vector. - + Parameters ---------- rotation_matrices : ndarray Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices. If an array of shape (3, 3) is passed, all the vectors are rotated using the same rotation matrix. - + vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors - + Returns ------- rotated_vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors - + Examples -------- In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. @@ -42,7 +42,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): rotation matrices that rotate each `v0` into the corresponding `v1`. Then we'll use the `rotate_vector_collection` function to apply each rotation, and verify that we recover each of the `v1`. - + >>> npts = int(1e4) >>> v0 = normalized_vectors(np.random.random((npts, 3))) >>> v1 = normalized_vectors(np.random.random((npts, 3))) @@ -63,7 +63,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): def rotation_matrices_from_angles(angles, directions): - r""" + r""" Calculate a collection of rotation matrices defined by an input collection of rotation angles and rotation axes. @@ -117,7 +117,7 @@ def rotation_matrices_from_angles(angles, directions): def rotation_matrices_from_vectors(v0, v1): - r""" + r""" Calculate a collection of rotation matrices defined by the unique transformation rotating v1 into v2 about the mutually perpendicular axis. @@ -168,18 +168,18 @@ def rotation_matrices_from_vectors(v0, v1): def rotation_matrices_from_basis(ux, uy, uz): """ Calculate a collection of rotation matrices defined by a set of basis vectors - + Parameters ---------- ux : array_like Numpy array of shape (npts, 3) storing a collection of unit vexctors - + uy : array_like Numpy array of shape (npts, 3) storing a collection of unit vexctors - + uz : array_like Numpy array of shape (npts, 3) storing a collection of unit vexctors - + Returns ------- matrices : ndarray @@ -331,6 +331,6 @@ def project_onto_plane(x1, x2): n = normalized_vectors(x2) d = elementwise_dot(x1,n) - + return x - d[:,np.newaxis]*n diff --git a/vector_utilities.py b/vector_utilities.py index abdc07542..033baeef1 100644 --- a/vector_utilities.py +++ b/vector_utilities.py @@ -12,24 +12,27 @@ def normalized_vectors(vectors): - r""" Return a unit-vector for each 3d vector in the input list of 3d points. + r""" + Return a unit-vector for each n-dimensional vector in the input list of n-dimensional points. Parameters ---------- x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points + Numpy array of shape (npts, ndim) storing a collection of n-dimensional points Returns ------- normed_x : ndarray - Numpy array of shape (npts, 3) + Numpy array of shape (npts, ndim) Examples -------- >>> npts = int(1e3) - >>> x = np.random.random((npts, 3)) + >>> ndim = 3 + >>> x = np.random.random((npts, ndim)) >>> normed_x = normalized_vectors(x) """ + vectors = np.atleast_2d(vectors) npts = vectors.shape[0] @@ -38,39 +41,43 @@ def normalized_vectors(vectors): def elementwise_norm(x): - r""" Calculate the normalization of each element in a list of 3d points. + r""" + Calculate the normalization of each element in a list of n-dimensional points. Parameters ---------- x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points + Numpy array of shape (npts, ndim) storing a collection of n-dimensional points Returns ------- result : ndarray - Numpy array of shape (npts, ) storing the norm of each 3d point in x. + Numpy array of shape (npts, ) storing the norm of each n-dimensional point in x. Examples -------- >>> npts = int(1e3) - >>> x = np.random.random((npts, 3)) + >>> ndim = 3 + >>> x = np.random.random((npts, ndim)) >>> norms = elementwise_norm(x) """ + x = np.atleast_2d(x) return np.sqrt(np.sum(x**2, axis=1)) def elementwise_dot(x, y): - r""" Calculate the dot product between - each pair of elements in two input lists of 3d points. + r""" + Calculate the dot product between + each pair of elements in two input lists of n-dimensional points. Parameters ---------- x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points + Numpy array of shape (npts, ndim) storing a collection of dimensional points y : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points + Numpy array of shape (npts, ndim) storing a collection of dimensional points Returns ------- @@ -81,10 +88,12 @@ def elementwise_dot(x, y): Examples -------- >>> npts = int(1e3) - >>> x = np.random.random((npts, 3)) - >>> y = np.random.random((npts, 3)) + >>> ndim = 3 + >>> x = np.random.random((npts, ndim)) + >>> y = np.random.random((npts, ndim)) >>> dots = elementwise_dot(x, y) """ + x = np.atleast_2d(x) y = np.atleast_2d(y) return np.sum(x*y, axis=1) @@ -98,17 +107,17 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): v0 : ndarray Numpy array of shape (npts, ndim) storing a collection of ndim-D vectors Note that the normalization of `v0` will be ignored. - + v1 : ndarray Numpy array of shape (npts, ndim) storing a collection of ndim-D vectors Note that the normalization of `v1` will be ignored. - + tol : float, optional Acceptable numerical error for errors in angle. This variable is only used to round off numerical noise that otherwise causes exceptions to be raised by the inverse cosine function. Default is 0.001. - + Returns ------- angles : ndarray @@ -116,7 +125,7 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): corresponding points in v0 and v1. Returned values are in units of radians spanning [0, pi]. - + Examples -------- >>> npts = int(1e4) @@ -124,7 +133,7 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): >>> v1 = np.random.random((npts, 3)) >>> angles = angles_between_list_of_vectors(v0, v1) """ - + dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) # Protect against tiny numerical excesses beyond the range [-1 ,1] From 2e98ebb689e2a59428597ea6ff82f62e63666925 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:34:21 -0400 Subject: [PATCH 16/48] fixed typos in doc strings --- mcrotations.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/mcrotations.py b/mcrotations.py index acd70c9a9..4bcf831eb 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -1,6 +1,6 @@ r""" A set of rotation utilites that apply monte carlo -roations to collections of 2D and 3D vectors +roations to collections of 2- and 3-dimensional vectors """ @@ -8,10 +8,11 @@ unicode_literals) import numpy as np from astropy.utils.misc import NumpyRNGContext -from .vector_calculations import * +from .vector_utilities import * -__all__=['random_rotation_3d', 'random_rotation_2d', +__all__=['random_rotation_3d', + 'random_rotation_2d', 'random_perpendicular_directions'] __author__ = ['Duncan Campbell'] @@ -24,10 +25,10 @@ def random_rotation_3d(vectors, seed=None): ---------- vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors - + seed : int, optional Random number seed - + Returns ------- rotated_vectors : ndarray @@ -35,10 +36,10 @@ def random_rotation_3d(vectors, seed=None): """ ran_angle = np.random.random(size=1)*(np.pi) - + with NumpyRNGContext(seed): ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 - + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) return rotate_vector_collection(ran_rot, vectors) @@ -46,27 +47,27 @@ def random_rotation_3d(vectors, seed=None): def random_rotation_2d(vectors, seed=None): r""" - Apply a random rotation to a set of 3d vectors. + Apply a random rotation to a set of 2d vectors. Parameters ---------- vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - + Numpy array of shape (npts, 2) storing a collection of 2d vectors + seed : int, optional Random number seed - + Returns ------- rotated_vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors + Numpy array of shape (npts, 2) storing a collection of 2d vectors """ ran_angle = np.random.random(size=1)*(np.pi) with NumpyRNGContext(seed): ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 - + ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) return rotate_vector_collection(ran_rot, vectors) @@ -77,15 +78,15 @@ def random_perpendicular_directions(v, seed=None): Given an input list of 3d vectors, v, return a list of 3d vectors such that each returned vector has unit-length and is orthogonal to the corresponding vector in v. - + Parameters ---------- v : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors - + seed : int, optional Random number seed used to choose a random orthogonal direction - + Returns ------- result : ndarray @@ -94,7 +95,7 @@ def random_perpendicular_directions(v, seed=None): v = np.atleast_2d(v) npts = v.shape[0] - + with NumpyRNGContext(seed): w = np.random.random((npts, 3)) @@ -108,7 +109,7 @@ def random_perpendicular_directions(v, seed=None): e_v_perp = e_w - v_dot_w*e_v e_v_perp_norm = elementwise_norm(e_v_perp).reshape((npts, 1)) - + return e_v_perp/e_v_perp_norm From b2a90f7e7a284ad5d6290ea60a50faa88b01742d Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:40:19 -0400 Subject: [PATCH 17/48] fixed bugs and added tests for mc rotations --- mcrotations.py | 15 ++++++++------- tests/test_mcrotations.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 tests/test_mcrotations.py diff --git a/mcrotations.py b/mcrotations.py index 4bcf831eb..af9caddbf 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -9,6 +9,10 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext from .vector_utilities import * +from .rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from .rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from .rotations3d import rotate_vector_collection as rotate_vector_collection_3d +from .rotations2d import rotate_vector_collection as rotate_vector_collection_2d __all__=['random_rotation_3d', @@ -40,9 +44,9 @@ def random_rotation_3d(vectors, seed=None): with NumpyRNGContext(seed): ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + ran_rot = rotation_matrices_from_angles_3d(ran_angle, ran_direction) - return rotate_vector_collection(ran_rot, vectors) + return rotate_vector_collection_3d(ran_rot, vectors) def random_rotation_2d(vectors, seed=None): @@ -65,12 +69,9 @@ def random_rotation_2d(vectors, seed=None): ran_angle = np.random.random(size=1)*(np.pi) - with NumpyRNGContext(seed): - ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 - - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + ran_rot = rotation_matrices_from_angles_2d(ran_angle) - return rotate_vector_collection(ran_rot, vectors) + return rotate_vector_collection_2d(ran_rot, vectors) def random_perpendicular_directions(v, seed=None): diff --git a/tests/test_mcrotations.py b/tests/test_mcrotations.py new file mode 100644 index 000000000..6176cedef --- /dev/null +++ b/tests/test_mcrotations.py @@ -0,0 +1,38 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext + +from ..mcrotations import * + +__all__ = ('test_random_rotation_3d', + 'test_random_rotation_3d', + 'test_random_perpendicular_directions' ) + +fixed_seed = 43 + + +def test_random_rotation_3d(): + """ + """ + + npts = 1000 + ndim = 3 + + v1 = np.random.random((npts,ndim)) + v2 = random_rotation_3d(v1) + + assert np.all(v1 != v2) + + +def test_random_rotation_2d(): + """ + """ + + npts = 1000 + ndim = 2 + + v1 = np.random.random((npts,ndim)) + v2 = random_rotation_2d(v1) + + assert np.all(v1 != v2) From d1e1df97fbebde6b9c4576194cf0cc1e9ab71b2e Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:46:44 -0400 Subject: [PATCH 18/48] added tests --- tests/test_mcrotations.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_mcrotations.py b/tests/test_mcrotations.py index 6176cedef..0577dff2a 100644 --- a/tests/test_mcrotations.py +++ b/tests/test_mcrotations.py @@ -2,7 +2,7 @@ """ import numpy as np from astropy.utils.misc import NumpyRNGContext - +from ..vector_utilities import * from ..mcrotations import * __all__ = ('test_random_rotation_3d', @@ -14,6 +14,7 @@ def test_random_rotation_3d(): """ + simple unit test """ npts = 1000 @@ -22,11 +23,13 @@ def test_random_rotation_3d(): v1 = np.random.random((npts,ndim)) v2 = random_rotation_3d(v1) + assert np.shape(v2) == (npts,ndim) assert np.all(v1 != v2) def test_random_rotation_2d(): """ + simple unit test """ npts = 1000 @@ -35,4 +38,20 @@ def test_random_rotation_2d(): v1 = np.random.random((npts,ndim)) v2 = random_rotation_2d(v1) + assert np.shape(v2) == (npts,ndim) assert np.all(v1 != v2) + + +def test_random_perpendicular_directions(): + """ + check to make sure dot product is zero. + """ + + npts = 1000 + ndim = 3 + + v1 = normalized_vectors(np.random.random((npts,ndim))) + v2 = random_perpendicular_directions(v1) + + assert np.allclose(elementwise_dot(v1,v2),np.zeros(npts)) + From 3ee97ccc3c5401c7a9801e4078aca65ffac2d9fb Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 11:48:19 -0400 Subject: [PATCH 19/48] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c46d7558..a1709e4c6 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ In order to use the functions in this package, you will need the following pytho ## Installation -Place this directory in your PYTHONPATH. The variuous functions can then be imported as, e.g.: +Place this directory in your PYTHONPATH. The various functions can then be imported as, e.g.: ``` from rotations.rotations3d import rotate_vector_collection From 8529c17e77f7792d03e3272cf4f945329407e13f Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 26 Sep 2018 12:01:25 -0400 Subject: [PATCH 20/48] fixed typo in doc string --- vector_utilities.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector_utilities.py b/vector_utilities.py index 033baeef1..40f9f14a0 100644 --- a/vector_utilities.py +++ b/vector_utilities.py @@ -100,7 +100,7 @@ def elementwise_dot(x, y): def angles_between_list_of_vectors(v0, v1, tol=1e-3): - r""" Calculate the angle between a collection of 3d vectors + r""" Calculate the angle between a collection of n-dimensional vectors Parameters ---------- @@ -129,8 +129,9 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): Examples -------- >>> npts = int(1e4) - >>> v0 = np.random.random((npts, 3)) - >>> v1 = np.random.random((npts, 3)) + >>> ndim = 3 + >>> v0 = np.random.random((npts, ndim)) + >>> v1 = np.random.random((npts, ndim)) >>> angles = angles_between_list_of_vectors(v0, v1) """ From a17f9a0c3cb4c60993fb64f43f631d9e93bd97c2 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 14:36:15 -0400 Subject: [PATCH 21/48] fixed bug in 2d rotation matrix function --- rotations2d.py | 6 ++---- tests/test_2d.py | 26 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/rotations2d.py b/rotations2d.py index fd8a8c61a..a6822e8c4 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -100,8 +100,8 @@ def rotation_matrices_from_angles(angles): R[:, 0, 0] = cosa R[:, 1, 1] = cosa - R[:, 0, 1] = sina - R[:, 1, 0] = -sina + R[:, 0, 1] = -sina + R[:, 1, 0] = sina return R @@ -145,8 +145,6 @@ def rotation_matrices_from_vectors(v0, v1): v1 = normalized_vectors(v1) angles = angles_between_list_of_vectors(v0, v1) - # where angles are 0.0, replace directions with v0 - mask = (angles==0.0) return rotation_matrices_from_angles(angles) diff --git a/tests/test_2d.py b/tests/test_2d.py index bb84bf5b9..6946be5fa 100644 --- a/tests/test_2d.py +++ b/tests/test_2d.py @@ -10,14 +10,32 @@ fixed_seed = 43 -def test_rotation_matrices_from_vectors(): +def test_rotation_matrices_from_vectors_1(): """ test to make sure null rotations return identiy matrix """ N = 1000 + v1 = np.random.random((N,2)) - rot_m = rotation_matrices_from_vectors(v1,v1) - - assert np.all(~np.isnan(rot_m)) \ No newline at end of file + assert np.all(~np.isnan(rot_m)) + + +def test_rotation_matrices_from_vectors(): + """ + validate 90 degree rotation result + """ + + N = 1000 + + v1 = np.zeros((N,2)) + v1[:,0] = 1 + v2 = np.zeros((N,2)) + v2[:,1] = 1 + + rot_m = rotation_matrices_from_vectors(v1,v2) + + v3 = rotate_vector_collection(rot_m, v1) + + assert np.allclose(v2,v3) \ No newline at end of file From 11fc5159ff0c4afa804894c923771b44c7b47dfa Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 14:37:11 -0400 Subject: [PATCH 22/48] small changes --- mcrotations.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mcrotations.py b/mcrotations.py index acd70c9a9..9239e5579 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -8,7 +8,11 @@ unicode_literals) import numpy as np from astropy.utils.misc import NumpyRNGContext -from .vector_calculations import * +from .vector_utilities import * +from .rotations2d import rotate_vector_collection as rotate_vector_collection_2d +from .rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from .rotations3d import rotate_vector_collection as rotate_vector_collection_3d +from .rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d __all__=['random_rotation_3d', 'random_rotation_2d', @@ -33,15 +37,14 @@ def random_rotation_3d(vectors, seed=None): rotated_vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors """ - - ran_angle = np.random.random(size=1)*(np.pi) with NumpyRNGContext(seed): ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 + ran_angle = np.random.random(size=1)*(np.pi) - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + ran_rot = rotation_matrices_from_angles_3d(ran_angle, ran_direction) - return rotate_vector_collection(ran_rot, vectors) + return rotate_vector_collection_3d(ran_rot, vectors) def random_rotation_2d(vectors, seed=None): @@ -62,14 +65,12 @@ def random_rotation_2d(vectors, seed=None): Numpy array of shape (npts, 3) storing a collection of 3d vectors """ - ran_angle = np.random.random(size=1)*(np.pi) - with NumpyRNGContext(seed): - ran_direction = normalized_vectors(np.random.random((2,)))*2.0 - 1.0 + ran_angle = np.random.random(size=1)*(np.pi) - ran_rot = rotation_matrices_from_angles(ran_angle, ran_direction) + ran_rot = rotation_matrices_from_angles_2d(ran_angle, ran_direction) - return rotate_vector_collection(ran_rot, vectors) + return rotate_vector_collection_2d(ran_rot, vectors) def random_perpendicular_directions(v, seed=None): From 0d4b9d138e1d63d18a4ae63ba2a7e8cf8cef6dcd Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 14:42:38 -0400 Subject: [PATCH 23/48] fixed bug --- mcrotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcrotations.py b/mcrotations.py index 648cf7a51..30f48dba9 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -69,7 +69,7 @@ def random_rotation_2d(vectors, seed=None): with NumpyRNGContext(seed): ran_angle = np.random.random(size=1)*(np.pi) - ran_rot = rotation_matrices_from_angles_2d(ran_angle, ran_direction) + ran_rot = rotation_matrices_from_angles_2d(ran_angle) return rotate_vector_collection_2d(ran_rot, vectors) From 0792eb6c0d331a6af2a2436e4d72ea4912ba2aa5 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 15:46:23 -0400 Subject: [PATCH 24/48] fixed bug in 2d rotation matrix from vectors --- rotations2d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rotations2d.py b/rotations2d.py index 220727284..d1df6434a 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -143,7 +143,9 @@ def rotation_matrices_from_vectors(v0, v1): """ v0 = normalized_vectors(v0) v1 = normalized_vectors(v1) - angles = angles_between_list_of_vectors(v0, v1) + + # use the atan2 function to get the direction of rotation right + angles = np.arctan2(v0[:,0], v0[:,1])-np.arctan2(v1[:,0],v1[:,1]) return rotation_matrices_from_angles(angles) From 56441702a0801e6349434151abd4c1e15433b1aa Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 16:54:14 -0400 Subject: [PATCH 25/48] moved rotate vector collection to its only file --- rotate_vector_collection.py | 78 +++++++++++++++++++++++++++++++++++++ rotations2d.py | 50 +----------------------- rotations3d.py | 50 +----------------------- 3 files changed, 80 insertions(+), 98 deletions(-) create mode 100644 rotate_vector_collection.py diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py new file mode 100644 index 000000000..ae54d3a7f --- /dev/null +++ b/rotate_vector_collection.py @@ -0,0 +1,78 @@ +""" +A function to rotate collectios of n-dimensional vectors +""" + +from __future__ import (division, print_function, absolute_import, + unicode_literals) +import numpy as np +from .vector_utilities import (elementwise_dot, elementwise_norm, + normalized_vectors, angles_between_list_of_vectors) + + +__all__=['rotate_vector_collection',] +__author__ = ['Duncan Campbell', 'Andrew Hearin'] + +def rotate_vector_collection(rotation_matrices, vectors, optimize=False): + r""" + Given a collection of rotation matrices and a collection of 3d vectors, + apply each an asscoiated matrix to rotate a corresponding vector. + + Parameters + ---------- + rotation_matrices : ndarray + The options are: + 1.) array of shape (npts, ndim, ndim) storing a collection of rotation matrices. + 2.) array of shape (ndim, ndim) storing a single rotation matrix + 3.) array of shape (nsets, ndim, ndim) storing a collection of rotation matrices. + + vectors : ndarray + The corresponding options for + 1.) array of shape (npts, ndim) storing a collection of 3d vectors + 2.) array of shape (npts, ndim) storing a collection of 3d vectors + 3.) array of shape (nsets, npts, ndim) storing a collection of 3d vectors + + Returns + ------- + rotated_vectors : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Examples + -------- + In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. + We'll use the `rotation_matrices_from_vectors` function to generate the + rotation matrices that rotate each `v0` into the corresponding `v1`. + Then we'll use the `rotate_vector_collection` function to apply each + rotation, and verify that we recover each of the `v1`. + + >>> npts = int(1e4) + >>> v0 = normalized_vectors(np.random.random((npts, 3))) + >>> v1 = normalized_vectors(np.random.random((npts, 3))) + >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) + >>> v2 = rotate_vector_collection(rotation_matrices, v0) + >>> assert np.allclose(v1, v2) + """ + + ndim = np.shape(rotation_matrices)[-1] + + # apply same rotation matrix to all vectors + if len(np.shape(rotation_matrices)) == 2: + return np.dot(rotation_matrices, vectors.T).T + # rotate each vector by associated rotation matrix + else: + # n1 sets of n2 vectors of ndim dimension + if len(np.shape(vectors))==3: + ein_string = 'ikl,ijl->ijk' + n1, n2, ndim = np.shape(vectors) + # n1 vectors of ndim dimension + elif len(np.shape(vectors))==2: + ein_string = 'ijk,ik->ij' + n1, ndim = np.shape(vectors) + + assert np.shape(rotation_matrices)==(n1,ndim,ndim) + + try: + return np.einsum(ein_string, rotation_matrices, vectors, optimize=optimize) + except TypeError: + return np.einsum(ein_string, rotation_matrices, vectors) + + diff --git a/rotations2d.py b/rotations2d.py index d1df6434a..3fb395f10 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -9,60 +9,12 @@ normalized_vectors, angles_between_list_of_vectors) -__all__=['rotate_vector_collection', - 'rotation_matrices_from_angles', +__all__=['rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] -def rotate_vector_collection(rotation_matrices, vectors, optimize=False): - r""" - Given a collection of rotation matrices and a collection of 2d vectors, - apply each matrix to rotate the corresponding vector. - - Parameters - ---------- - rotation_matrices : ndarray - Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices. - If an array of shape (2, 2) is passed, all the vectors - are rotated using the same rotation matrix. - - vectors : ndarray - Numpy array of shape (npts, 2) storing a collection of 3d vectors - - Returns - ------- - rotated_vectors : ndarray - Numpy array of shape (npts, 2) storing a collection of 3d vectors - - Examples - -------- - In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. - We'll use the `rotation_matrices_from_vectors` function to generate the - rotation matrices that rotate each `v0` into the corresponding `v1`. - Then we'll use the `rotate_vector_collection` function to apply each - rotation, and verify that we recover each of the `v1`. - - >>> npts = int(1e4) - >>> v0 = normalized_vectors(np.random.random((npts, 2))) - >>> v1 = normalized_vectors(np.random.random((npts, 2))) - >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) - >>> v2 = rotate_vector_collection(rotation_matrices, v0) - >>> assert np.allclose(v1, v2) - """ - - # apply same rotation matrix to all vectors - if np.shape(rotation_matrices) == (2, 2): - return np.dot(rotation_matrices, vectors.T).T - # rotate each vector by associated rotation matrix - else: - try: - return np.einsum('ijk,ik->ij', rotation_matrices, vectors, optimize=optimize) - except TypeError: - return np.einsum('ijk,ik->ij', rotation_matrices, vectors) - - def rotation_matrices_from_angles(angles): r""" Calculate a collection of rotation matrices defined by diff --git a/rotations3d.py b/rotations3d.py index aef2f2986..8194b3257 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -9,59 +9,11 @@ normalized_vectors, angles_between_list_of_vectors) -__all__=['rotate_vector_collection', - 'rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', +__all__=['rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', 'vectors_between_list_of_vectors', 'vectors_normal_to_planes', 'project_onto_plane'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] -def rotate_vector_collection(rotation_matrices, vectors, optimize=False): - r""" - Given a collection of rotation matrices and a collection of 3d vectors, - apply each matrix to rotate the corresponding vector. - - Parameters - ---------- - rotation_matrices : ndarray - Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices. - If an array of shape (3, 3) is passed, all the vectors - are rotated using the same rotation matrix. - - vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Returns - ------- - rotated_vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Examples - -------- - In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. - We'll use the `rotation_matrices_from_vectors` function to generate the - rotation matrices that rotate each `v0` into the corresponding `v1`. - Then we'll use the `rotate_vector_collection` function to apply each - rotation, and verify that we recover each of the `v1`. - - >>> npts = int(1e4) - >>> v0 = normalized_vectors(np.random.random((npts, 3))) - >>> v1 = normalized_vectors(np.random.random((npts, 3))) - >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) - >>> v2 = rotate_vector_collection(rotation_matrices, v0) - >>> assert np.allclose(v1, v2) - """ - - # apply same rotation matrix to all vectors - if np.shape(rotation_matrices) == (3, 3): - return np.dot(rotation_matrices, vectors.T).T - # rotate each vector by associated rotation matrix - else: - try: - return np.einsum('ijk,ik->ij', rotation_matrices, vectors, optimize=optimize) - except TypeError: - return np.einsum('ijk,ik->ij', rotation_matrices, vectors) - - def rotation_matrices_from_angles(angles, directions): r""" Calculate a collection of rotation matrices defined by From 5eef73c07d5fdfd55a0d30bd2a2cc43176b8bcd9 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 17:06:26 -0400 Subject: [PATCH 26/48] updated tests --- mcrotations.py | 7 +++---- rotate_vector_collection.py | 13 +++++++++++-- tests/test_2d.py | 1 + tests/test_3d.py | 1 + tests/test_utilities.py | 11 +++++------ 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/mcrotations.py b/mcrotations.py index 30f48dba9..e78f2c3d5 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -9,9 +9,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext from .vector_utilities import * -from .rotations2d import rotate_vector_collection as rotate_vector_collection_2d +from .rotate_vector_collection import rotate_vector_collection from .rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from .rotations3d import rotate_vector_collection as rotate_vector_collection_3d from .rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d @@ -45,7 +44,7 @@ def random_rotation_3d(vectors, seed=None): ran_rot = rotation_matrices_from_angles_3d(ran_angle, ran_direction) - return rotate_vector_collection_3d(ran_rot, vectors) + return rotate_vector_collection(ran_rot, vectors) def random_rotation_2d(vectors, seed=None): @@ -71,7 +70,7 @@ def random_rotation_2d(vectors, seed=None): ran_rot = rotation_matrices_from_angles_2d(ran_angle) - return rotate_vector_collection_2d(ran_rot, vectors) + return rotate_vector_collection(ran_rot, vectors) def random_perpendicular_directions(v, seed=None): diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index ae54d3a7f..1e8510d40 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -52,11 +52,19 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): >>> assert np.allclose(v1, v2) """ - ndim = np.shape(rotation_matrices)[-1] + ndim_rotm = np.shape(rotation_matrices)[-1] + ndim_vec = np.shape(vectors)[-1] + + assert ndim_rotm==ndim_vec # apply same rotation matrix to all vectors - if len(np.shape(rotation_matrices)) == 2: + if (len(np.shape(rotation_matrices)) == 2): return np.dot(rotation_matrices, vectors.T).T + + # apply same rotation matrix to all vectors + if (np.shape(rotation_matrices)[0] == 1): + return np.dot(rotation_matrices[0], vectors.T).T + # rotate each vector by associated rotation matrix else: # n1 sets of n2 vectors of ndim dimension @@ -68,6 +76,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): ein_string = 'ijk,ik->ij' n1, ndim = np.shape(vectors) + print(np.shape(rotation_matrices),(n1,ndim,ndim)) assert np.shape(rotation_matrices)==(n1,ndim,ndim) try: diff --git a/tests/test_2d.py b/tests/test_2d.py index 22ce039d1..b6454ddca 100644 --- a/tests/test_2d.py +++ b/tests/test_2d.py @@ -3,6 +3,7 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext +from ..rotate_vector_collection import rotate_vector_collection from ..rotations2d import * __all__ = ('test_rotation_matrices_from_vectors', diff --git a/tests/test_3d.py b/tests/test_3d.py index 2d75c7d9b..4b27d9393 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -3,6 +3,7 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext +from ..rotate_vector_collection import rotate_vector_collection from ..rotations3d import * __all__ = ('test_rotation_matrices_from_vectors', diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 52710470d..da10068bc 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -5,10 +5,9 @@ from astropy.utils.misc import NumpyRNGContext from ..vector_utilities import * +from ..rotate_vector_collection import rotate_vector_collection from ..rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from ..rotations2d import rotate_vector_collection as rotate_vector_collection_2d from ..rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d -from ..rotations3d import rotate_vector_collection as rotate_vector_collection_3d from ..rotations3d import vectors_normal_to_planes __all__ = ('test_normalized_vectors', 'test_elementwise_norm', 'test_elementwise_dot', @@ -78,7 +77,7 @@ def test_elementwise_dot(): # get a set of perpendicular vectors rot = rotation_matrices_from_angles_2d(np.ones(npts)*np.pi/2.0) - v2 = rotate_vector_collection_2d(rot, v1) + v2 = rotate_vector_collection(rot, v1) # assert the dot products are all zero assert np.allclose(elementwise_dot(v1,v2),np.zeros(npts)) @@ -95,7 +94,7 @@ def test_elementwise_dot(): # get a set of vectors rotated by 90 degrees rot = rotation_matrices_from_angles_3d(np.ones(npts)*np.pi/2.0, v3) - v4 = rotate_vector_collection_2d(rot, v1) + v4 = rotate_vector_collection(rot, v1) # assert the dot products are all zero assert np.allclose(elementwise_dot(v1,v4),np.zeros(npts)) @@ -117,7 +116,7 @@ def test_angles_between_list_of_vectors(): # get a set of vectors rotated by random angles angles = np.random.uniform(-np.pi/2.0, np.pi/2.0, npts) rot = rotation_matrices_from_angles_2d(angles) - v2 = rotate_vector_collection_2d(rot, v1) + v2 = rotate_vector_collection(rot, v1) # assert the dot products are all zero assert np.allclose(angles_between_list_of_vectors(v1,v2),np.fabs(angles)) @@ -133,7 +132,7 @@ def test_angles_between_list_of_vectors(): # get a set of vectors rotated by random angles angles = np.random.uniform(-np.pi/2.0, np.pi/2.0, npts) rot = rotation_matrices_from_angles_3d(angles, v3) - v4 = rotate_vector_collection_2d(rot, v1) + v4 = rotate_vector_collection(rot, v1) # assert the dot products are all zero assert np.allclose(angles_between_list_of_vectors(v1,v4),np.fabs(angles)) From fcad2ab49985c6578125a89925357ae37e7a0ccb Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 17:09:12 -0400 Subject: [PATCH 27/48] updated readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1709e4c6..2de9dfac4 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,14 @@ In order to use the functions in this package, you will need the following pytho Place this directory in your PYTHONPATH. The various functions can then be imported as, e.g.: ``` -from rotations.rotations3d import rotate_vector_collection +from rotations import rotate_vector_collection ``` +or for 2- and 3-D specific functions, + +``` +from rotations.rotations3d import rotation_matrices_from_vectors +``` contact: duncanc@andrew.cmu.edu \ No newline at end of file From 0290d3bdf102bb029d4c147009ec5df0f062bac7 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Oct 2018 17:16:39 -0400 Subject: [PATCH 28/48] expanded doc string --- rotate_vector_collection.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index 1e8510d40..b2414f1c4 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -15,7 +15,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): r""" Given a collection of rotation matrices and a collection of 3d vectors, - apply each an asscoiated matrix to rotate a corresponding vector. + apply an asscoiated matrix to rotate corresponding vector(s). Parameters ---------- @@ -26,7 +26,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): 3.) array of shape (nsets, ndim, ndim) storing a collection of rotation matrices. vectors : ndarray - The corresponding options for + The corresponding options for above are: 1.) array of shape (npts, ndim) storing a collection of 3d vectors 2.) array of shape (npts, ndim) storing a collection of 3d vectors 3.) array of shape (nsets, npts, ndim) storing a collection of 3d vectors @@ -36,6 +36,16 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): rotated_vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors + Notes + ----- + This function is set up to preform either: + 1. rotation operations on a single collection of vectors, + either applying a single rotation matrix to all vectors in the collection, + or applying a unique rotation matrix to each vector in the set. + 2. applying a one rotation matrix to each collection of vectors. + + The behavior of the function is determined by the arguments supplied by the user. + Examples -------- In this example, we'll randomly generate two sets of unit-vectors, `v0` and `v1`. From 7a12db4961827605b3338c2c5f2a0c73a07e0433 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Mon, 8 Oct 2018 12:40:28 -0400 Subject: [PATCH 29/48] removed print statement --- rotate_vector_collection.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index b2414f1c4..110bbb8f4 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -74,7 +74,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): # apply same rotation matrix to all vectors if (np.shape(rotation_matrices)[0] == 1): return np.dot(rotation_matrices[0], vectors.T).T - + # rotate each vector by associated rotation matrix else: # n1 sets of n2 vectors of ndim dimension @@ -85,10 +85,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): elif len(np.shape(vectors))==2: ein_string = 'ijk,ik->ij' n1, ndim = np.shape(vectors) - - print(np.shape(rotation_matrices),(n1,ndim,ndim)) - assert np.shape(rotation_matrices)==(n1,ndim,ndim) - + try: return np.einsum(ein_string, rotation_matrices, vectors, optimize=optimize) except TypeError: From 14f4b7f4516b2363c4f190a3ab4bebdd85ae0415 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Tue, 16 Oct 2018 10:33:14 -0400 Subject: [PATCH 30/48] updated doc string --- rotate_vector_collection.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index 110bbb8f4..093a941e4 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -14,7 +14,7 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): r""" - Given a collection of rotation matrices and a collection of 3d vectors, + Given a collection of rotation matrices and a collection of n-dimensional vectors, apply an asscoiated matrix to rotate corresponding vector(s). Parameters @@ -27,14 +27,14 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): vectors : ndarray The corresponding options for above are: - 1.) array of shape (npts, ndim) storing a collection of 3d vectors - 2.) array of shape (npts, ndim) storing a collection of 3d vectors - 3.) array of shape (nsets, npts, ndim) storing a collection of 3d vectors + 1.) array of shape (npts, ndim) storing a collection of ndim-dimensional vectors + 2.) array of shape (npts, ndim) storing a collection of ndim-dimensional vectors + 3.) array of shape (nsets, npts, ndim) storing a collection of ndim-dimensional vectors Returns ------- rotated_vectors : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors + Numpy array of shape (npts, ndim) storing a collection of ndim-dimensional vectors Notes ----- @@ -54,9 +54,9 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): Then we'll use the `rotate_vector_collection` function to apply each rotation, and verify that we recover each of the `v1`. - >>> npts = int(1e4) - >>> v0 = normalized_vectors(np.random.random((npts, 3))) - >>> v1 = normalized_vectors(np.random.random((npts, 3))) + >>> npts, ndim = int(1e4), 3 + >>> v0 = normalized_vectors(np.random.random((npts, ndim))) + >>> v1 = normalized_vectors(np.random.random((npts, ndim))) >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) >>> v2 = rotate_vector_collection(rotation_matrices, v0) >>> assert np.allclose(v1, v2) From 8802c0bf295de4832d8a7d591da2bb2b6a0313a5 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Tue, 16 Oct 2018 19:16:11 -0400 Subject: [PATCH 31/48] fixed bug --- mcrotations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mcrotations.py b/mcrotations.py index e78f2c3d5..67173d16a 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -37,11 +37,11 @@ def random_rotation_3d(vectors, seed=None): rotated_vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors """ - + with NumpyRNGContext(seed): ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 ran_angle = np.random.random(size=1)*(np.pi) - + ran_rot = rotation_matrices_from_angles_3d(ran_angle, ran_direction) return rotate_vector_collection(ran_rot, vectors) @@ -67,7 +67,7 @@ def random_rotation_2d(vectors, seed=None): with NumpyRNGContext(seed): ran_angle = np.random.random(size=1)*(np.pi) - + ran_rot = rotation_matrices_from_angles_2d(ran_angle) return rotate_vector_collection(ran_rot, vectors) @@ -97,7 +97,7 @@ def random_perpendicular_directions(v, seed=None): npts = v.shape[0] with NumpyRNGContext(seed): - w = np.random.random((npts, 3)) + w = np.random.random((npts, 3))*2.0 - 1.0 vnorms = elementwise_norm(v).reshape((npts, 1)) wnorms = elementwise_norm(w).reshape((npts, 1)) From ecc71d09b6bbcf2406aabaa8bfc959f32b83a2e4 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Mon, 22 Oct 2018 12:38:10 +0100 Subject: [PATCH 32/48] fixed bug in random perp vectors function --- __init__.py | 4 ++++ mcrotations.py | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index e69de29bb..17553a25d 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1,4 @@ +""" +""" + +from rotate_vector_collection import rotate_vector_collection diff --git a/mcrotations.py b/mcrotations.py index 67173d16a..6a7fd5288 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -16,7 +16,8 @@ __all__=['random_rotation_3d', 'random_rotation_2d', - 'random_perpendicular_directions'] + 'random_perpendicular_directions', + 'random_unit_vectors_3d'] __author__ = ['Duncan Campbell'] @@ -97,7 +98,7 @@ def random_perpendicular_directions(v, seed=None): npts = v.shape[0] with NumpyRNGContext(seed): - w = np.random.random((npts, 3))*2.0 - 1.0 + w = random_unit_vectors_3d(npts) vnorms = elementwise_norm(v).reshape((npts, 1)) wnorms = elementwise_norm(w).reshape((npts, 1)) @@ -113,3 +114,14 @@ def random_perpendicular_directions(v, seed=None): return e_v_perp/e_v_perp_norm +def random_unit_vectors_3d(npts): + r""" + """ + + ndim = 3 + x = np.random.normal(size=(npts,ndim), scale=1.0) + r = np.sqrt(np.sum((x)**2, axis=-1)) + + return (1.0/r[:,np.newaxis])*x + + From cfdf4a3299468e14146ac09c1385394a88f044f2 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Mon, 22 Oct 2018 16:26:27 +0100 Subject: [PATCH 33/48] added feature to angles between vectors --- vector_utilities.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/vector_utilities.py b/vector_utilities.py index 40f9f14a0..66a7a9e65 100644 --- a/vector_utilities.py +++ b/vector_utilities.py @@ -99,7 +99,7 @@ def elementwise_dot(x, y): return np.sum(x*y, axis=1) -def angles_between_list_of_vectors(v0, v1, tol=1e-3): +def angles_between_list_of_vectors(v0, v1, tol=1e-3, vn=None): r""" Calculate the angle between a collection of n-dimensional vectors Parameters @@ -118,6 +118,9 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): causes exceptions to be raised by the inverse cosine function. Default is 0.001. + n1 : ndarray + normal vector + Returns ------- angles : ndarray @@ -136,13 +139,18 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3): """ dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) - - # Protect against tiny numerical excesses beyond the range [-1 ,1] - mask1 = (dot > 1) & (dot < 1 + tol) - dot = np.where(mask1, 1., dot) - mask2 = (dot < -1) & (dot > -1 - tol) - dot = np.where(mask2, -1., dot) - - return np.arccos(dot) + + if vn is None: + # Protect against tiny numerical excesses beyond the range [-1 ,1] + mask1 = (dot > 1) & (dot < 1 + tol) + dot = np.where(mask1, 1., dot) + mask2 = (dot < -1) & (dot > -1 - tol) + dot = np.where(mask2, -1., dot) + a = np.arccos(dot) + else: + cross = np.cross(v0,v1) + a = np.arctan2(elementwise_dot(cross, vn), dot) + + return a From 7d76ca58fe6564669c695a48159be43ed7d055fc Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 9 Nov 2018 00:10:44 -0500 Subject: [PATCH 34/48] fixed bug --- mcrotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcrotations.py b/mcrotations.py index 6a7fd5288..fb54b01c9 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -40,7 +40,7 @@ def random_rotation_3d(vectors, seed=None): """ with NumpyRNGContext(seed): - ran_direction = normalized_vectors(np.random.random((3,)))*2.0 - 1.0 + ran_direction = random_unit_vectors_3d(1)[0] ran_angle = np.random.random(size=1)*(np.pi) ran_rot = rotation_matrices_from_angles_3d(ran_angle, ran_direction) From 7f87c464c813beb6ba302348879811ec394f9a03 Mon Sep 17 00:00:00 2001 From: Francois Lanusse Date: Wed, 27 Feb 2019 22:31:59 -0800 Subject: [PATCH 35/48] Fixes the __init__.py for python3 compat I think you need the . there, my python complains if it's not there --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 17553a25d..eff176c63 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ """ """ -from rotate_vector_collection import rotate_vector_collection +from .rotate_vector_collection import rotate_vector_collection From ba2d4cefec7415da8a2a0ad8603231f8be3585bb Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Tue, 5 Mar 2019 15:25:23 -0500 Subject: [PATCH 36/48] fixed bug --- rotate_vector_collection.py | 13 +++--- tests/test_rotate_vector_collection.py | 55 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tests/test_rotate_vector_collection.py diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index 093a941e4..ce93e3bbb 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -64,17 +64,20 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): ndim_rotm = np.shape(rotation_matrices)[-1] ndim_vec = np.shape(vectors)[-1] - assert ndim_rotm==ndim_vec + if len(np.shape(vectors))==2: + ntps, ndim = np.shape(vectors) + nsets = 0 + elif len(np.shape(vectors))==3: + nsets, ntps, ndim = np.shape(vectors) + # apply same rotation matrix to all vectors if (len(np.shape(rotation_matrices)) == 2): + if nsets == 1: + vectors = vectors[0] return np.dot(rotation_matrices, vectors.T).T - # apply same rotation matrix to all vectors - if (np.shape(rotation_matrices)[0] == 1): - return np.dot(rotation_matrices[0], vectors.T).T - # rotate each vector by associated rotation matrix else: # n1 sets of n2 vectors of ndim dimension diff --git a/tests/test_rotate_vector_collection.py b/tests/test_rotate_vector_collection.py new file mode 100644 index 000000000..40df3ebcc --- /dev/null +++ b/tests/test_rotate_vector_collection.py @@ -0,0 +1,55 @@ +""" +""" +import numpy as np +from astropy.utils.misc import NumpyRNGContext + +from ..rotate_vector_collection import rotate_vector_collection +from ..rotations3d import * + +__all__ = ('test_rotation_3d', + ) + +fixed_seed = 43 + +def test_rotation_1(): + """ + single rotation matrix + single set of points + """ + + # create a single rotation matrix + nsets = 1 + ndim = 3 + v1 = np.random.random((nsets,ndim)) + v2 = np.random.random((nsets,ndim)) + rot_m = rotation_matrices_from_vectors(v1,v2) + rot = rot_m[0] + + # create a single set of vectors + npts = 1000 + ndim = 3 + v3 = np.random.random((npts,ndim)) + + v4 = rotate_vector_collection(rot, v3) + + assert np.shape(v4)==(npts, ndim) + + +def test_rotation_3(): + """ + nset of rotation matrices + nset of npts of points + """ + + nsets = 2 + ndim = 3 + v1 = np.random.random((nsets,ndim)) + v2 = np.random.random((nsets,ndim)) + + rot = rotation_matrices_from_vectors(v1,v2) + + npts = 1000 + ndim = 3 + v3 = np.random.random((nsets, npts, ndim)) + + v4 = rotate_vector_collection(rot, v3) + + assert np.shape(v4)==(nsets, npts, ndim) From e213c99a45a18a1a641b80044ed59b32fcf956f1 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 27 Jun 2019 14:03:38 -0400 Subject: [PATCH 37/48] removed relative imports --- __init__.py | 2 +- mcrotations.py | 8 ++++---- rotate_vector_collection.py | 2 +- rotations2d.py | 2 +- rotations3d.py | 2 +- tests/test_2d.py | 4 ++-- tests/test_3d.py | 4 ++-- tests/test_mcrotations.py | 4 ++-- tests/test_rotate_vector_collection.py | 4 ++-- tests/test_utilities.py | 10 +++++----- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/__init__.py b/__init__.py index 17553a25d..eff176c63 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ """ """ -from rotate_vector_collection import rotate_vector_collection +from .rotate_vector_collection import rotate_vector_collection diff --git a/mcrotations.py b/mcrotations.py index fb54b01c9..89ac195fd 100644 --- a/mcrotations.py +++ b/mcrotations.py @@ -8,10 +8,10 @@ unicode_literals) import numpy as np from astropy.utils.misc import NumpyRNGContext -from .vector_utilities import * -from .rotate_vector_collection import rotate_vector_collection -from .rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from .rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from vector_utilities import * +from rotate_vector_collection import rotate_vector_collection +from rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d __all__=['random_rotation_3d', diff --git a/rotate_vector_collection.py b/rotate_vector_collection.py index ce93e3bbb..f581bdeaa 100644 --- a/rotate_vector_collection.py +++ b/rotate_vector_collection.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import (elementwise_dot, elementwise_norm, +from rotations.vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) diff --git a/rotations2d.py b/rotations2d.py index 3fb395f10..5f99456c0 100644 --- a/rotations2d.py +++ b/rotations2d.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import (elementwise_dot, elementwise_norm, +from rotations.vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) diff --git a/rotations3d.py b/rotations3d.py index 8194b3257..f8924f459 100644 --- a/rotations3d.py +++ b/rotations3d.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import (elementwise_dot, elementwise_norm, +from rotations.vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) diff --git a/tests/test_2d.py b/tests/test_2d.py index b6454ddca..3b276afb9 100644 --- a/tests/test_2d.py +++ b/tests/test_2d.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..rotate_vector_collection import rotate_vector_collection -from ..rotations2d import * +from rotations.rotate_vector_collection import rotate_vector_collection +from rotations.rotations2d import * __all__ = ('test_rotation_matrices_from_vectors', 'test_rotation_matrices_from_angles', diff --git a/tests/test_3d.py b/tests/test_3d.py index 4b27d9393..8db27802e 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..rotate_vector_collection import rotate_vector_collection -from ..rotations3d import * +from rotations.rotate_vector_collection import rotate_vector_collection +from rotations.rotations3d import * __all__ = ('test_rotation_matrices_from_vectors', 'test_rotation_matrices_from_angles', diff --git a/tests/test_mcrotations.py b/tests/test_mcrotations.py index 0577dff2a..d118bc807 100644 --- a/tests/test_mcrotations.py +++ b/tests/test_mcrotations.py @@ -2,8 +2,8 @@ """ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..vector_utilities import * -from ..mcrotations import * +from rotations.vector_utilities import * +from rotations.mcrotations import * __all__ = ('test_random_rotation_3d', 'test_random_rotation_3d', diff --git a/tests/test_rotate_vector_collection.py b/tests/test_rotate_vector_collection.py index 40df3ebcc..eca44aff1 100644 --- a/tests/test_rotate_vector_collection.py +++ b/tests/test_rotate_vector_collection.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..rotate_vector_collection import rotate_vector_collection -from ..rotations3d import * +from rotations.rotate_vector_collection import rotate_vector_collection +from rotations.rotations3d import * __all__ = ('test_rotation_3d', ) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index da10068bc..188d85789 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -4,11 +4,11 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..vector_utilities import * -from ..rotate_vector_collection import rotate_vector_collection -from ..rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from ..rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d -from ..rotations3d import vectors_normal_to_planes +from rotations.vector_utilities import * +from rotations.rotate_vector_collection import rotate_vector_collection +from rotations.rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from rotations.rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from rotations.rotations3d import vectors_normal_to_planes __all__ = ('test_normalized_vectors', 'test_elementwise_norm', 'test_elementwise_dot', 'test_angles_between_list_of_vectors') From 00f1c14e54821d681423490bc736410a82483716 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 27 Jun 2019 14:26:25 -0400 Subject: [PATCH 38/48] updated readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2de9dfac4..50baae394 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This package contains functions to rotate collections of 2D and 3D vectors. -Some of the functionality of this package is taken from the [Halotools](https://halotools.readthedocs.io/en/latest/) utilities sudmodule, and reproduced here for convenience. +Some of the functionality of this package is taken from the [Halotools](https://halotools.readthedocs.io/en/latest/) utilities sudmodule, reproduced here for convenience. ## Description @@ -36,5 +36,12 @@ or for 2- and 3-D specific functions, from rotations.rotations3d import rotation_matrices_from_vectors ``` +You can run the testing suite for this package using the [pytest](https://docs.pytest.org/en/latest/) framework by executing the following command in the package directory: + +``` +pytest +``` + + contact: duncanc@andrew.cmu.edu \ No newline at end of file From 10330af3ed1018790667bf18a812e0174297d07c Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 3 Jun 2020 10:40:02 -0400 Subject: [PATCH 39/48] moved code into rotations subdirectory --- README.md => rotations/README.md | 0 __init__.py => rotations/__init__.py | 0 mcrotations.py => rotations/mcrotations.py | 0 .../rotate_vector_collection.py | 0 rotations2d.py => rotations/rotations2d.py | 0 rotations3d.py => rotations/rotations3d.py | 0 {tests => rotations/tests}/__init__.py | 0 {tests => rotations/tests}/test_2d.py | 0 {tests => rotations/tests}/test_3d.py | 0 {tests => rotations/tests}/test_mcrotations.py | 0 {tests => rotations/tests}/test_rotate_vector_collection.py | 0 {tests => rotations/tests}/test_utilities.py | 0 vector_utilities.py => rotations/vector_utilities.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename README.md => rotations/README.md (100%) rename __init__.py => rotations/__init__.py (100%) rename mcrotations.py => rotations/mcrotations.py (100%) rename rotate_vector_collection.py => rotations/rotate_vector_collection.py (100%) rename rotations2d.py => rotations/rotations2d.py (100%) rename rotations3d.py => rotations/rotations3d.py (100%) rename {tests => rotations/tests}/__init__.py (100%) rename {tests => rotations/tests}/test_2d.py (100%) rename {tests => rotations/tests}/test_3d.py (100%) rename {tests => rotations/tests}/test_mcrotations.py (100%) rename {tests => rotations/tests}/test_rotate_vector_collection.py (100%) rename {tests => rotations/tests}/test_utilities.py (100%) rename vector_utilities.py => rotations/vector_utilities.py (100%) diff --git a/README.md b/rotations/README.md similarity index 100% rename from README.md rename to rotations/README.md diff --git a/__init__.py b/rotations/__init__.py similarity index 100% rename from __init__.py rename to rotations/__init__.py diff --git a/mcrotations.py b/rotations/mcrotations.py similarity index 100% rename from mcrotations.py rename to rotations/mcrotations.py diff --git a/rotate_vector_collection.py b/rotations/rotate_vector_collection.py similarity index 100% rename from rotate_vector_collection.py rename to rotations/rotate_vector_collection.py diff --git a/rotations2d.py b/rotations/rotations2d.py similarity index 100% rename from rotations2d.py rename to rotations/rotations2d.py diff --git a/rotations3d.py b/rotations/rotations3d.py similarity index 100% rename from rotations3d.py rename to rotations/rotations3d.py diff --git a/tests/__init__.py b/rotations/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to rotations/tests/__init__.py diff --git a/tests/test_2d.py b/rotations/tests/test_2d.py similarity index 100% rename from tests/test_2d.py rename to rotations/tests/test_2d.py diff --git a/tests/test_3d.py b/rotations/tests/test_3d.py similarity index 100% rename from tests/test_3d.py rename to rotations/tests/test_3d.py diff --git a/tests/test_mcrotations.py b/rotations/tests/test_mcrotations.py similarity index 100% rename from tests/test_mcrotations.py rename to rotations/tests/test_mcrotations.py diff --git a/tests/test_rotate_vector_collection.py b/rotations/tests/test_rotate_vector_collection.py similarity index 100% rename from tests/test_rotate_vector_collection.py rename to rotations/tests/test_rotate_vector_collection.py diff --git a/tests/test_utilities.py b/rotations/tests/test_utilities.py similarity index 100% rename from tests/test_utilities.py rename to rotations/tests/test_utilities.py diff --git a/vector_utilities.py b/rotations/vector_utilities.py similarity index 100% rename from vector_utilities.py rename to rotations/vector_utilities.py From 7620ff83c2576337316ab1c2027f65c085599a9e Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 3 Jun 2020 11:26:58 -0400 Subject: [PATCH 40/48] incorporated rotations and tests into utils directory structure --- halotools/utils/__init__.py | 2 + {rotations => halotools/utils}/mcrotations.py | 23 +++++++-- .../utils}/rotate_vector_collection.py | 2 + {rotations => halotools/utils}/rotations2d.py | 5 +- {rotations => halotools/utils}/rotations3d.py | 3 +- .../utils/tests/test_2d_rotations.py | 4 +- .../utils/tests/test_3d_rotations.py | 4 +- .../utils}/tests/test_mcrotations.py | 4 +- .../tests/test_rotate_vector_collection.py | 0 .../utils/tests/test_vector_utilities.py | 10 ++-- .../utils}/vector_utilities.py | 0 rotations/README.md | 47 ------------------- rotations/__init__.py | 4 -- rotations/tests/__init__.py | 0 14 files changed, 39 insertions(+), 69 deletions(-) rename {rotations => halotools/utils}/mcrotations.py (84%) rename {rotations => halotools/utils}/rotate_vector_collection.py (96%) rename {rotations => halotools/utils}/rotations2d.py (96%) rename {rotations => halotools/utils}/rotations3d.py (98%) rename rotations/tests/test_2d.py => halotools/utils/tests/test_2d_rotations.py (92%) rename rotations/tests/test_3d.py => halotools/utils/tests/test_3d_rotations.py (91%) rename {rotations => halotools/utils}/tests/test_mcrotations.py (92%) rename {rotations => halotools/utils}/tests/test_rotate_vector_collection.py (100%) rename rotations/tests/test_utilities.py => halotools/utils/tests/test_vector_utilities.py (90%) rename {rotations => halotools/utils}/vector_utilities.py (100%) delete mode 100644 rotations/README.md delete mode 100644 rotations/__init__.py delete mode 100644 rotations/tests/__init__.py diff --git a/halotools/utils/__init__.py b/halotools/utils/__init__.py index aca5bb586..9a65425ac 100644 --- a/halotools/utils/__init__.py +++ b/halotools/utils/__init__.py @@ -14,3 +14,5 @@ from .distribution_matching import * from .probabilistic_binning import fuzzy_digitize from .conditional_percentile import sliding_conditional_percentile +from .vector_utilities import * +from .rotate_vector_collection import rotate_vector_collection \ No newline at end of file diff --git a/rotations/mcrotations.py b/halotools/utils/mcrotations.py similarity index 84% rename from rotations/mcrotations.py rename to halotools/utils/mcrotations.py index 89ac195fd..27db2f520 100644 --- a/rotations/mcrotations.py +++ b/halotools/utils/mcrotations.py @@ -8,10 +8,10 @@ unicode_literals) import numpy as np from astropy.utils.misc import NumpyRNGContext -from vector_utilities import * -from rotate_vector_collection import rotate_vector_collection -from rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from .vector_utilities import * +from .rotate_vector_collection import rotate_vector_collection +from .rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from .rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d __all__=['random_rotation_3d', @@ -116,6 +116,7 @@ def random_perpendicular_directions(v, seed=None): def random_unit_vectors_3d(npts): r""" + generate random 3D unit vectors """ ndim = 3 @@ -125,3 +126,17 @@ def random_unit_vectors_3d(npts): return (1.0/r[:,np.newaxis])*x +def random_unit_vectors_2d(npts): + r""" + generate random 2D unit vectors + """ + + ndim = 2 + r = 1.0 + phi = np.random.uniform(0.0, 2.0*np.pi, size=(npts,ndim)) + x = r*np.cos(phi) + y = r*np.sin(phi) + + return np.vstack((x,y)).T + + diff --git a/rotations/rotate_vector_collection.py b/halotools/utils/rotate_vector_collection.py similarity index 96% rename from rotations/rotate_vector_collection.py rename to halotools/utils/rotate_vector_collection.py index f581bdeaa..76783dfb3 100644 --- a/rotations/rotate_vector_collection.py +++ b/halotools/utils/rotate_vector_collection.py @@ -54,6 +54,8 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): Then we'll use the `rotate_vector_collection` function to apply each rotation, and verify that we recover each of the `v1`. + >>> from halotools.utils.rotations3d import rotation_matrices_from_vectors + >>> from halotools.utils import normalized_vectors >>> npts, ndim = int(1e4), 3 >>> v0 = normalized_vectors(np.random.random((npts, ndim))) >>> v1 = normalized_vectors(np.random.random((npts, ndim))) diff --git a/rotations/rotations2d.py b/halotools/utils/rotations2d.py similarity index 96% rename from rotations/rotations2d.py rename to halotools/utils/rotations2d.py index 5f99456c0..e1608439b 100644 --- a/rotations/rotations2d.py +++ b/halotools/utils/rotations2d.py @@ -5,7 +5,7 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from rotations.vector_utilities import (elementwise_dot, elementwise_norm, +from .vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) @@ -32,9 +32,10 @@ def rotation_matrices_from_angles(angles): Examples -------- + >>> from halotools.utils.mcrotations import random_unit_vectors_2d >>> npts = int(1e4) >>> angles = np.random.uniform(-np.pi/2., np.pi/2., npts) - >>> rotation_matrices = rotation_matrices_from_angles(angles, directions) + >>> rotation_matrices = rotation_matrices_from_angles(angles) Notes ----- diff --git a/rotations/rotations3d.py b/halotools/utils/rotations3d.py similarity index 98% rename from rotations/rotations3d.py rename to halotools/utils/rotations3d.py index f8924f459..2f898dab6 100644 --- a/rotations/rotations3d.py +++ b/halotools/utils/rotations3d.py @@ -5,8 +5,9 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from rotations.vector_utilities import (elementwise_dot, elementwise_norm, +from .vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) +from halotools.utils import rotate_vector_collection __all__=['rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', diff --git a/rotations/tests/test_2d.py b/halotools/utils/tests/test_2d_rotations.py similarity index 92% rename from rotations/tests/test_2d.py rename to halotools/utils/tests/test_2d_rotations.py index 3b276afb9..b6454ddca 100644 --- a/rotations/tests/test_2d.py +++ b/halotools/utils/tests/test_2d_rotations.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from rotations.rotate_vector_collection import rotate_vector_collection -from rotations.rotations2d import * +from ..rotate_vector_collection import rotate_vector_collection +from ..rotations2d import * __all__ = ('test_rotation_matrices_from_vectors', 'test_rotation_matrices_from_angles', diff --git a/rotations/tests/test_3d.py b/halotools/utils/tests/test_3d_rotations.py similarity index 91% rename from rotations/tests/test_3d.py rename to halotools/utils/tests/test_3d_rotations.py index 8db27802e..4b27d9393 100644 --- a/rotations/tests/test_3d.py +++ b/halotools/utils/tests/test_3d_rotations.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from rotations.rotate_vector_collection import rotate_vector_collection -from rotations.rotations3d import * +from ..rotate_vector_collection import rotate_vector_collection +from ..rotations3d import * __all__ = ('test_rotation_matrices_from_vectors', 'test_rotation_matrices_from_angles', diff --git a/rotations/tests/test_mcrotations.py b/halotools/utils/tests/test_mcrotations.py similarity index 92% rename from rotations/tests/test_mcrotations.py rename to halotools/utils/tests/test_mcrotations.py index d118bc807..0577dff2a 100644 --- a/rotations/tests/test_mcrotations.py +++ b/halotools/utils/tests/test_mcrotations.py @@ -2,8 +2,8 @@ """ import numpy as np from astropy.utils.misc import NumpyRNGContext -from rotations.vector_utilities import * -from rotations.mcrotations import * +from ..vector_utilities import * +from ..mcrotations import * __all__ = ('test_random_rotation_3d', 'test_random_rotation_3d', diff --git a/rotations/tests/test_rotate_vector_collection.py b/halotools/utils/tests/test_rotate_vector_collection.py similarity index 100% rename from rotations/tests/test_rotate_vector_collection.py rename to halotools/utils/tests/test_rotate_vector_collection.py diff --git a/rotations/tests/test_utilities.py b/halotools/utils/tests/test_vector_utilities.py similarity index 90% rename from rotations/tests/test_utilities.py rename to halotools/utils/tests/test_vector_utilities.py index 188d85789..da10068bc 100644 --- a/rotations/tests/test_utilities.py +++ b/halotools/utils/tests/test_vector_utilities.py @@ -4,11 +4,11 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from rotations.vector_utilities import * -from rotations.rotate_vector_collection import rotate_vector_collection -from rotations.rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d -from rotations.rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d -from rotations.rotations3d import vectors_normal_to_planes +from ..vector_utilities import * +from ..rotate_vector_collection import rotate_vector_collection +from ..rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d +from ..rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d +from ..rotations3d import vectors_normal_to_planes __all__ = ('test_normalized_vectors', 'test_elementwise_norm', 'test_elementwise_dot', 'test_angles_between_list_of_vectors') diff --git a/rotations/vector_utilities.py b/halotools/utils/vector_utilities.py similarity index 100% rename from rotations/vector_utilities.py rename to halotools/utils/vector_utilities.py diff --git a/rotations/README.md b/rotations/README.md deleted file mode 100644 index 50baae394..000000000 --- a/rotations/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Rotations - -This package contains functions to rotate collections of 2D and 3D vectors. - -Some of the functionality of this package is taken from the [Halotools](https://halotools.readthedocs.io/en/latest/) utilities sudmodule, reproduced here for convenience. - - -## Description - -This package contains tools to perform: - -* rotations of 3D vectors, -* rotations of 2D vectors, -* and monte carlo rotations of 2D and 3D vectors. - - -## Requirements - -In order to use the functions in this package, you will need the following python packages installed: - -* numpy -* astropy - - -## Installation - -Place this directory in your PYTHONPATH. The various functions can then be imported as, e.g.: - -``` -from rotations import rotate_vector_collection -``` - -or for 2- and 3-D specific functions, - -``` -from rotations.rotations3d import rotation_matrices_from_vectors -``` - -You can run the testing suite for this package using the [pytest](https://docs.pytest.org/en/latest/) framework by executing the following command in the package directory: - -``` -pytest -``` - - -contact: -duncanc@andrew.cmu.edu \ No newline at end of file diff --git a/rotations/__init__.py b/rotations/__init__.py deleted file mode 100644 index eff176c63..000000000 --- a/rotations/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -""" - -from .rotate_vector_collection import rotate_vector_collection diff --git a/rotations/tests/__init__.py b/rotations/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 From ced904c1493c30d791c6788d26565d55b2c665f4 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Wed, 3 Jun 2020 11:45:05 -0400 Subject: [PATCH 41/48] improved doc strings --- halotools/utils/mcrotations.py | 67 ++++++++++++++++++++++++++--- halotools/utils/vector_utilities.py | 28 ++++++++---- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/halotools/utils/mcrotations.py b/halotools/utils/mcrotations.py index 27db2f520..ab5c0eccc 100644 --- a/halotools/utils/mcrotations.py +++ b/halotools/utils/mcrotations.py @@ -1,4 +1,4 @@ -r""" +""" A set of rotation utilites that apply monte carlo roations to collections of 2- and 3-dimensional vectors """ @@ -17,7 +17,8 @@ __all__=['random_rotation_3d', 'random_rotation_2d', 'random_perpendicular_directions', - 'random_unit_vectors_3d'] + 'random_unit_vectors_3d', + 'random_unit_vectors_2d'] __author__ = ['Duncan Campbell'] @@ -37,6 +38,17 @@ def random_rotation_3d(vectors, seed=None): ------- rotated_vectors : ndarray Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Example + ------- + Create a random set of 3D unit vectors. + + >>> npts = 1000 + >>> x1 = random_unit_vectors_3d(npts) + + Randomly rotate these vectors. + + >>> x2 = random_rotation_3d(x1) """ with NumpyRNGContext(seed): @@ -64,6 +76,17 @@ def random_rotation_2d(vectors, seed=None): ------- rotated_vectors : ndarray Numpy array of shape (npts, 2) storing a collection of 2d vectors + + Example + ------- + Create a random set of 2D unit vectors. + + >>> npts = 1000 + >>> x1 = random_unit_vectors_2d(npts) + + Randomly rotate these vectors. + + >>> x2 = random_rotation_2d(x1) """ with NumpyRNGContext(seed): @@ -76,7 +99,7 @@ def random_rotation_2d(vectors, seed=None): def random_perpendicular_directions(v, seed=None): r""" - Given an input list of 3d vectors, v, return a list of 3d vectors + Given an input list of 3D vectors, v, return a list of 3D vectors such that each returned vector has unit-length and is orthogonal to the corresponding vector in v. @@ -92,6 +115,17 @@ def random_perpendicular_directions(v, seed=None): ------- result : ndarray Numpy array of shape (npts, 3) + + Example + ------- + Create a random set of 3D unit vectors. + + >>> npts = 1000 + >>> x1 = random_unit_vectors_3d(npts) + + For each vector in x1, create a perpendicular vector + + >>> x2 = random_perpendicular_directions(x1) """ v = np.atleast_2d(v) @@ -116,7 +150,17 @@ def random_perpendicular_directions(v, seed=None): def random_unit_vectors_3d(npts): r""" - generate random 3D unit vectors + Generate random 3D unit vectors. + + Parameters + ---------- + npts : int + number of vectors + + Returns + ------- + result : numpy.array + Numpy array of shape (npts, 3) containing random unit vectors """ ndim = 3 @@ -128,12 +172,21 @@ def random_unit_vectors_3d(npts): def random_unit_vectors_2d(npts): r""" - generate random 2D unit vectors + Generate random 2D unit vectors. + + Parameters + ---------- + npts : int + number of vectors + + Returns + ------- + result : numpy.array + Numpy array of shape (npts, 2) containing random unit vectors """ - ndim = 2 r = 1.0 - phi = np.random.uniform(0.0, 2.0*np.pi, size=(npts,ndim)) + phi = np.random.uniform(0.0, 2.0*np.pi, size=(npts,)) x = r*np.cos(phi) y = r*np.sin(phi) diff --git a/halotools/utils/vector_utilities.py b/halotools/utils/vector_utilities.py index 66a7a9e65..62164c53b 100644 --- a/halotools/utils/vector_utilities.py +++ b/halotools/utils/vector_utilities.py @@ -27,6 +27,8 @@ def normalized_vectors(vectors): Examples -------- + Let's create a set of semi-random 3D unit vectors. + >>> npts = int(1e3) >>> ndim = 3 >>> x = np.random.random((npts, ndim)) @@ -74,24 +76,29 @@ def elementwise_dot(x, y): Parameters ---------- x : ndarray - Numpy array of shape (npts, ndim) storing a collection of dimensional points + Numpy array of shape (npts, ndim) storing a collection of n-dimensional vectors y : ndarray - Numpy array of shape (npts, ndim) storing a collection of dimensional points + Numpy array of shape (npts, ndim) storing a collection of n-dimensional vectors Returns ------- result : ndarray Numpy array of shape (npts, ) storing the dot product between each - pair of corresponding points in x and y. + pair of corresponding vectors in x and y. Examples -------- + Let's create two sets of semi-random 3D vectors, x1 and x2. + >>> npts = int(1e3) >>> ndim = 3 - >>> x = np.random.random((npts, ndim)) - >>> y = np.random.random((npts, ndim)) - >>> dots = elementwise_dot(x, y) + >>> x1 = np.random.random((npts, ndim)) + >>> x2 = np.random.random((npts, ndim)) + + We then can find the dot product between each pair of vectors in x1 and x2. + + >>> dots = elementwise_dot(x1, x2) """ x = np.atleast_2d(x) @@ -131,11 +138,16 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3, vn=None): Examples -------- + Let's create two sets of semi-random 3D unit vectors. + >>> npts = int(1e4) >>> ndim = 3 - >>> v0 = np.random.random((npts, ndim)) >>> v1 = np.random.random((npts, ndim)) - >>> angles = angles_between_list_of_vectors(v0, v1) + >>> v2 = np.random.random((npts, ndim)) + + We then can find the angle between each pair of vectors in v1 and v2. + + >>> angles = angles_between_list_of_vectors(v1, v2) """ dot = elementwise_dot(normalized_vectors(v0), normalized_vectors(v1)) From 1e0750706d6c86bd41900d6f1e61e0416c77cc4f Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 11:57:12 -0400 Subject: [PATCH 42/48] improved doc strings and testing --- halotools/utils/rotations2d.py | 35 ++++++++++- halotools/utils/rotations3d.py | 73 +++++++++++++++++++--- halotools/utils/tests/test_2d_rotations.py | 31 +++++++-- 3 files changed, 124 insertions(+), 15 deletions(-) diff --git a/halotools/utils/rotations2d.py b/halotools/utils/rotations2d.py index e1608439b..90e2e8a78 100644 --- a/halotools/utils/rotations2d.py +++ b/halotools/utils/rotations2d.py @@ -1,5 +1,5 @@ r""" -A set of rotation utilites for manipulating 2-dimensional vectors +A set of vector rotation utilites for manipulating 2-dimensional vectors """ from __future__ import (division, print_function, absolute_import, @@ -103,8 +103,8 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles) -def rotation_matrices_from_basis(ux, uy): - """ +def rotation_matrices_from_basis(ux, uy, tol=np.pi/1000.0): + r""" Calculate a collection of rotation matrices defined by an input collection of basis vectors. @@ -116,10 +116,34 @@ def rotation_matrices_from_basis(ux, uy): uy : array_like Numpy array of shape (npts, 2) storing a collection of unit vectors + tol : float, optional + angular tolerance for orthogonality of the input basis vectors in radians. + Returns ------- matrices : ndarray Numpy array of shape (npts, 2, 2) storing a collection of rotation matrices + + Example + ------- + Let's build a rotation matrix that roates from a frame + rotated by 45 degrees to the standard frame. + + >>> u1 = [np.sqrt(2), np.sqrt(2)] + >>> u2 = [np.sqrt(2), -1.0*np.sqrt(2)] + >>> rot = rotation_matrices_from_basis(u1, u2) + + Notes + ----- + The rotation matrices transform from the Cartesian frame defined by the standard + basis vectors, + + .. math:: + \u_1=(1,0) + \u_2=(0,1) + + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 2d vectors """ N = np.shape(ux)[0] @@ -131,6 +155,11 @@ def rotation_matrices_from_basis(ux, uy): ux = normalized_vectors(ux) uy = normalized_vectors(uy) + d_theta = angles_between_list_of_vectors(ux, uy) + if np.any((np.pi/2.0 - d_theta) > tol): + msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') + raise ValueError(msg) + r_11 = elementwise_dot(ex, ux) r_12 = elementwise_dot(ex, uy) diff --git a/halotools/utils/rotations3d.py b/halotools/utils/rotations3d.py index 2f898dab6..0dba00670 100644 --- a/halotools/utils/rotations3d.py +++ b/halotools/utils/rotations3d.py @@ -1,5 +1,5 @@ """ -A set of rotation utilites for manipulating 3-dimensional vectors +A set of vector rotation utilites for manipulating 3-dimensional vectors """ from __future__ import (division, print_function, absolute_import, @@ -35,9 +35,12 @@ def rotation_matrices_from_angles(angles, directions): Examples -------- + Create a set of random rotation matrices. + + >>> from halotools.utils.mcrotations import random_unit_vectors_3d >>> npts = int(1e4) >>> angles = np.random.uniform(-np.pi/2., np.pi/2., npts) - >>> directions = np.random.random((npts, 3)) + >>> directions = random_unit_vectors_3d(npts) >>> rotation_matrices = rotation_matrices_from_angles(angles, directions) Notes @@ -93,9 +96,12 @@ def rotation_matrices_from_vectors(v0, v1): Examples -------- + Create a set of random rotation matrices. + + >>> from halotools.utils.mcrotations import random_unit_vectors_3d >>> npts = int(1e4) - >>> v0 = np.random.random((npts, 3)) - >>> v1 = np.random.random((npts, 3)) + >>> v0 = random_unit_vectors_3d(npts) + >>> v1 = random_unit_vectors_3d(npts) >>> rotation_matrices = rotation_matrices_from_vectors(v0, v1) Notes @@ -118,8 +124,8 @@ def rotation_matrices_from_vectors(v0, v1): return rotation_matrices_from_angles(angles, directions) -def rotation_matrices_from_basis(ux, uy, uz): - """ +def rotation_matrices_from_basis(ux, uy, uz, tol=np.pi/1000.0): + r""" Calculate a collection of rotation matrices defined by a set of basis vectors Parameters @@ -133,10 +139,36 @@ def rotation_matrices_from_basis(ux, uy, uz): uz : array_like Numpy array of shape (npts, 3) storing a collection of unit vexctors + tol : float, optional + angular tolerance for orthogonality of the input basis vectors in radians. + Returns ------- matrices : ndarray Numpy array of shape (npts, 3, 3) storing a collection of rotation matrices + + Example + ------- + Let's build a rotation matrix that rotates from a frame + rotated by 45 degrees to the standard frame. + + >>> u1 = [np.sqrt(2), np.sqrt(2), 0.0] + >>> u2 = [np.sqrt(2), -1.0*np.sqrt(2), 0.0] + >>> u3 = [0,0,1.0] + >>> rot = rotation_matrices_from_basis(u1, u2, u3) + + Notes + ----- + The rotation matrices transform from the Cartesian frame defined by the standard + basis vectors, + + .. math:: + \u_1=(1,0,0) + \u_2=(0,1,0) + \u_3=(0,0,1) + + The function `rotate_vector_collection` can be used to efficiently + apply the returned collection of matrices to a collection of 3d vectors """ N = np.shape(ux)[0] @@ -150,6 +182,21 @@ def rotation_matrices_from_basis(ux, uy, uz): uy = normalized_vectors(uy) uz = normalized_vectors(uz) + d_theta = angles_between_list_of_vectors(ux, uy) + if np.any((np.pi/2.0 - d_theta) > tol): + msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') + raise ValueError(msg) + + d_theta = angles_between_list_of_vectors(ux, uz) + if np.any((np.pi/2.0 - d_theta) > tol): + msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') + raise ValueError(msg) + + d_theta = angles_between_list_of_vectors(uy, uz) + if np.any((np.pi/2.0 - d_theta) > tol): + msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') + raise ValueError(msg) + r_11 = elementwise_dot(ex, ux) r_12 = elementwise_dot(ex, uy) r_13 = elementwise_dot(ex, uz) @@ -265,7 +312,7 @@ def vectors_normal_to_planes(x, y): def project_onto_plane(x1, x2): r""" Given a collection of 3D vectors, x1 and x2, project each vector - in x1 onto the plane normal to the corresponding vector x2 + in x1 onto the plane normal to the corresponding vector x2. Parameters ---------- @@ -280,10 +327,20 @@ def project_onto_plane(x1, x2): result : ndarray Numpy array of shape (npts, 3) storing a collection of 3d points + Examples + -------- + >>> npts = int(1e4) + >>> x1 = np.random.random((npts, 3)) + >>> x2 = np.random.random((npts, 3)) + >>> x3 = project_onto_plane(x1, x2) + + Notes + ----- + This operations is sometimes called "vector rejection". """ n = normalized_vectors(x2) d = elementwise_dot(x1,n) - return x - d[:,np.newaxis]*n + return x1 - d[:,np.newaxis]*n diff --git a/halotools/utils/tests/test_2d_rotations.py b/halotools/utils/tests/test_2d_rotations.py index b6454ddca..d1d7eb5c4 100644 --- a/halotools/utils/tests/test_2d_rotations.py +++ b/halotools/utils/tests/test_2d_rotations.py @@ -6,9 +6,11 @@ from ..rotate_vector_collection import rotate_vector_collection from ..rotations2d import * -__all__ = ('test_rotation_matrices_from_vectors', +__all__ = ('test_rotation_matrices_from_vectors_1', + 'test_rotation_matrices_from_vectors_2' 'test_rotation_matrices_from_angles', - 'test_rotation_matrices_from_basis' ) + 'test_rotation_matrices_from_basis_1', + 'test_rotation_matrices_from_basis_2' ) fixed_seed = 43 @@ -25,7 +27,7 @@ def test_rotation_matrices_from_vectors_1(): assert np.all(~np.isnan(rot_m)) -def test_rotation_matrices_from_vectors(): +def test_rotation_matrices_from_vectors_2(): """ validate 90 degree rotation result """ @@ -57,7 +59,7 @@ def test_rotation_matrices_from_angles(): assert np.all(~np.isnan(rot_m)) -def test_rotation_matrices_from_basis(): +def test_rotation_matrices_from_basis_1(): """ test to make sure null rotations return identiy matrix """ @@ -73,3 +75,24 @@ def test_rotation_matrices_from_basis(): rot_m = rotation_matrices_from_basis(ux, uy) assert np.all(~np.isnan(rot_m)) + +def test_rotation_matrices_from_basis_2(): + """ + validate 90 degree rotation result + """ + + npts = 1000 + ndim = 2 + + ux = np.zeros((npts,ndim)) + ux[:,1] = 1.0 # carteisian +y-axis + uy = np.zeros((npts,ndim)) + uy[:,0] = -1.0 # carteisian -x-axis + + rot_m = rotation_matrices_from_basis(ux, uy) + + # rotate (0,1) by 45 degrees counter-clockwise. + v = rotate_vector_collection(rot_m, ux) + + assert np.allclose(v,uy) + From 904665edca51fe56ebf2ef89f89add38f77881c5 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 12:53:01 -0400 Subject: [PATCH 43/48] fixed external package import error --- halotools/utils/rotate_vector_collection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/halotools/utils/rotate_vector_collection.py b/halotools/utils/rotate_vector_collection.py index 76783dfb3..6d81f9baa 100644 --- a/halotools/utils/rotate_vector_collection.py +++ b/halotools/utils/rotate_vector_collection.py @@ -1,11 +1,11 @@ """ -A function to rotate collectios of n-dimensional vectors +A function to rotate collections of n-dimensional vectors """ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from rotations.vector_utilities import (elementwise_dot, elementwise_norm, +from .vector_utilities import (elementwise_dot, elementwise_norm, normalized_vectors, angles_between_list_of_vectors) From 1aa28d3e2f8505f16d5485537abcde6c7efdac76 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 14:20:19 -0400 Subject: [PATCH 44/48] removed group-wise rotation option --- halotools/utils/rotate_vector_collection.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/halotools/utils/rotate_vector_collection.py b/halotools/utils/rotate_vector_collection.py index 6d81f9baa..7a687589b 100644 --- a/halotools/utils/rotate_vector_collection.py +++ b/halotools/utils/rotate_vector_collection.py @@ -23,13 +23,11 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): The options are: 1.) array of shape (npts, ndim, ndim) storing a collection of rotation matrices. 2.) array of shape (ndim, ndim) storing a single rotation matrix - 3.) array of shape (nsets, ndim, ndim) storing a collection of rotation matrices. - + vectors : ndarray The corresponding options for above are: 1.) array of shape (npts, ndim) storing a collection of ndim-dimensional vectors 2.) array of shape (npts, ndim) storing a collection of ndim-dimensional vectors - 3.) array of shape (nsets, npts, ndim) storing a collection of ndim-dimensional vectors Returns ------- @@ -38,11 +36,9 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): Notes ----- - This function is set up to preform either: - 1. rotation operations on a single collection of vectors, - either applying a single rotation matrix to all vectors in the collection, + This function is set up to preform either rotation operations on a single collection \ + of vectors, either applying a single rotation matrix to all vectors in the collection, or applying a unique rotation matrix to each vector in the set. - 2. applying a one rotation matrix to each collection of vectors. The behavior of the function is determined by the arguments supplied by the user. @@ -79,17 +75,10 @@ def rotate_vector_collection(rotation_matrices, vectors, optimize=False): if nsets == 1: vectors = vectors[0] return np.dot(rotation_matrices, vectors.T).T - # rotate each vector by associated rotation matrix else: - # n1 sets of n2 vectors of ndim dimension - if len(np.shape(vectors))==3: - ein_string = 'ikl,ijl->ijk' - n1, n2, ndim = np.shape(vectors) - # n1 vectors of ndim dimension - elif len(np.shape(vectors))==2: - ein_string = 'ijk,ik->ij' - n1, ndim = np.shape(vectors) + ein_string = 'ijk,ik->ij' + n1, ndim = np.shape(vectors) try: return np.einsum(ein_string, rotation_matrices, vectors, optimize=optimize) From 6ebd9b1fe6c8801145874eef64ddd368c1c7aa63 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 15:10:48 -0400 Subject: [PATCH 45/48] improved doc strings and tests --- halotools/utils/rotations3d.py | 36 +++++-- halotools/utils/tests/test_3d_rotations.py | 113 ++++++++++++++++++++- 2 files changed, 141 insertions(+), 8 deletions(-) diff --git a/halotools/utils/rotations3d.py b/halotools/utils/rotations3d.py index 0dba00670..0f78c6a02 100644 --- a/halotools/utils/rotations3d.py +++ b/halotools/utils/rotations3d.py @@ -74,8 +74,9 @@ def rotation_matrices_from_angles(angles, directions): def rotation_matrices_from_vectors(v0, v1): r""" - Calculate a collection of rotation matrices defined by the unique - transformation rotating v1 into v2 about the mutually perpendicular axis. + Calculate a collection of rotation matrices defined by two sets of vectors, + v1 into v2, such that the resulting matrices rotate v1 into v2 about + the mutually perpendicular axis. Parameters ---------- @@ -183,17 +184,17 @@ def rotation_matrices_from_basis(ux, uy, uz, tol=np.pi/1000.0): uz = normalized_vectors(uz) d_theta = angles_between_list_of_vectors(ux, uy) - if np.any((np.pi/2.0 - d_theta) > tol): + if np.any(np.fabs(np.pi/2.0 - d_theta) > tol): msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') raise ValueError(msg) d_theta = angles_between_list_of_vectors(ux, uz) - if np.any((np.pi/2.0 - d_theta) > tol): + if np.any(np.fabs(np.pi/2.0 - d_theta) > tol): msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') raise ValueError(msg) d_theta = angles_between_list_of_vectors(uy, uz) - if np.any((np.pi/2.0 - d_theta) > tol): + if np.any(np.fabs(np.pi/2.0 - d_theta) > tol): msg = ('At least one set of basis vectors are not orthoginal to within the specified tolerance.') raise ValueError(msg) @@ -257,15 +258,28 @@ def vectors_between_list_of_vectors(x, y, p): Examples -------- + Create two set of 3D vectors + >>> npts = int(1e4) >>> x = np.random.random((npts, 3)) >>> y = np.random.random((npts, 3)) + + Define the parameter `p` fpr each pair of vectors + >>> p = np.random.uniform(0, 1, npts) + + Find a set of vectors between the two sets + >>> v = vectors_between_list_of_vectors(x, y, p) + + Note that `p` determines how close the new vector is to + the corresponding vectors in the original sets. + >>> angles_xy = angles_between_list_of_vectors(x, y) >>> angles_xp = angles_between_list_of_vectors(x, v) >>> assert np.allclose(angles_xy*p, angles_xp) """ + assert np.all(p >= 0), "All values of p must be non-negative" assert np.all(p <= 1), "No value of p can exceed unity" @@ -300,11 +314,15 @@ def vectors_normal_to_planes(x, y): Examples -------- + Define a set of random 3D vectors + >>> npts = int(1e4) >>> x = np.random.random((npts, 3)) >>> y = np.random.random((npts, 3)) - >>> normed_z = angles_between_list_of_vectors(x, y) + now calculate a thrid set of vectors to a corresponding pair in `x` and `y`. + + >>> normed_z = angles_between_list_of_vectors(x, y) """ return normalized_vectors(np.cross(x, y)) @@ -329,9 +347,15 @@ def project_onto_plane(x1, x2): Examples -------- + Define a set of random 3D vectors. + >>> npts = int(1e4) >>> x1 = np.random.random((npts, 3)) >>> x2 = np.random.random((npts, 3)) + + Find the projection of each vector in `x1` onto a plane defined by + each vector in `x2`. + >>> x3 = project_onto_plane(x1, x2) Notes diff --git a/halotools/utils/tests/test_3d_rotations.py b/halotools/utils/tests/test_3d_rotations.py index 4b27d9393..04ee20b1d 100644 --- a/halotools/utils/tests/test_3d_rotations.py +++ b/halotools/utils/tests/test_3d_rotations.py @@ -3,12 +3,17 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext +from ..vector_utilities import angles_between_list_of_vectors, elementwise_dot from ..rotate_vector_collection import rotate_vector_collection from ..rotations3d import * __all__ = ('test_rotation_matrices_from_vectors', 'test_rotation_matrices_from_angles', - 'test_rotation_matrices_from_basis' ) + 'test_rotation_matrices_from_basis_1', + 'test_rotation_matrices_from_basis_2', + 'test_vectors_between_list_of_vectors', + 'test_vectors_normal_to_planes', + 'test_project_onto_plane' ) fixed_seed = 43 @@ -43,7 +48,7 @@ def test_rotation_matrices_from_angles(): assert np.all(~np.isnan(rot_m)) -def test_rotation_matrices_from_basis(): +def test_rotation_matrices_from_basis_1(): """ test to make sure null rotations return identiy matrix """ @@ -61,3 +66,107 @@ def test_rotation_matrices_from_basis(): rot_m = rotation_matrices_from_basis(ux, uy, uz) assert np.all(~np.isnan(rot_m)) + +def test_rotation_matrices_from_basis_2(): + """ + test tolerance feature + """ + + npts = 1000 + ndim = 3 + + tol = np.pi/1000 + epsilon = np.array([tol+0.01]*npts) + + ux = np.zeros((npts,ndim)) + ux[:,0] = 1.0 + uy = np.zeros((npts,ndim)) + uy[:,1] = 1.0 + uz = np.zeros((npts,ndim)) + uz[:,2] = 1.0 + + rot_m = rotation_matrices_from_basis(ux, uy, uz, tol=np.pi/1000) + assert np.all(~np.isnan(rot_m)) + + # perturbate x + rot_m = rotation_matrices_from_angles(epsilon, uz) + ux_prime = rotate_vector_collection(rot_m, ux) + + dtheta = angles_between_list_of_vectors(ux_prime, uy) + dtheta = np.fabs(np.pi/2.0 - np.max(dtheta)) + + try: + rot_m = rotation_matrices_from_basis(ux_prime, uy, uz, tol=tol) + assert True==False, "tolerance set to {0}, but measured misalignment was at least {1}.".format(tol, dtheta) + except ValueError: + pass + + # perturbate y + rot_m = rotation_matrices_from_angles(epsilon, ux) + uy_prime = rotate_vector_collection(rot_m, uy) + + dtheta = angles_between_list_of_vectors(ux_prime, uy) + dtheta = np.fabs(np.pi/2.0 - np.max(dtheta)) + + try: + rot_m = rotation_matrices_from_basis(ux, uy_prime, uz, tol=tol) + assert True==False, "tolerance set to {0}, but measured misalignment was at least {1}.".format(tol, dtheta) + except ValueError: + pass + + # perturbate z + rot_m = rotation_matrices_from_angles(epsilon, uy) + uz_prime = rotate_vector_collection(rot_m, uz) + + dtheta = angles_between_list_of_vectors(ux_prime, uy) + dtheta = np.fabs(np.pi/2.0 - np.max(dtheta)) + + try: + rot_m = rotation_matrices_from_basis(ux, uy, uz_prime, tol=tol) + assert True==False, "tolerance set to {0}, but measured misalignment was at least {1}.".format(tol, dtheta) + except ValueError: + pass + +def test_vectors_between_list_of_vectors(): + """ + """ + + npts = 1000 + x = np.random.random((npts, 3)) + y = np.random.random((npts, 3)) + p = np.random.uniform(0, 1, npts) + + v = vectors_between_list_of_vectors(x, y, p) + + angles_xy = angles_between_list_of_vectors(x, y) + angles_xp = angles_between_list_of_vectors(x, v) + + assert np.allclose(angles_xy*p, angles_xp) + + +def test_vectors_normal_to_planes(): + """ + """ + + npts = 1000 + x1 = np.random.random((npts, 3)) + x2 = np.random.random((npts, 3)) + + x3 = vectors_normal_to_planes(x1, x2) + + assert np.allclose(elementwise_dot(x3, x1),0.0) + assert np.allclose(elementwise_dot(x3, x2),0.0) + + +def test_project_onto_plane(): + """ + """ + + npts = 1000 + x1 = np.random.random((npts, 3)) + x2 = np.random.random((npts, 3)) + + x3 = project_onto_plane(x1, x2) + + assert np.allclose(elementwise_dot(x3, x2),0.0) + From 010bc96af264e7ff74c44a1f37d92cd22dd7aa5d Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 15:18:44 -0400 Subject: [PATCH 46/48] removed external import error --- halotools/utils/tests/test_rotate_vector_collection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/halotools/utils/tests/test_rotate_vector_collection.py b/halotools/utils/tests/test_rotate_vector_collection.py index eca44aff1..40df3ebcc 100644 --- a/halotools/utils/tests/test_rotate_vector_collection.py +++ b/halotools/utils/tests/test_rotate_vector_collection.py @@ -3,8 +3,8 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from rotations.rotate_vector_collection import rotate_vector_collection -from rotations.rotations3d import * +from ..rotate_vector_collection import rotate_vector_collection +from ..rotations3d import * __all__ = ('test_rotation_3d', ) From 4886d391b9dd309ae3ecdee241fada49cf4893bb Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Thu, 4 Jun 2020 15:28:12 -0400 Subject: [PATCH 47/48] removed test of group-wise calc and replaced it with option 2 test --- .../tests/test_rotate_vector_collection.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/halotools/utils/tests/test_rotate_vector_collection.py b/halotools/utils/tests/test_rotate_vector_collection.py index 40df3ebcc..104714c24 100644 --- a/halotools/utils/tests/test_rotate_vector_collection.py +++ b/halotools/utils/tests/test_rotate_vector_collection.py @@ -13,7 +13,7 @@ def test_rotation_1(): """ - single rotation matrix + single set of points + test option 1: single rotation matrix + set of points """ # create a single rotation matrix @@ -24,7 +24,7 @@ def test_rotation_1(): rot_m = rotation_matrices_from_vectors(v1,v2) rot = rot_m[0] - # create a single set of vectors + # create a set of vectors npts = 1000 ndim = 3 v3 = np.random.random((npts,ndim)) @@ -34,22 +34,20 @@ def test_rotation_1(): assert np.shape(v4)==(npts, ndim) -def test_rotation_3(): +def test_rotation_2(): """ - nset of rotation matrices + nset of npts of points + test option 2: n rotation matrices + n points """ - nsets = 2 + npts = 1000 ndim = 3 - v1 = np.random.random((nsets,ndim)) - v2 = np.random.random((nsets,ndim)) + v1 = np.random.random((npts,ndim)) + v2 = np.random.random((npts,ndim)) rot = rotation_matrices_from_vectors(v1,v2) - npts = 1000 - ndim = 3 - v3 = np.random.random((nsets, npts, ndim)) + v3 = np.random.random((npts, ndim)) v4 = rotate_vector_collection(rot, v3) - assert np.shape(v4)==(nsets, npts, ndim) + assert np.shape(v4)==(npts, ndim) From 0e8b5e93db6395fa9fe46e88060c78b7028b2281 Mon Sep 17 00:00:00 2001 From: Duncan Campbell Date: Fri, 5 Jun 2020 11:07:51 -0400 Subject: [PATCH 48/48] moved some rotations3d functions into vector_utilities and updated tests. --- halotools/utils/rotations3d.py | 89 ++----------------- halotools/utils/tests/test_3d_rotations.py | 32 +------ .../utils/tests/test_vector_utilities.py | 29 +++++- halotools/utils/vector_utilities.py | 82 ++++++++++++++++- 4 files changed, 114 insertions(+), 118 deletions(-) diff --git a/halotools/utils/rotations3d.py b/halotools/utils/rotations3d.py index 0f78c6a02..8db384ca0 100644 --- a/halotools/utils/rotations3d.py +++ b/halotools/utils/rotations3d.py @@ -5,13 +5,14 @@ from __future__ import (division, print_function, absolute_import, unicode_literals) import numpy as np -from .vector_utilities import (elementwise_dot, elementwise_norm, - normalized_vectors, angles_between_list_of_vectors) +from .vector_utilities import * from halotools.utils import rotate_vector_collection -__all__=['rotation_matrices_from_angles', 'rotation_matrices_from_vectors', 'rotation_matrices_from_basis', - 'vectors_between_list_of_vectors', 'vectors_normal_to_planes', 'project_onto_plane'] +__all__=['rotation_matrices_from_angles', + 'rotation_matrices_from_vectors', + 'rotation_matrices_from_basis', + 'vectors_between_list_of_vectors'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -288,83 +289,3 @@ def vectors_between_list_of_vectors(x, y, p): angles = p*theta rotation_matrices = rotation_matrices_from_angles(angles, z) return normalized_vectors(rotate_vector_collection(rotation_matrices, x)) - - -def vectors_normal_to_planes(x, y): - r""" Given a collection of 3d vectors x and y, - return a collection of 3d unit-vectors that are orthogonal to x and y. - - Parameters - ---------- - x : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `x` will be ignored. - - y : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d vectors - - Note that the normalization of `y` will be ignored. - - Returns - ------- - z : ndarray - Numpy array of shape (npts, 3). Each 3d vector in z will be orthogonal - to the corresponding vector in x and y. - - Examples - -------- - Define a set of random 3D vectors - - >>> npts = int(1e4) - >>> x = np.random.random((npts, 3)) - >>> y = np.random.random((npts, 3)) - - now calculate a thrid set of vectors to a corresponding pair in `x` and `y`. - - >>> normed_z = angles_between_list_of_vectors(x, y) - """ - return normalized_vectors(np.cross(x, y)) - - -def project_onto_plane(x1, x2): - r""" - Given a collection of 3D vectors, x1 and x2, project each vector - in x1 onto the plane normal to the corresponding vector x2. - - Parameters - ---------- - x1 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - x2 : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - Returns - ------- - result : ndarray - Numpy array of shape (npts, 3) storing a collection of 3d points - - Examples - -------- - Define a set of random 3D vectors. - - >>> npts = int(1e4) - >>> x1 = np.random.random((npts, 3)) - >>> x2 = np.random.random((npts, 3)) - - Find the projection of each vector in `x1` onto a plane defined by - each vector in `x2`. - - >>> x3 = project_onto_plane(x1, x2) - - Notes - ----- - This operations is sometimes called "vector rejection". - """ - - n = normalized_vectors(x2) - d = elementwise_dot(x1,n) - - return x1 - d[:,np.newaxis]*n - diff --git a/halotools/utils/tests/test_3d_rotations.py b/halotools/utils/tests/test_3d_rotations.py index 04ee20b1d..0966b3824 100644 --- a/halotools/utils/tests/test_3d_rotations.py +++ b/halotools/utils/tests/test_3d_rotations.py @@ -3,7 +3,7 @@ import numpy as np from astropy.utils.misc import NumpyRNGContext -from ..vector_utilities import angles_between_list_of_vectors, elementwise_dot +from ..vector_utilities import * from ..rotate_vector_collection import rotate_vector_collection from ..rotations3d import * @@ -11,9 +11,7 @@ 'test_rotation_matrices_from_angles', 'test_rotation_matrices_from_basis_1', 'test_rotation_matrices_from_basis_2', - 'test_vectors_between_list_of_vectors', - 'test_vectors_normal_to_planes', - 'test_project_onto_plane' ) + 'test_vectors_between_list_of_vectors') fixed_seed = 43 @@ -144,29 +142,3 @@ def test_vectors_between_list_of_vectors(): assert np.allclose(angles_xy*p, angles_xp) -def test_vectors_normal_to_planes(): - """ - """ - - npts = 1000 - x1 = np.random.random((npts, 3)) - x2 = np.random.random((npts, 3)) - - x3 = vectors_normal_to_planes(x1, x2) - - assert np.allclose(elementwise_dot(x3, x1),0.0) - assert np.allclose(elementwise_dot(x3, x2),0.0) - - -def test_project_onto_plane(): - """ - """ - - npts = 1000 - x1 = np.random.random((npts, 3)) - x2 = np.random.random((npts, 3)) - - x3 = project_onto_plane(x1, x2) - - assert np.allclose(elementwise_dot(x3, x2),0.0) - diff --git a/halotools/utils/tests/test_vector_utilities.py b/halotools/utils/tests/test_vector_utilities.py index da10068bc..826ad621e 100644 --- a/halotools/utils/tests/test_vector_utilities.py +++ b/halotools/utils/tests/test_vector_utilities.py @@ -8,10 +8,10 @@ from ..rotate_vector_collection import rotate_vector_collection from ..rotations2d import rotation_matrices_from_angles as rotation_matrices_from_angles_2d from ..rotations3d import rotation_matrices_from_angles as rotation_matrices_from_angles_3d -from ..rotations3d import vectors_normal_to_planes __all__ = ('test_normalized_vectors', 'test_elementwise_norm', 'test_elementwise_dot', - 'test_angles_between_list_of_vectors') + 'test_angles_between_list_of_vectors', + 'test_vectors_normal_to_planes', 'test_project_onto_plane' ) fixed_seed = 43 @@ -138,5 +138,30 @@ def test_angles_between_list_of_vectors(): assert np.allclose(angles_between_list_of_vectors(v1,v4),np.fabs(angles)) +def test_vectors_normal_to_planes(): + """ + """ + + npts = 1000 + x1 = np.random.random((npts, 3)) + x2 = np.random.random((npts, 3)) + + x3 = vectors_normal_to_planes(x1, x2) + + assert np.allclose(elementwise_dot(x3, x1),0.0) + assert np.allclose(elementwise_dot(x3, x2),0.0) + + +def test_project_onto_plane(): + """ + """ + + npts = 1000 + x1 = np.random.random((npts, 3)) + x2 = np.random.random((npts, 3)) + + x3 = project_onto_plane(x1, x2) + + assert np.allclose(elementwise_dot(x3, x2),0.0) diff --git a/halotools/utils/vector_utilities.py b/halotools/utils/vector_utilities.py index 62164c53b..98729eea7 100644 --- a/halotools/utils/vector_utilities.py +++ b/halotools/utils/vector_utilities.py @@ -6,8 +6,12 @@ unicode_literals) import numpy as np -__all__=['elementwise_dot', 'elementwise_norm', 'normalized_vectors', - 'angles_between_list_of_vectors'] +__all__=['elementwise_dot', + 'elementwise_norm', + 'normalized_vectors', + 'angles_between_list_of_vectors', + 'vectors_normal_to_planes', + 'project_onto_plane'] __author__ = ['Duncan Campbell', 'Andrew Hearin'] @@ -166,3 +170,77 @@ def angles_between_list_of_vectors(v0, v1, tol=1e-3, vn=None): return a +def vectors_normal_to_planes(x, y): + r""" + Given a collection of 3d vectors x and y, return a collection of + 3d unit-vectors that are orthogonal to x and y. + + Parameters + ---------- + x : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `x` will be ignored. + + y : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d vectors + + Note that the normalization of `y` will be ignored. + + Returns + ------- + z : ndarray + Numpy array of shape (npts, 3). Each 3d vector in z will be orthogonal + to the corresponding vector in x and y. + + Examples + -------- + Define a set of random 3D vectors + + >>> npts = int(1e4) + >>> x = np.random.random((npts, 3)) + >>> y = np.random.random((npts, 3)) + + now calculate a thrid set of vectors to a corresponding pair in `x` and `y`. + + >>> normed_z = angles_between_list_of_vectors(x, y) + """ + return normalized_vectors(np.cross(x, y)) + + +def project_onto_plane(x1, x2): + r""" + Given a collection of vectors, x1 and x2, project each vector + in x1 onto the plane normal to the corresponding vector x2. + + Parameters + ---------- + x1 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + x2 : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Returns + ------- + result : ndarray + Numpy array of shape (npts, 3) storing a collection of 3d points + + Examples + -------- + Define a set of random 3D vectors. + + >>> npts = int(1e4) + >>> x1 = np.random.random((npts, 3)) + >>> x2 = np.random.random((npts, 3)) + + Find the projection of each vector in `x1` onto a plane defined by + each vector in `x2`. + + >>> x3 = project_onto_plane(x1, x2) + """ + + n = normalized_vectors(x2) + d = elementwise_dot(x1,n) + + return x1 - d[:,np.newaxis]*n