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 mc_unit_sphere(self, Npts, **kwargs): r""" Returns Npts anisotropically distributed points on the unit sphere. Parameters ---------- Npts : int Number of 3d points to generate seed : int, optional Random number seed used in the Monte Carlo realization. Default is None, which will produce stochastic results. Returns ------- x, y, z : array_like Length-Npts arrays of the coordinate positions. """ seed = kwargs.get('seed', None) if 'table' in kwargs: table = kwargs['table'] try: b_to_a = table['halo_b_to_a'] except KeyError: b_to_a = 1.0 try: c_to_a = table['halo_c_to_a'] except KeyError: c_to_a = 1.0 try: halo_axisA_x = table['halo_axisA_x'] halo_axisA_y = table['halo_axisA_y'] halo_axisA_z = table['halo_axisA_z'] except KeyError: with NumpyRNGContext(seed): v = random_unit_vectors_3d(len(table)) halo_axisA_x = v[:,0] halo_axisA_y = v[:,1] halo_axisA_z = v[:,2] try: halo_axisC_x = table['halo_axisC_x'] halo_axisC_y = table['halo_axisC_y'] halo_axisC_z = table['halo_axisC_z'] except KeyError: with NumpyRNGContext(seed): v = random_unit_vectors_3d(len(table)) halo_axisC_x = v[:,0] halo_axisC_y = v[:,1] halo_axisC_z = v[:,2] else: try: b_to_a = np.atleast_1d(kwargs['b_to_a']) except KeyError: b_to_a = 1.0 try: c_to_a = np.atleast_1d(kwargs['c_to_a']) except KeyError: c_to_a = 1.0 try: halo_axisA_x = np.atleast_1d(kwargs['halo_axisA_x']) halo_axisA_y = np.atleast_1d(kwargs['halo_axisA_y']) halo_axisA_z = np.atleast_1d(kwargs['halo_axisA_z']) except KeyError: with NumpyRNGContext(seed): v = random_unit_vectors_3d(1) halo_axisC_x = v[:,0] halo_axisC_y = v[:,1] halo_axisC_z = v[:,2] try: halo_axisC_x = np.atleast_1d(kwargs['halo_axisC_x']) halo_axisC_y = np.atleast_1d(kwargs['halo_axisC_y']) halo_axisC_z = np.atleast_1d(kwargs['halo_axisC_z']) except KeyError: with NumpyRNGContext(seed): v = random_unit_vectors_3d(len(halo_axisA_x)) halo_axisC_x = v[:,0] halo_axisC_y = v[:,1] halo_axisC_z = v[:,2] v1 = np.vstack((halo_axisA_x, halo_axisA_y, halo_axisA_z)).T v3 = np.vstack((halo_axisC_x, halo_axisC_y, halo_axisC_z)).T v2 = np.cross(v1,v3) with NumpyRNGContext(seed): phi = np.random.uniform(0, 2*np.pi, Npts) uran = np.random.rand(Npts)*2 - 1 cos_t = uran sin_t = np.sqrt((1.-cos_t*cos_t)) b_to_a, c_to_a = self.anisotropy_bias_response(b_to_a, c_to_a) c_to_b = c_to_a/b_to_a # temporarily use x-axis as the major axis x = 1.0/c_to_a*sin_t * np.cos(phi) y = 1.0/c_to_b*sin_t * np.sin(phi) z = cos_t x_correlated_axes = np.vstack((x, y, z)).T x_axes = np.tile((1, 0, 0), Npts).reshape((Npts, 3)) major_axes = v1 matrices = rotation_matrices_from_basis(v1,v2,v3) # rotate x-axis into the major axis #angles = angles_between_list_of_vectors(x_axes, major_axes) #rotation_axes = vectors_normal_to_planes(x_axes, major_axes) #matrices = rotation_matrices_from_angles(angles, rotation_axes) correlated_axes = rotate_vector_collection(matrices, x_correlated_axes) #correlated_axes = x_correlated_axes return correlated_axes[:, 0], correlated_axes[:, 1], correlated_axes[:, 2]
def assign_positions(self, **kwargs): """ assign satellite positions based on subhalo radial positions and random angular positions. """ if 'table' in kwargs.keys(): table = kwargs['table'] halo_x = table['halo_x'] halo_y = table['halo_y'] halo_z = table['halo_z'] halo_hostid = table['halo_hostid'] halo_id = table['halo_id'] b_to_a = table['halo_b_to_a'] c_to_a = table['halo_c_to_a'] halo_axisA_x = table['halo_axisA_x'] halo_axisA_y = table['halo_axisA_y'] halo_axisA_z = table['halo_axisA_z'] halo_axisC_x = table['halo_axisC_x'] halo_axisC_y = table['halo_axisC_y'] halo_axisC_z = table['halo_axisC_z'] concentration = table['halo_nfw_conc'] rvir = table['halo_rvir'] try: Lbox = kwargs['Lbox'] except KeyError: Lbox = self._Lbox else: halo_x = kwargs['halo_x'] halo_y = kwargs['halo_y'] halo_z = kwargs['halo_z'] halo_hostid = kwargs['halo_hostid'] halo_id = kwargs['halo_id'] b_to_a = kwargs['halo_b_to_a'] c_to_a = kwargs['halo_c_to_a'] halo_axisA_x = kwargs['halo_axisA_x'] halo_axisA_y = kwargs['halo_axisA_y'] halo_axisA_z = kwargs['halo_axisA_z'] halo_axisC_x = kwargs['halo_axisC_x'] halo_axisC_y = kwargs['halo_axisC_y'] halo_axisC_z = kwargs['halo_axisC_z'] concentration = kwargs['halo_nfw_conc'] rvir = tabel['halo_rvir'] try: Lbox = kwargs['Lbox'] except KeyError: Lbox = self._Lbox Npts = len(halo_x) # get host halo properties inds1, inds2 = crossmatch(halo_hostid, halo_id) # some sub-haloes point to a host that does not exist no_host = ~np.in1d(halo_hostid, halo_id) if np.sum(no_host) > 0: msg = ("There are {0} sub-haloes with no host halo.".format( np.sum(no_host))) warn(msg) host_halo_concentration = np.zeros(Npts) host_halo_concentration[inds1] = concentration[inds2] host_halo_rvir = np.zeros(Npts) host_halo_rvir[inds1] = rvir[inds2] host_b_to_a = np.zeros(Npts) host_b_to_a[inds1] = b_to_a[inds2] host_c_to_a = np.zeros(Npts) host_c_to_a[inds1] = c_to_a[inds2] major_axis = np.vstack((halo_axisA_x, halo_axisA_y, halo_axisA_z)).T minor_axis = np.vstack((halo_axisC_x, halo_axisC_y, halo_axisC_z)).T inter_axis = np.cross(major_axis, minor_axis) host_major_axis = np.zeros((Npts, 3)) host_inter_axis = np.zeros((Npts, 3)) host_minor_axis = np.zeros((Npts, 3)) host_major_axis[inds1] = major_axis[inds2] host_inter_axis[inds1] = inter_axis[inds2] host_minor_axis[inds1] = minor_axis[inds2] # host x,y,z-position halo_x[inds1] = halo_x[inds2] halo_y[inds1] = halo_y[inds2] halo_z[inds1] = halo_z[inds2] # host halo centric positions phi = np.random.uniform(0, 2 * np.pi, Npts) uran = np.random.rand(Npts) * 2 - 1 cos_t = uran sin_t = np.sqrt((1. - cos_t * cos_t)) b_to_a, c_to_a = self.anisotropy_bias_response(host_b_to_a, host_c_to_a) c_to_b = c_to_a / b_to_a # temporarily use x-axis as the major axis x = 1.0 / c_to_a * sin_t * np.cos(phi) y = 1.0 / c_to_b * sin_t * np.sin(phi) z = cos_t x_correlated_axes = np.vstack((x, y, z)).T x_axes = np.tile((1, 0, 0), Npts).reshape((Npts, 3)) matrices = rotation_matrices_from_basis(host_major_axis, host_inter_axis, host_minor_axis) # rotate x-axis into the major axis #angles = angles_between_list_of_vectors(x_axes, major_axes) #rotation_axes = vectors_normal_to_planes(x_axes, major_axes) #matrices = rotation_matrices_from_angles(angles, rotation_axes) correlated_axes = rotate_vector_collection(matrices, x_correlated_axes) x, y, z = correlated_axes[:, 0], correlated_axes[:, 1], correlated_axes[:, 2] nfw = NFWPhaseSpace(conc_mass_model='direct_from_halo_catalog', ) dimensionless_radial_distance = nfw._mc_dimensionless_radial_distance( host_halo_concentration) x *= dimensionless_radial_distance y *= dimensionless_radial_distance z *= dimensionless_radial_distance x *= host_halo_rvir y *= host_halo_rvir z *= host_halo_rvir a = 1 b = b_to_a * a c = c_to_a * a T = (c**2 - b**2) / (c**2 - a**2) q = b / a s = c / a x *= np.sqrt(q * s) y *= np.sqrt(q * s) z *= np.sqrt(q * s) # host-halo centric radial distance r = np.sqrt(x * x + y * y + z * z) # move back into original cordinate system xx = halo_x + x yy = halo_y + y zz = halo_z + z xx[no_host] = halo_x[no_host] yy[no_host] = halo_y[no_host] zz[no_host] = halo_z[no_host] # account for PBCs xx, yy, zz = wrap_coordinates(xx, yy, zz, Lbox) if 'table' in kwargs.keys(): # assign satellite galaxy positions try: mask = (table['gal_type'] == 'satellites') except KeyError: mask = np.array([True] * len(table)) msg = ( "`gal_type` not indicated in `table`.", "The orientation is being assigned for all galaxies in the `table`." ) print(msg) table['x'] = halo_x * 1.0 table['y'] = halo_y * 1.0 table['z'] = halo_z * 1.0 table['x'][mask] = xx[mask] table['y'][mask] = yy[mask] table['z'][mask] = zz[mask] table['r'] = 0.0 table['r'][mask] = r[mask] table['halo_x'][mask] = halo_x[mask] table['halo_y'][mask] = halo_y[mask] table['halo_z'][mask] = halo_z[mask] return table else: x = xx y = yy z = zz return np.vstack((x, y, z)).T
def assign_positions(self, **kwargs): """ assign satellite positions based on subhalo radial positions and random angular positions. """ if 'table' in kwargs.keys(): table = kwargs['table'] halo_x = table['halo_x'] halo_y = table['halo_y'] halo_z = table['halo_z'] halo_axisA_x = table['halo_axisA_x'] halo_axisA_y = table['halo_axisA_y'] halo_axisA_z = table['halo_axisA_z'] halo_hostid = table['halo_hostid'] halo_id = table['halo_id'] try: Lbox = kwargs['Lbox'] except KeyError: Lbox = self._Lbox else: halo_x = kwargs['halo_x'] halo_y = kwargs['halo_y'] halo_z = kwargs['halo_z'] halo_hostid = kwargs['halo_hostid'] halo_id = kwargs['halo_id'] try: Lbox = kwargs['Lbox'] except KeyError: Lbox = self._Lbox # get subhalo positions x = halo_x * 1.0 y = halo_y * 1.0 z = halo_z * 1.0 # get host halo positions inds1, inds2 = crossmatch(halo_hostid, halo_id) # x-position halo_x[inds1] = halo_x[inds2] # y-position halo_y[inds1] = halo_y[inds2] # z-position halo_z[inds1] = halo_z[inds2] # get host halo orientation host_halo_axisA_x = halo_axisA_x host_halo_axisA_x[inds1] = halo_axisA_x[inds2] host_halo_axisA_y = halo_axisA_y host_halo_axisA_y[inds1] = halo_axisA_y[inds2] host_halo_axisA_z = halo_axisA_z host_halo_axisA_z[inds1] = halo_axisA_z[inds2] host_halo_mjor_axes = np.vstack( (halo_axisA_x, halo_axisA_y, halo_axisA_z)).T # calculate radial positions vec_r, r = radial_distance(x, y, z, halo_x, halo_y, halo_z, Lbox) # rotate radial vectors arond halo major axis N = len(x) rot_angles = np.random.uniform(0.0, 2 * np.pi, N) rot_axes = host_halo_mjor_axes rot_m = rotation_matrices_from_angles(rot_angles, rot_axes) new_vec_r = rotate_vector_collection(rot_m, vec_r) xx = new_vec_r[:, 0] yy = new_vec_r[:, 1] zz = new_vec_r[:, 2] # move back into original cordinate system xx = halo_x + xx yy = halo_y + yy zz = halo_z + zz # account for PBCs mask = (xx < 0.0) xx[mask] = xx[mask] + Lbox[0] mask = (xx > Lbox[0]) xx[mask] = xx[mask] - Lbox[0] mask = (yy < 0.0) yy[mask] = yy[mask] + Lbox[1] mask = (yy > Lbox[1]) yy[mask] = yy[mask] - Lbox[1] mask = (zz < 0.0) zz[mask] = zz[mask] + Lbox[2] mask = (zz > Lbox[2]) zz[mask] = zz[mask] - Lbox[2] if 'table' in kwargs.keys(): # assign satellite galaxy positions try: mask = (table['gal_type'] == 'satellites') except KeyError: mask = np.array([True] * len(table)) msg = ( "`gal_type` not indicated in `table`.", "The orientation is being assigned for all galaxies in the `table`." ) print(msg) table['x'] = halo_x * 1.0 table['y'] = halo_y * 1.0 table['z'] = halo_z * 1.0 table['x'][mask] = xx[mask] table['y'][mask] = yy[mask] table['z'][mask] = zz[mask] table['halo_x'][mask] = halo_x[mask] table['halo_y'][mask] = halo_y[mask] table['halo_z'][mask] = halo_z[mask] return table else: x = xx y = yy z = zz return np.vstack((x, y, z)).T
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 -------- 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" 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))