def particle_cos_alpha(coords, vels, L): """ cosine of the angle between the orbital angular momentum of the particle and that of the galaxy system Parameters ---------- coords : normalized particle positions vels : array_like normalized particle velocities L : array_like array of 3D angular momentum vector Returns ------- cos_alpha : array_like array of cosine(alpha) values """ # specific angular momomentum of particles j = normalized_vectors(np.cross(coords, vels)) l = normalized_vectors(L)[0] return np.dot(j, l)
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 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
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) # 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)
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 rotation_matrices_from_vectors(v0, v1): r""" Calculate a collection of rotation matrices defined by the unique transformation rotating v1 into v2. 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 2d vectors """ v0 = normalized_vectors(v0) v1 = normalized_vectors(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)
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 vectors uy : array_like Numpy array of shape (npts, 2) storing a collection of unit vectors 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, 2) ey = np.array([0.0, 1.0]*N).reshape(N, 2) 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
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 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 particle_cos_beta(coords, L): """ cosine of the angle between the radial vector and the spin axis of the galaxy Parameters ---------- coords : normalized particle positions L : array_like array of 3D angular momentum vector Returns ------- cos_alpha : array_like array of cosine(alpha) values """ # specific angular momomentum of particles r = normalized_vectors(coords) l = normalized_vectors(L)[0] return np.dot(r, l)
def assign_orientation(self, **kwargs): r""" """ if 'table' in kwargs.keys(): table = kwargs['table'] N = len(table) else: N = kwargs['size'] # assign random orientations major_v = random_unit_vectors_3d(N) inter_v = random_perpendicular_directions(major_v) minor_v = normalized_vectors(np.cross(major_v, inter_v)) if 'table' in kwargs.keys(): try: mask = (table['gal_type'] == self.gal_type) except KeyError: mask = np.array([True] * N) msg = ( "Because `gal_type` not indicated in `table`.", "The orientation is being assigned for all galaxies in the `table`." ) print(msg) # check to see if the columns exist for key in list(self._galprop_dtypes_to_allocate.names): if key not in table.keys(): table[key] = 0.0 table['galaxy_axisA_x'][mask] = major_v[mask, 0] table['galaxy_axisA_y'][mask] = major_v[mask, 1] table['galaxy_axisA_z'][mask] = major_v[mask, 2] table['galaxy_axisB_x'][mask] = inter_v[mask, 0] table['galaxy_axisB_y'][mask] = inter_v[mask, 1] table['galaxy_axisB_z'][mask] = inter_v[mask, 2] table['galaxy_axisC_x'][mask] = minor_v[mask, 0] table['galaxy_axisC_y'][mask] = minor_v[mask, 1] table['galaxy_axisC_z'][mask] = minor_v[mask, 2] return table else: return major_v, inter_v, minor_v
def axes_correlated_with_input_vector(input_vectors, p=0., seed=None): r""" Calculate a list of 3d unit-vectors whose orientation is correlated with the orientation of `input_vectors`. Parameters ---------- input_vectors : ndarray Numpy array of shape (npts, 3) storing a list of 3d vectors defining the preferred orientation with which the returned vectors will be correlated. Note that the normalization of `input_vectors` will be ignored. p : ndarray, optional Numpy array with shape (npts, ) defining the strength of the correlation between the orientation of the returned vectors and the z-axis. Default is zero, for no correlation. Positive (negative) values of `p` produce galaxy principal axes that are statistically aligned with the positive (negative) z-axis; the strength of this alignment increases with the magnitude of p. When p = 0, galaxy axes are randomly oriented. seed : int, optional Random number seed used to choose a random orthogonal direction Returns ------- unit_vectors : ndarray Numpy array of shape (npts, 3) """ input_unit_vectors = normalized_vectors(input_vectors) assert input_unit_vectors.shape[1] == 3 npts = input_unit_vectors.shape[0] z_correlated_axes = axes_correlated_with_z(p, seed) z_axes = np.tile((0, 0, 1), npts).reshape((npts, 3)) angles = angles_between_list_of_vectors(z_axes, input_unit_vectors) rotation_axes = vectors_normal_to_planes(z_axes, input_unit_vectors) matrices = rotation_matrices_from_angles(angles, rotation_axes) return rotate_vector_collection(matrices, z_correlated_axes)
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
def assign_orientation(self, **kwargs): r""" assign a a set of three orthoganl unit vectors indicating the orientation of the galaxies' major, intermediate, and minor axis """ if 'table' in kwargs.keys(): table = kwargs['table'] halo_x = table['halo_x'] halo_y = table['halo_y'] halo_z = table['halo_z'] Ax = table[self.list_of_haloprops_needed[3]] Ay = table[self.list_of_haloprops_needed[4]] Az = table[self.list_of_haloprops_needed[5]] halo_r = table['halo_rvir'] Lbox = self._Lbox else: halo_x = kwargs['halo_x'] halo_y = kwargs['halo_y'] halo_z = kwargs['halo_z'] Ax = kwargs['halo_axisA_x'] Ay = kwargs['halo_axisA_y'] Az = kwargs['halo_axisA_z'] halo_r = kwargs['halo_rvir'] Lbox = kwargs['Lbox'] Ngal = len(Ax) # define halo-center - satellite vector dx = (x - halo_x) mask = dx > Lbox[0] / 2.0 dx[mask] = dx[mask] - Lbox[0] mask = dx < -1.0 * Lbox[0] / 2.0 dx[mask] = dx[mask] + Lbox[0] dy = (y - halo_y) mask = dy > Lbox[1] / 2.0 dy[mask] = dy[mask] - Lbox[1] mask = dy < -1.0 * Lbox[1] / 2.0 dy[mask] = dy[mask] + Lbox[1] dz = (z - halo_z) mask = dz > Lbox[2] / 2.0 dz[mask] = dz[mask] - Lbox[2] mask = dz < -1.0 * Lbox[2] / 2.0 dz[mask] = dz[mask] + Lbox[2] # radial vector v1 = normalized_vectors(np.vstack((dx, dy, dz)).T) # major axis orientation v2 = normalized_vectors(np.vstack((Ax, Ay, Az)).T) # account for handedness by randomly flipping alignment components seed = kwargs.get('seed', None) with NumpyRNGContext(seed): uran1 = np.random.random(Ngal) if seed is not None: seed = seed + 1 with NumpyRNGContext(seed): uran2 = np.random.random(Ngal) flip1 = np.ones(Ngal) flip1[uran1 < 0.5] = -1.0 flip2 = np.ones(Ngal) flip2[uran2 < 0.5] = -1.0 v1 = flip1[:, np.newaxis] * v1 v2 = flip2[:, np.newaxis] * v2 # calculate scaled halo virial radius r = np.sqrt(dx**2 + dy**2 + dz**2) / halo_r # get alignment strength for each galaxy if 'table' in kwargs.keys(): try: p = table['satellite_alignment_strength'] except KeyError: msg = ( '`satellite_alignment_strength` not detected in the table, using value in self.param_dict.' ) warn(msg) p = np.ones(len( table)) * self.param_dict['satellite_alignment_strength'] else: N = len(self.param_dict['x']) p = np.ones(N * self.param_dict['satellite_alignment_strength']) # get major to radial parameter a = self.radial_hybrid_alignment_vector_parameter(r) # define alignment vector inbetween v1 and v2 v3 = normalized_vectors(vectors_between_list_of_vectors(v1, v2, a)) # get galaxy major axis major_v = axes_correlated_with_input_vector(v3, p=p) # randomly set minor axis orientation minor_v = random_perpendicular_directions(major_v) # the intermediate axis is determined inter_v = vectors_normal_to_planes(major_v, minor_v) mask = (table['gal_type'] == self.gal_type) # add orientations to the galaxy table table['galaxy_axisA_x'][mask] = major_v[mask, 0] table['galaxy_axisA_y'][mask] = major_v[mask, 1] table['galaxy_axisA_z'][mask] = major_v[mask, 2] table['galaxy_axisB_x'][mask] = inter_v[mask, 0] table['galaxy_axisB_y'][mask] = inter_v[mask, 1] table['galaxy_axisB_z'][mask] = inter_v[mask, 2] table['galaxy_axisC_x'][mask] = minor_v[mask, 0] table['galaxy_axisC_y'][mask] = minor_v[mask, 1] table['galaxy_axisC_z'][mask] = minor_v[mask, 2] return table