def test_lebedev_laikov_sphere(self): """Test the integration of sphere and its points and weights.""" previous_npoint = 0 for i in range(1, 132): npoint = AngularGrid._get_lebedev_size_and_degree(degree=i)[1] if npoint > previous_npoint: grid = AngularGrid(size=npoint) assert isinstance(grid, AngularGrid) assert_allclose(grid.weights.sum(), 1.0 * 4 * np.pi) # check surface area (i.e., integral of constant function 1) assert_allclose(grid.integrate(np.ones(grid.size)), 4 * np.pi) # check integral of x * y * z is zero (i.e., f orbital is orthogonal to s orbital) assert_allclose(grid.integrate(np.product(grid.points, axis=1)), 0.0, atol=1.0e-12) assert_allclose(grid.points[:, 0].sum(), 0, atol=1e-10) assert_allclose(grid.points[:, 1].sum(), 0, atol=1e-10) assert_allclose(grid.points[:, 2].sum(), 0, atol=1e-10) assert_allclose(grid.points[:, 0] @ grid.weights, 0, atol=1e-10) assert_allclose(grid.points[:, 1] @ grid.weights, 0, atol=1e-10) assert_allclose(grid.points[:, 2] @ grid.weights, 0, atol=1e-10) previous_npoint = npoint
def _generate_atomic_grid(rgrid, degrees, rotate=False): """Generate atomic grid for each radial point with angular degree L. Parameters ---------- rgrid : OneDGrid The (1-dimensional) radial grid representing the radius of spherical grids. degrees : np.ndarray(N,) Sequence of Lebedev grid degrees used for constructing spherical grids at each radial grid point. If the given degree is not supported, the next largest degree is used. rotate : bool or int , optional Whether to rotate the Lebedev spherical grids at each radial grid point. If given an integer, it is used as a seed for generating random rotation matrices. Returns ------- tuple(np.ndarray(M,), np.ndarray(M,), np.ndarray(N,)), grid points, grid weights, and indices for each shell. """ if len(degrees) != rgrid.size: raise ValueError( "The shape of radial grid does not match given degs.") all_points, all_weights = [], [] shell_pt_indices = np.zeros(len(degrees) + 1, dtype=int) # set index to int for i, deg_i in enumerate(degrees): # TODO: proper tests sphere_grid = AngularGrid(degree=deg_i) if rotate is False: pass # if rotate is True, rotate each shell elif rotate is True: rot_mt = R.random().as_matrix() new_points = sphere_grid.points @ rot_mt sphere_grid = AngularGrid(new_points, sphere_grid.weights) # if rotate is a seed else: assert isinstance(rotate, int) # check seed proper value rot_mt = R.random(random_state=rotate + i).as_matrix() new_points = sphere_grid.points @ rot_mt sphere_grid = AngularGrid(new_points, sphere_grid.weights) # construct atomic grid with each radial point and each spherical shell # compute points points = sphere_grid.points * rgrid[i].points # compute weights weights = sphere_grid.weights * rgrid[i].weights * rgrid[ i].points**2 # locate separators shell_pt_indices[i + 1] = shell_pt_indices[i] + len(points) all_points.append(points) all_weights.append(weights) indices = shell_pt_indices points = np.vstack(all_points) weights = np.hstack(all_weights) return points, weights, indices
def test_integration_of_spherical_harmonic_not_accurate_beyond_degree(self): r"""Test integration of spherical harmonic of degree higher than grid is not accurate.""" grid = AngularGrid(degree=3) r = np.linalg.norm(grid.points, axis=1) phi = np.arccos(grid.points[:, 2] / r) theta = np.arctan2(grid.points[:, 1], grid.points[:, 0]) sph_harm = generate_real_spherical_harmonics(l_max=6, theta=theta, phi=phi) # Check that l=4,m=0 gives inaccurate results assert np.abs(grid.integrate(sph_harm[(4) ** 2, :])) > 1e-8 # Check that l=6,m=0 gives inaccurate results assert np.abs(grid.integrate(sph_harm[(6) ** 2, :])) > 1e-8
def test_consistency(self): """Consistency tests from old grid.""" for i in LEBEDEV_NPOINTS: assert_equal( AngularGrid._get_lebedev_size_and_degree(degree=LEBEDEV_NPOINTS[i]), (LEBEDEV_NPOINTS[i], i), ) for j in LEBEDEV_DEGREES: assert_equal( AngularGrid._get_lebedev_size_and_degree(size=LEBEDEV_DEGREES[j]), (j, LEBEDEV_DEGREES[j]), )
def test_lebedev_cache(self): """Test cache behavior of spherical grid.""" degrees = np.random.randint(1, 100, 50) LEBEDEV_CACHE.clear() for i in degrees: AngularGrid(degree=i, cache=False) assert len(LEBEDEV_CACHE) == 0 for i in degrees: AngularGrid(degree=i) ref_d = AngularGrid._get_lebedev_size_and_degree(degree=i)[0] assert ref_d in LEBEDEV_CACHE
def test_convert_lebedev_sizes_to_degrees(self): """Test size to degree conversion.""" # first test nums = [38, 50, 74, 86, 110, 38, 50, 74] degs = AngularGrid.convert_lebedev_sizes_to_degrees(nums) ref_degs = [9, 11, 13, 15, 17, 9, 11, 13] assert_array_equal(degs, ref_degs) # second test nums = [6] degs = AngularGrid.convert_lebedev_sizes_to_degrees(nums) ref_degs = [3] assert_array_equal(degs, ref_degs)
def test_generate_spherical(self): """Test generated real spherical harmonics values.""" atgrid = AngularGrid(degree=7) pts = atgrid.points wts = atgrid.weights r = np.linalg.norm(pts, axis=1) # polar phi = np.arccos(pts[:, 2] / r) # azimuthal theta = np.arctan2(pts[:, 1], pts[:, 0]) # generate spherical harmonics sph_h = AtomGrid._generate_real_sph_harm(3, theta, phi) # l_max = 3 assert sph_h.shape == (16, 26) for _ in range(100): n1, n2 = np.random.randint(0, 16, 2) re = sum(sph_h[n1] * sph_h[n2] * wts) if n1 != n2: print(n1, n2, re) assert_almost_equal(re, 0) else: print(n1, n2, re) assert_almost_equal(re, 1) for i in range(10): sph_h = AtomGrid._generate_real_sph_harm(i, theta, phi) assert sph_h.shape == ((i + 1)**2, 26)
def test_generate_real_sph_harms_integrates_correctly(self): """Test generated real spherical harmonics integrates correctly.""" angular = AngularGrid(degree=7) pts = angular.points wts = angular.weights r = np.linalg.norm(pts, axis=1) # polar phi = np.arccos(pts[:, 2] / r) # azimuthal theta = np.arctan2(pts[:, 1], pts[:, 0]) # generate spherical harmonics lmax = 3 sph_h = generate_real_spherical_harmonics(lmax, theta, phi) # l_max = 3 # Test the shape matches (order, degree, number of points) assert sph_h.shape == (1 + 3 + 5 + 7, 26) counter = 0 for l_value in range(0, lmax + 1): for m in ([0] + [x for x in range(1, l_value + 1)] + [-x for x in range(1, l_value + 1)]): # print(l_value, m) re = sum(sph_h[counter, :] * wts) if l_value == 0: assert_almost_equal(re, np.sqrt(4.0 * np.pi)) else: assert_almost_equal(re, 0) # no nan in the final result assert np.sum(np.isnan(re)) == 0 counter += 1
def test_project_function_onto_spherical_expansion(self): r"""Test projecting a spherical harmonic onto itself and check if it's the same.""" l_max = 4 angular_grid = AngularGrid(degree=10) # Convert Cartesian points to spherical coordinates r = np.linalg.norm(angular_grid.points, axis=1) phi = np.arccos(angular_grid.points[:, 2] / r) theta = np.arctan2(angular_grid.points[:, 1], angular_grid.points[:, 0]) # Generate the real spherical harmonics up to degree l_max for all orders m. sph_harms = generate_real_sph_harms(l_max, theta, phi) # Go through each degree l and order m for l_deg in range(0, l_max + 1): for m_ord in range(-l_deg, l_deg + 1): # Get spherical harmonic with order m and degree l sph_harm_l_m = sph_harms[m_ord, l_deg, :] # Project sph_harm_l_m onto the spherical harmonic expnasion # Returns (1, l_{max}* 2 + 1, l_{max}) proj_coeffs = project_function_onto_spherical_expansion( sph_harms, sph_harm_l_m, angular_grid.weights, [0, angular_grid.size], ) # Fourier coefficients from projection onto itself should be one. assert np.abs(proj_coeffs[0, m_ord, l_deg] - 1.0) < 1e-8 for l2 in range(0, l_max + 1): for m2 in range(-l2, l2 + 1): if l2 != l_deg or m_ord != m2: # Spherical harmonic forms an orthogonal system. assert np.abs(proj_coeffs[0, m2, l2]) < 1e-8
def get_shell_grid(self, index, r_sq=True): """Get the spherical integral grid at radial point {index}. Parameters ---------- index : int index of radial points, start from 0 r_sq : bool, default True the grid weights times r**2, total integral sum to 4 pi r**2 if False, the total integral sum to 4 pi Returns ------- AngularGrid AngularGrid at given radial index position. """ ind_start = self._indices[index] ind_end = self._indices[index + 1] pts = self._points[ind_start:ind_end] wts = self._weights[ind_start:ind_end] # try not to modify wts incase some weird situation. if r_sq is False: new_wts = wts / (self._rgrid.points[index]**2) else: new_wts = wts return AngularGrid(pts, new_wts)
def from_pruned( cls, rgrid, radius, *_, sectors_r, sectors_degree=None, sectors_size=None, center=None, rotate=False, ): """Initialize an instance for given r_sectors of radius and degrees. Examples -------- >>> sectors_r = [0.5, 1., 1.5] >>> sectors_degree = [3, 7, 5, 3] rad is the radius of atom # 0 <= r < 0.5rad, angular grid with degree 3 # 0.5rad <= r < rad, angular grid with degree 7 # rad <= r < 1.5rad, angular grid with degree 5 # 1.5rad <= r, angular grid with degree 3 >>> atgrid = AtomGrid.from_pruned(rgrid, radius, sectors_r, sectors_degree) Parameters ---------- rgrid : OneDGrid The (1-dimensional) radial grid representing the radius of spherical grids. radius : float The atomic radius to be multiplied with `r_sectors` (to make them atom specific). sectors_r : np.ndarray(N,), keyword-only argument Sequence of boundary points specifying radial sectors of the pruned grid. The first sector is ``[0, radius*sectors_r[0]]``, then ``[radius*sectors_r[0], radius*sectors_r[1]]``, and so on. sectors_degree : np.ndarray(N + 1, dtype=int), keyword-only argument Sequence of Lebedev degrees for each radial sector of the pruned grid. sectors_size : np.ndarray(N + 1, dtype=int), keyword-only argument Sequence of Lebedev sizes for each radial sector of the pruned grid. If both sectors_degree and sectors_size are given, sectors_degree is used. center : np.ndarray(3,), optional, keyword-only argument Cartesian coordinates of the grid center. If `None`, the origin is used. rotate : bool or int , optional Whether to rotate the Lebedev spherical grids at each radial grid point. If given an integer, it is used as a seed for generating random rotation matrices. Returns ------- AtomGrid Generated AtomGrid instance for this special init method. """ if sectors_degree is None: sectors_degree = AngularGrid.convert_lebedev_sizes_to_degrees( sectors_size) center = (np.zeros(3, dtype=float) if center is None else np.asarray(center, dtype=float)) cls._input_type_check(rgrid, center) degrees = cls._generate_degree_from_radius(rgrid, radius, sectors_r, sectors_degree) return cls(rgrid, degrees=degrees, center=center, rotate=rotate)
def convert_cartesian_to_spherical(self, points: np.ndarray = None, center: np.ndarray = None): r"""Convert a set of points from Cartesian to spherical coordinates. The conversion is defined as .. math:: \begin{align} r &= \sqrt{x^2 + y^2 + z^2}\\ \theta &= arc\tan (\frac{y}{x})\\ \phi &= arc\cos(\frac{z}{r}) \end{align} with the canonical choice :math:`r=0`, then :math:`\theta,\phi = 0`. If the `points` attribute is not specified, then atomic grid points are used and the canonical choice when :math:`r=0`, is the points :math:`(r=0, \theta_j, \phi_j)` where :math:`(\theta_j, \phi_j)` come from the Angular/Lebedev grid with the degree at :math:`r=0`. Parameters ---------- points : ndarray(n, 3), optional Points in three-dimensions. Atomic grid points will be used if `points` is not given center : ndarray(3,), optional Center of the atomic grid points. If `center` is not provided, then the atomic center of this class is used. Returns ------- ndarray(N, 3) Spherical coordinates of atoms respect to the center (radius :math:`r`, azimuthal :math:`\theta`, polar :math:`\phi`). """ is_atomic = False if points is None: points = self.points is_atomic = True if points.ndim == 1: points = points.reshape(-1, 3) center = self.center if center is None else np.asarray(center) spherical_points = convert_cart_to_sph(points, center) # If atomic grid points are being converted, then choose canonical angles (when r=0) # to be from the degree specified of that point. The reasoning is to insure that # the integration of spherical harmonic when l=l, m=0, is zero even when r=0. if is_atomic: r_index = np.where(self.rgrid.points == 0.0)[0] for i in r_index: # build angular grid for the degree at r=0 agrid = AngularGrid(degree=self._degs[i]) start_index = self._indices[i] final_index = self._indices[i + 1] spherical_points[start_index:final_index, 1:] = convert_cart_to_sph(agrid.points)[:, 1:] return spherical_points
def test_integration_of_spherical_harmonic_up_to_degree_three(self): r"""Test integration of spherical harmonic up to degree three is accurate.""" degree = 3 grid = AngularGrid(degree=100) # Concert to spherical coordinates from Cartesian. r = np.linalg.norm(grid.points, axis=1) phi = np.arccos(grid.points[:, 2] / r) theta = np.arctan2(grid.points[:, 1], grid.points[:, 0]) # Generate All Spherical Harmonics Up To Degree = 3 # Returns a three dimensional array where [order m, degree l, points] sph_harm = generate_real_spherical_harmonics(degree, theta, phi) for l_deg in range(0, 4): for m_ord in range(-l_deg, l_deg): sph_harm_one = sph_harm[l_deg**2 : (l_deg + 1) ** 2, :] if l_deg == 0 and m_ord == 0: actual = np.sqrt(4.0 * np.pi) assert_equal(actual, grid.integrate(sph_harm_one[m_ord, :])) else: assert_almost_equal(0.0, grid.integrate(sph_harm_one[m_ord, :]))
def test_orthogonality_of_spherical_harmonic_up_to_degree_three(self): r"""Test orthogonality of spherical harmonic up to degree 3 is accurate.""" degree = 3 grid = AngularGrid(degree=10) # Concert to spherical coordinates from Cartesian. r = np.linalg.norm(grid.points, axis=1) phi = np.arccos(grid.points[:, 2] / r) theta = np.arctan2(grid.points[:, 1], grid.points[:, 0]) # Generate All Spherical Harmonics Up To Degree = 3 # Returns a three dimensional array where [order m, degree l, points] sph_harm = generate_real_sph_harms(degree, theta, phi) for l_deg in range(0, 4): for m_ord in range(-l_deg, l_deg + 1): for l2 in range(0, 4): for m2 in range(-l_deg, l_deg + 1): integral = grid.integrate(sph_harm[m_ord, l_deg, :] * sph_harm[m2, l2, :]) if l2 != l_deg or m2 != m_ord: assert np.abs(integral) < 1e-8 else: assert np.abs(integral - 1.0) < 1e-8
def get_shell_grid(self, index: int, r_sq: bool = True): """Get the spherical integral grid at radial point from specified index. The spherical integration grid has points scaled with the ith radial point and weights multipled by the ith weight of the radial grid. Note that when :math:`r=0` then the Cartesian points are all zeros. Parameters ---------- index : int Index of radial points. r_sq : bool, default True If true, multiplies the angular grid weights with r**2. Returns ------- AngularGrid AngularGrid at given radial index position and weights. """ if not (0 <= index < len(self.degrees)): raise ValueError( f"Index {index} should be between 0 and less than number of " f"radial points {len(self.degrees)}.") degree = self.degrees[index] sphere_grid = AngularGrid(degree=degree) pts = sphere_grid.points.copy() wts = sphere_grid.weights.copy() # Rotate the points if self.rotate != 0: rot_mt = R.random(random_state=self.rotate + index).as_matrix() pts = pts.dot(rot_mt) pts = pts * self.rgrid[index].points wts = wts * self.rgrid[index].weights if r_sq is True: wts = wts * self.rgrid[index].points**2 return AngularGrid(pts, wts)
def from_preset( cls, rgrid: OneDGrid = None, *, atnum: int, preset: str, center: np.ndarray = None, rotate: int = 0, ): """High level api to construct an atomic grid with preset arguments. Examples -------- # construct an atomic grid for H with fine grid setting >>> atgrid = AtomGrid.from_preset(rgrid, atnum=1, preset="fine") Parameters ---------- rgrid : OneDGrid, optional The (1-dimensional) radial grid representing the radius of spherical grids. atnum : int, keyword-only argument The atomic number specifying the predefined grid. preset : str, keyword-only argument The name of predefined grid specifying the radial sectors and their corresponding number of Lebedev grid points. Supported preset options include: 'coarse', 'medium', 'fine', 'veryfine', 'ultrafine', and 'insane'. center : ndarray(3,), optional, keyword-only argument Cartesian coordinates of the grid center. If `None`, the origin is used. rotate : int, optional Integer used as a seed for generating random rotation matrices to rotate the Lebedev spherical grids at each radial grid point. If the integer is zero, then no rotate is used. """ if rgrid is None: # TODO: generate a default rgrid, currently raise an error instead raise ValueError("A default OneDGrid will be generated") center = (np.zeros(3, dtype=float) if center is None else np.asarray(center, dtype=float)) cls._input_type_check(rgrid, center) # load radial points and with path("grid.data.prune_grid", f"prune_grid_{preset}.npz") as npz_file: data = np.load(npz_file) # load predefined_radial sectors and num_of_points in each sectors rad = data[f"{atnum}_rad"] npt = data[f"{atnum}_npt"] degs = AngularGrid.convert_lebedev_sizes_to_degrees(npt) rad_degs = AtomGrid._find_l_for_rad_list(rgrid.points, rad, degs) return cls(rgrid, degrees=rad_degs, center=center, rotate=rotate)
def test_get_shell_grid(self): """Test angular grid get from get_shell_grid function.""" rad_pts = np.array([0.1, 0.5, 1]) rad_wts = np.array([0.3, 0.4, 0.3]) rad_grid = OneDGrid(rad_pts, rad_wts) degs = [3, 5, 7] atgrid = AtomGrid(rad_grid, degrees=degs) assert atgrid.n_shells == 3 # grep shell with r^2 for i in range(atgrid.n_shells): sh_grid = atgrid.get_shell_grid(i) assert isinstance(sh_grid, AngularGrid) ref_grid = AngularGrid(degree=degs[i]) assert np.allclose(sh_grid.points, ref_grid.points * rad_pts[i]) assert np.allclose(sh_grid.weights, ref_grid.weights * rad_wts[i] * rad_pts[i]**2) # grep shell without r^2 for i in range(atgrid.n_shells): sh_grid = atgrid.get_shell_grid(i, r_sq=False) assert isinstance(sh_grid, AngularGrid) ref_grid = AngularGrid(degree=degs[i]) assert np.allclose(sh_grid.points, ref_grid.points * rad_pts[i]) assert np.allclose(sh_grid.weights, ref_grid.weights * rad_wts[i])
def integrate_angular_coordinates(self, func_vals: np.ndarray): r"""Integrate the angular coordinates of a sequence of functions. Given a series of functions :math:`f_k \in L^2(\mathbb{R}^3)`, this returns the values .. math:: f_k(r_i) = \int \int f(r_i, \theta, \phi) sin(\theta) d\theta d\phi on each radial point :math:`r_i` in the atomic grid. Parameters ---------- func_vals : ndarray(..., N) The function values evaluated on all :math:`N` points on the atomic grid for many types of functions. This can also be one-dimensional. Returns ------- ndarray(..., M) : The function :math:`f_{...}(r_i)` on each :math:`M` radial points. """ # Integrate f(r, \theta, \phi) sin(\theta) d\theta d\phi by multiplying against its weights prod_value = func_vals * self.weights # Multiply weights to the last axis. # [..., indices] means only take the last axis, this is due func_vals being # multi-dimensional, take a sum over the last axis only and swap axes so that it # has shape (..., M) where ... is the number of functions and M is the number of # radial points. radial_coefficients = np.array([ np.sum(prod_value[..., self.indices[i]:self.indices[i + 1]], axis=-1) for i in range(self.n_shells) ]) radial_coefficients = np.moveaxis(radial_coefficients, 0, -1) # swap points axes to last # Remove the radial weights and r^2 values that are in self.weights radial_coefficients /= self.rgrid.points**2 * self.rgrid.weights # For radius smaller than 1.0e-8, due to division by zero, we regenerate # the angular grid and calculate the integral at those points. r_index = np.where(self.rgrid.points < 1e-8)[0] for i in r_index: # if r_index = [], then for loop doesn't occur. # build angular grid for i-th shell agrid = AngularGrid(degree=self._degs[i]) values = (func_vals[..., self.indices[i]:self.indices[i + 1]] * agrid.weights) radial_coefficients[..., i] = np.sum(values, axis=-1) return radial_coefficients
def _generate_degree_from_radius( rgrid: OneDGrid, radius: float, r_sectors: Union[list, np.ndarray], deg_sectors: Union[list, np.ndarray], ): """ Get all degrees for every radial point inside the radial grid based on the sectors. Parameters ---------- rgrid : OneDGrid Radial grid with :math:`N` points. radius : float Radius of interested atom. r_sectors : list or ndarray List of radial sectors r_sectors array. degrees : list or ndarray Degrees for each radius section. Returns ------- ndarray(N,) Array of degree values :math:`l` for each radial point. """ r_sectors = np.array(r_sectors) deg_sectors = np.array(deg_sectors) if len(deg_sectors) == 0: raise ValueError("deg_sectors can't be empty.") if len(deg_sectors) - len(r_sectors) != 1: raise ValueError( "degs should have only one more element than r_sectors.") # match given degrees to the supported (i.e., pre-computed) Lebedev degrees matched_deg = np.array([ AngularGrid._get_lebedev_size_and_degree(degree=d)[0] for d in deg_sectors ]) rad_degs = AtomGrid._find_l_for_rad_list(rgrid.points, radius * r_sectors, matched_deg) return rad_degs
def test_generate_atomic_grid(self): """Test for generating atomic grid.""" # setup testing class rad_pts = np.array([0.1, 0.5, 1]) rad_wts = np.array([0.3, 0.4, 0.3]) rad_grid = OneDGrid(rad_pts, rad_wts) degs = np.array([3, 5, 7]) pts, wts, ind = AtomGrid._generate_atomic_grid(rad_grid, degs) assert len(pts) == 46 assert_equal(ind, [0, 6, 20, 46]) # set tests for slicing grid from atomic grid for i in range(3): # set each layer of points ref_grid = AngularGrid(degree=degs[i]) # check for each point assert_allclose(pts[ind[i] : ind[i + 1]], ref_grid.points * rad_pts[i]) # check for each weight assert_allclose( wts[ind[i] : ind[i + 1]], ref_grid.weights * rad_wts[i] * rad_pts[i] ** 2, )
def _generate_degree_from_radius(rgrid, radius, r_sectors, degrees): """Generate proper degrees for radius. Parameters ---------- rgrid : RadialGrid A radialgrid instance radius : float radius of interested atom r_sectors : list or np.ndarray a list of r_sectors number degrees : list or np.ndarray a list of degs for each radius section Returns ------- np.ndarray a numpy array of L degree value for each radial point """ r_sectors = np.array(r_sectors) degrees = np.array(degrees) if len(degrees) == 0: raise ValueError("rad_list can't be empty.") if len(degrees) - len(r_sectors) != 1: raise ValueError( "degs should have only one more element than r_sectors.") # match given degrees to the supported (i.e., pre-computed) Lebedev degrees matched_deg = np.array([ AngularGrid._get_lebedev_size_and_degree(degree=d)[0] for d in degrees ]) rad_degs = AtomGrid._find_l_for_rad_list(rgrid.points, radius * r_sectors, matched_deg) return rad_degs
def _generate_atomic_grid(rgrid: OneDGrid, degrees: np.ndarray, rotate: int = 0): """Generate atomic grid for each radial point with angular degree. Parameters ---------- rgrid : OneDGrid The (1-dimensional) radial grid representing the radius of spherical grids. degrees : ndarray(N,) Sequence of Lebedev grid degrees used for constructing spherical grids at each radial grid point. If the given degree is not supported, the next largest degree is used. rotate : int, optional Integer used as a seed for generating random rotation matrices to rotate the Lebedev spherical grids at each radial grid point. If the integer is zero, then no rotate is used. Returns ------- tuple(ndarray(M,), ndarray(M,), ndarray(N + 1,), ndarray(N,)), Atomic grid points, atomic grid weights, indices and degrees for each shell. """ if len(degrees) != rgrid.size: raise ValueError( "The shape of radial grid does not match given degs.") all_points, all_weights = [], [] shell_pt_indices = np.zeros(len(degrees) + 1, dtype=int) # set index to int actual_degrees = ( [] ) # The actual degree used to construct the Angular/lebedev grid. for i, deg_i in enumerate(degrees): # TODO: proper tests # Generate Angular grid with the correct degree at the ith radial point. sphere_grid = AngularGrid(degree=deg_i) # Note that the copy is needed here. points, weights = sphere_grid.points.copy( ), sphere_grid.weights.copy() actual_degrees.append(sphere_grid.degree) if rotate == 0: pass # if rotate is a seed else: assert isinstance(rotate, int) # check seed proper value rot_mt = R.random(random_state=rotate + i).as_matrix() points = points @ rot_mt # construct atomic grid with each radial point and each spherical shell # compute points points = points * rgrid[i].points # compute weights weights = weights * rgrid[i].weights * rgrid[i].points**2 # locate separators shell_pt_indices[i + 1] = shell_pt_indices[i] + len(points) all_points.append(points) all_weights.append(weights) indices = shell_pt_indices points = np.vstack(all_points) weights = np.hstack(all_weights) return points, weights, indices, actual_degrees
def test_errors_and_warnings(self): """Tests for errors and warning.""" # low level function tests with self.assertRaises(ValueError): AngularGrid._get_lebedev_size_and_degree() with self.assertRaises(ValueError): AngularGrid._get_lebedev_size_and_degree(degree=-1) with self.assertRaises(ValueError): AngularGrid._get_lebedev_size_and_degree(degree=132) with self.assertRaises(ValueError): AngularGrid._get_lebedev_size_and_degree(size=-1) with self.assertRaises(ValueError): AngularGrid._get_lebedev_size_and_degree(size=6000) with self.assertWarns(RuntimeWarning): AngularGrid._get_lebedev_size_and_degree(degree=5, size=10) # load lebedev grid npz file with self.assertRaises(ValueError): AngularGrid._load_lebedev_grid(degree=2, size=6) with self.assertRaises(ValueError): AngularGrid._load_lebedev_grid(degree=3, size=2) # high level function tests with self.assertRaises(ValueError): AngularGrid() with self.assertRaises(ValueError): AngularGrid(size=6000) with self.assertRaises(ValueError): AngularGrid(size=-1) with self.assertRaises(ValueError): AngularGrid(degree=132) with self.assertRaises(ValueError): AngularGrid(degree=-2) with self.assertWarns(RuntimeWarning): AngularGrid(degree=5, size=10) with self.assertWarns(RuntimeWarning): pts = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 1]], dtype=float) wts = np.array([0.3, 0.4, 0.5]) AngularGrid(pts, wts, degree=7) with self.assertWarns(RuntimeWarning): pts = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 1]], dtype=float) wts = np.array([0.3, 0.4, 0.5]) AngularGrid(pts, wts, size=14)
def from_pruned( cls, rgrid: OneDGrid, radius: float, *_, sectors_r: np.ndarray, sectors_degree: np.ndarray = None, sectors_size: np.ndarray = None, center: np.ndarray = None, rotate: int = 0, ): r""" Initialize AtomGrid class that splits radial sections into sectors which specified degrees. Given a sequence of radial sectors :math:`\{a_i\}_{i=1}^Q`, a radius number :math:`R` and angular degree sectors :math:`\{L_i \}_{i=1}^{Q+1}`. This assigned the degrees to the following radial points: .. math:: \begin{align*} &L_1 \text{ when } r < R a_1 \\ &L_2 \text{ when } R a_1 \leq r < R a_2 \vdots \\ &L_{Q+1} \text{ when } R a_{Q} < r. \end{align*} Examples -------- >>> sectors_r = [0.5, 1., 1.5] >>> sectors_degree = [3, 7, 5, 3] # 0 <= r < 0.5 radius, angular grid with degree 3 # 0.5 radius <= r < radius, angular grid with degree 7 # rad <= r < 1.5 radius, angular grid with degree 5 # 1.5 radius <= r, angular grid with degree 3 >>> atgrid = AtomGrid.from_pruned(rgrid, radius, sectors_r, sectors_degree) Parameters ---------- rgrid : OneDGrid The (one-dimensional) radial grid representing the radius of spherical grids. radius : float The atomic radius to be multiplied with `r_sectors` (to make them atom specific). sectors_r : ndarray(N,), keyword-only argument Sequence of boundary points specifying radial sectors of the pruned grid. The first sector is ``[0, radius*sectors_r[0]]``, then ``[radius*sectors_r[0], radius*sectors_r[1]]``, and so on. sectors_degree : ndarray(N + 1, dtype=int), keyword-only argument Sequence of Lebedev degrees for each radial sector of the pruned grid. sectors_size : ndarray(N + 1, dtype=int), keyword-only argument Sequence of Lebedev sizes for each radial sector of the pruned grid. If both sectors_degree and sectors_size are given, sectors_degree is used. center : ndarray(3,), optional, keyword-only argument Cartesian coordinates of the grid center. If `None`, the origin is used. rotate : int, optional Integer used as a seed for generating random rotation matrices to rotate the Lebedev spherical grids at each radial grid point. If the integer is zero, then no rotate is used. Returns ------- AtomGrid Generated AtomGrid instance for this special init method. """ if sectors_degree is None: sectors_degree = AngularGrid.convert_lebedev_sizes_to_degrees( sectors_size) center = (np.zeros(3, dtype=float) if center is None else np.asarray(center, dtype=float)) cls._input_type_check(rgrid, center) degrees = cls._generate_degree_from_radius(rgrid, radius, sectors_r, sectors_degree) return cls(rgrid, degrees=degrees, center=center, rotate=rotate)
def setUp(self): """Generate atomic grid for constant test call.""" self.ang_grid = AngularGrid(degree=7)
def __init__(self, rgrid, *, degrees=None, sizes=None, center=None, rotate=False): """Construct atomic grid for given arguments. Parameters ---------- rgrid : OneDGrid The (1-dimensional) radial grid representing the radius of spherical grids. degrees : np.ndarray(N, dtype=int) or list, keyword-only argument Sequence of Lebedev grid degrees used for constructing spherical grids at each radial grid point. If only one degree is given, the specified degree is used for all spherical grids. If the given degree is not supported, the next largest degree is used. sizes : np.ndarray(N, dtype=int) or list, keyword-only argument Sequence of Lebedev grid sizes used for constructing spherical grids at each radial grid point. If only one size is given, the specified size is used for all spherical grids. If the given size is not supported, the next largest size is used. If both degrees and sizes are given, degrees is used for making the spherical grids. center : np.ndarray(3,), optional, keyword-only argument Cartesian coordinates of the grid center. If `None`, the origin is used. rotate : bool or int , optional Whether to rotate the Lebedev spherical grids at each radial grid point. If given an integer, it is used as a seed for generating random rotation matrices. """ # check stage, if center is None, set to (0., 0., 0.) center = (np.zeros(3, dtype=float) if center is None else np.asarray(center, dtype=float)) self._input_type_check(rgrid, center) # assign & check stage self._center = center self._rgrid = rgrid # check rotate if not isinstance(rotate, (int, np.integer)): raise TypeError( f"rotate needs to be an bool or integer, got {type(rotate)}") if (rotate is not False) and (not 0 <= rotate < 2**32): raise ValueError( f"rotate need to be an integer [0, 2^32 - 1]\n" f"rotate is not within [0, 2^32 - 1], got {rotate}") self._rot = rotate # check degs and size if degrees is None: if not isinstance(sizes, (np.ndarray, list)): raise TypeError( f"sizes is not type: np.array or list, got {type(sizes)}") degrees = AngularGrid.convert_lebedev_sizes_to_degrees(sizes) if not isinstance(degrees, (np.ndarray, list)): raise TypeError( f"degrees is not type: np.array or list, got {type(degrees)}") if len(degrees) == 1: degrees = np.ones(rgrid.size, dtype=int) * degrees self._degs = degrees self._points, self._weights, self._indices = self._generate_atomic_grid( self._rgrid, self._degs, rotate=self._rot) self._size = self._weights.size self._basis = None