Example #1
0
 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
Example #2
0
    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
Example #3
0
    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
Example #4
0
 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]),
         )
Example #5
0
    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
Example #6
0
 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)
Example #7
0
    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)
Example #8
0
 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
Example #9
0
 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
Example #10
0
    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)
Example #11
0
    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)
Example #12
0
    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
Example #13
0
 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, :]))
Example #14
0
 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
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
 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])
Example #18
0
    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
Example #19
0
    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
Example #20
0
 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,
         )
Example #21
0
    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
Example #22
0
    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
Example #23
0
 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)
Example #24
0
    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)
Example #25
0
 def setUp(self):
     """Generate atomic grid for constant test call."""
     self.ang_grid = AngularGrid(degree=7)
Example #26
0
    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