Exemple #1
0
    def test_atoms_to_system(self):
        """Tests that an ASE Atoms is succesfully converted to a System object.
        """
        class NaClFactory(SimpleCubicFactory):
            "A factory for creating NaCl (B1, Rocksalt) lattices."

            bravais_basis = [[0, 0, 0], [0, 0, 0.5], [0, 0.5, 0],
                             [0, 0.5, 0.5], [0.5, 0, 0], [0.5, 0, 0.5],
                             [0.5, 0.5, 0], [0.5, 0.5, 0.5]]
            element_basis = (0, 1, 1, 0, 1, 0, 0, 1)

        nacl = NaClFactory()(symbol=["Na", "Cl"], latticeconstant=5.6402)
        system = System.from_atoms(nacl)

        self.assertTrue(
            np.array_equal(nacl.get_positions(), system.get_positions()))
        self.assertTrue(
            np.array_equal(nacl.get_initial_charges(),
                           system.get_initial_charges()))
        self.assertTrue(
            np.array_equal(nacl.get_atomic_numbers(),
                           system.get_atomic_numbers()))
        self.assertTrue(
            np.array_equal(nacl.get_chemical_symbols(),
                           system.get_chemical_symbols()))
        self.assertTrue(np.array_equal(nacl.get_cell(), system.get_cell()))
        self.assertTrue(np.array_equal(nacl.get_pbc(), system.get_pbc()))
        self.assertTrue(
            np.array_equal(nacl.get_scaled_positions(),
                           system.get_scaled_positions()))
    def test_system_input(self):
        """Tests that create takes internal system object.
        """
        system = System.from_atoms(H2O)

        lmax = 5
        nmax = 5
        n_elems = 2
        desc = SOAP(species=[1, 8], rcut=3, nmax=nmax, lmax=lmax, periodic=True)

        vec = desc.create(system)
Exemple #3
0
    def _get_k3(self, system):
        """Calculates the third order terms.

        Returns:
            1D ndarray: flattened K3 values.
        """
        grid = self.k3["grid"]
        start = grid["min"]
        stop = grid["max"]
        n = grid["n"]
        sigma = grid["sigma"]

        # Determine the weighting function and possible radial cutoff
        radial_cutoff = None
        weighting = self.k3.get("weighting")
        parameters = {}
        if weighting is not None:
            weighting_function = weighting["function"]
            if weighting_function == "exp" or weighting_function == "exponential":
                scale = weighting["scale"]
                threshold = weighting["threshold"]
                if scale != 0:
                    radial_cutoff = -0.5 * math.log(threshold) / scale
                parameters = {b"scale": scale, b"threshold": threshold}
        else:
            weighting_function = "unity"

        # Determine the geometry function
        geom_func_name = self.k3["geometry"]["function"]

        # If needed, create the extended system
        if self.periodic:
            centers = system.get_positions()
            ext_system, cell_indices = dscribe.utils.geometry.get_extended_system(
                system, radial_cutoff, centers, return_cell_indices=True)
            ext_system = System.from_atoms(ext_system)
        else:
            ext_system = system
            cell_indices = np.zeros((len(system), 3), dtype=int)

        cmbtr = MBTRWrapper(self.atomic_number_to_index,
                            self._interaction_limit, cell_indices)

        # If radial cutoff is finite, use it to calculate the sparse
        # distance matrix to reduce computational complexity from O(n^2) to
        # O(n log(n))
        n_atoms = len(ext_system)
        if radial_cutoff is not None:
            dmat = ext_system.get_distance_matrix_within_radius(radial_cutoff)
            adj_list = dscribe.utils.geometry.get_adjacency_list(dmat)
            dmat_dense = np.full(
                (n_atoms, n_atoms), sys.float_info.max
            )  # The non-neighbor values are treated as "infinitely far".
            dmat_dense[dmat.col, dmat.row] = dmat.data
        # If no weighting is used, the full distance matrix is calculated
        else:
            dmat_dense = ext_system.get_distance_matrix()
            adj_list = np.tile(np.arange(n_atoms), (n_atoms, 1))

        k3_map = cmbtr.get_k3(
            ext_system.get_atomic_numbers(),
            dmat_dense,
            adj_list,
            geom_func_name.encode(),
            weighting_function.encode(),
            parameters,
            start,
            stop,
            sigma,
            n,
        )

        k3_map = self._make_new_kmap(k3_map)
        # Depending of flattening, use either a sparse matrix or a dense one.
        n_elem = self.n_elements
        if self.flatten:
            k3 = sparse.DOK((int(n_elem * n_elem * (n_elem + 1) / 2 * n)),
                            dtype=np.float32)
        else:
            k3 = np.zeros((n_elem, n_elem, n_elem, n), dtype=np.float32)

        for key, gaussian_sum in k3_map.items():
            i = key[0]
            j = key[1]
            k = key[2]

            # This is the index of the spectrum. It is given by enumerating the
            # elements of a three-dimensional array where for valid elements
            # k>=i. The enumeration begins from [0, 0, 0], and ends at [n_elem,
            # n_elem, n_elem], looping the elements in the order j, i, k.
            m = int(j * n_elem * (n_elem + 1) / 2 + k + i * n_elem - i *
                    (i + 1) / 2)

            # Denormalize if requested
            if not self.normalize_gaussians:
                max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                gaussian_sum /= max_val

            if self.flatten:
                start = m * n
                end = (m + 1) * n
                k3[start:end] = gaussian_sum
            else:
                k3[i, j, k, :] = gaussian_sum
        if self.flatten:
            k3 = k3.to_coo()

        return k3
Exemple #4
0
    def _get_k3(self, system, new_system, indices):
        """Calculates the second order terms where the scalar mapping is the
        inverse distance between atoms.

        Returns:
            1D ndarray: flattened K2 values.
        """
        grid = self.k3["grid"]
        start = grid["min"]
        stop = grid["max"]
        n = grid["n"]
        sigma = grid["sigma"]

        # Determine the weighting function and possible radial cutoff
        radial_cutoff = None
        weighting = self.k3.get("weighting")
        parameters = {}
        if weighting is not None:
            weighting_function = weighting["function"]
            if weighting_function == "exponential" or weighting_function == "exp":
                scale = weighting["scale"]
                cutoff = weighting["cutoff"]
                if scale != 0:
                    radial_cutoff = -0.5 * math.log(cutoff) / scale
                parameters = {b"scale": scale, b"cutoff": cutoff}
        else:
            weighting_function = "unity"

        # Determine the geometry function
        geom_func_name = self.k3["geometry"]["function"]

        # Calculate extended system
        if self.periodic:
            centers_new = new_system.get_positions()
            centers_existing = system.get_positions()[indices]
            centers = np.concatenate((centers_new, centers_existing), axis=0)
            ext_system, cell_indices = dscribe.utils.geometry.get_extended_system(
                system,
                radial_cutoff,
                centers,
                return_cell_indices=True,
            )
            ext_system = System.from_atoms(ext_system)
        else:
            ext_system = system
            cell_indices = np.zeros((len(system), 3), dtype=int)

        cmbtr = MBTRWrapper(self.atomic_number_to_index,
                            self._interaction_limit, cell_indices)

        # If radial cutoff is finite, use it to calculate the sparse
        # distance matrix to reduce computational complexity from O(n^2) to
        # O(n log(n))
        fin_system = ext_system + new_system
        n_atoms_ext = len(ext_system)
        n_atoms_fin = len(fin_system)
        n_atoms_new = len(new_system)
        ext_pos = ext_system.get_positions()
        new_pos = new_system.get_positions()
        if radial_cutoff is not None:

            # Calculate distance within the extended system
            dmat_ext_to_ext = ext_system.get_distance_matrix_within_radius(
                radial_cutoff, pos=ext_pos)
            col = dmat_ext_to_ext.col
            row = dmat_ext_to_ext.row
            data = dmat_ext_to_ext.data
            dmat = scipy.sparse.coo_matrix((data, (row, col)),
                                           shape=(n_atoms_fin, n_atoms_fin))

            # Calculate the distances from the new positions to atoms in the
            # extended system using the cutoff
            if len(new_pos) != 0:
                dmat_ext_to_new = ext_system.get_distance_matrix_within_radius(
                    radial_cutoff, pos=new_pos)
                col = dmat_ext_to_new.col
                row = dmat_ext_to_new.row
                data = dmat_ext_to_new.data
                dmat.col = np.append(dmat.col, col + n_atoms_ext)
                dmat.row = np.append(dmat.row, row)
                dmat.data = np.append(dmat.data, data)
                dmat.col = np.append(dmat.col, row)
                dmat.row = np.append(dmat.row, col + n_atoms_ext)
                dmat.data = np.append(dmat.data, data)

            # Calculate adjacencies and transform to the dense matrix for
            # sending information to C++
            adj_list = dscribe.utils.geometry.get_adjacency_list(dmat)
            dmat_dense = np.full(
                (n_atoms_fin, n_atoms_fin), sys.float_info.max
            )  # The non-neighbor values are treated as "infinitely far".
            dmat_dense[dmat.row, dmat.col] = dmat.data

        # If no weighting is used, the full distance matrix is calculated
        else:
            dmat = scipy.sparse.lil_matrix((n_atoms_fin, n_atoms_fin))

            # Fill in block for extended system
            dmat_ext_to_ext = ext_system.get_distance_matrix()
            dmat[0:n_atoms_ext, 0:n_atoms_ext] = dmat_ext_to_ext

            # Fill in block for extended system to new system
            dmat_ext_to_new = scipy.spatial.distance.cdist(ext_pos, new_pos)
            dmat[0:n_atoms_ext,
                 n_atoms_ext:n_atoms_ext + n_atoms_new] = dmat_ext_to_new
            dmat[n_atoms_ext:n_atoms_ext + n_atoms_new,
                 0:n_atoms_ext] = dmat_ext_to_new.T

            # Calculate adjacencies and the dense version
            dmat = dmat.tocoo()
            adj_list = dscribe.utils.geometry.get_adjacency_list(dmat)
            dmat_dense = np.full(
                (n_atoms_fin, n_atoms_fin), sys.float_info.max
            )  # The non-neighbor values are treated as "infinitely far".
            dmat_dense[dmat.row, dmat.col] = dmat.data

        # Form new indices that include the existing atoms and the newly added
        # ones
        indices = np.array(np.append(
            indices, [n_atoms_ext + i for i in range(n_atoms_new)]),
                           dtype=int)

        k3_list = cmbtr.get_k3_local(
            indices,
            fin_system.get_atomic_numbers(),
            dmat_dense,
            adj_list,
            geom_func_name.encode(),
            weighting_function.encode(),
            parameters,
            start,
            stop,
            sigma,
            n,
        )

        k3_list = self._make_new_klist_local(k3_list)
        # Depending on flattening, use either a sparse matrix or a dense one.
        n_elem = self.n_elements
        n_loc = len(indices)
        if self.flatten:
            k3 = lil_matrix((n_loc, int((n_elem * (3 * n_elem - 1) * n / 2))),
                            dtype=np.float32)

            for i_loc, k3_map in enumerate(k3_list):
                for key, gaussian_sum in k3_map.items():
                    i = key[0]
                    j = key[1]
                    k = key[2]

                    # This is the index of the spectrum. It is given by enumerating the
                    # elements of a three-dimensional array and only considering
                    # elements for which k>=i and i || j == 0. The enumeration begins
                    # from [0, 0, 0], and ends at [n_elem, n_elem, n_elem], looping the
                    # elements in the order k, i, j.
                    if j == 0:
                        m = k + i * n_elem - i * (i + 1) / 2
                    else:
                        m = n_elem * (n_elem + 1) / 2 + (j - 1) * n_elem + k
                    start = int(m * n)
                    end = int((m + 1) * n)

                    # Denormalize if requested
                    if not self.normalize_gaussians:
                        max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                        gaussian_sum /= max_val

                    k3[i_loc, start:end] = gaussian_sum
        else:
            k3 = np.zeros((n_loc, n_elem, n_elem, n_elem, n), dtype=np.float32)
            for i_loc, k3_map in enumerate(k3_list):
                for key, gaussian_sum in k3_map.items():
                    i = key[0]
                    j = key[1]
                    k = key[2]

                    # Denormalize if requested
                    if not self.normalize_gaussians:
                        max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                        gaussian_sum /= max_val

                    k3[i_loc, i, j, k, :] = gaussian_sum
        return k3
Exemple #5
0
    def _get_k2(self, system, new_system, indices):
        """Calculates the second order terms where the scalar mapping is the
        inverse distance between atoms.

        Returns:
            1D ndarray: flattened K2 values.
        """
        grid = self.k2["grid"]
        start = grid["min"]
        stop = grid["max"]
        n = grid["n"]
        sigma = grid["sigma"]

        # Determine the weighting function and possible radial cutoff
        radial_cutoff = None
        weighting = self.k2.get("weighting")
        parameters = {}
        if weighting is not None:
            weighting_function = weighting["function"]
            if weighting_function == "exponential" or weighting_function == "exp":
                scale = weighting["scale"]
                cutoff = weighting["cutoff"]
                if scale != 0:
                    radial_cutoff = -math.log(cutoff) / scale
                parameters = {
                    b"scale": weighting["scale"],
                    b"cutoff": weighting["cutoff"]
                }
        else:
            weighting_function = "unity"

        # Determine the geometry function
        geom_func_name = self.k2["geometry"]["function"]

        # Calculate extended system
        if self.periodic:
            centers = new_system.get_positions()
            ext_system, cell_indices = dscribe.utils.geometry.get_extended_system(
                system,
                radial_cutoff,
                centers,
                return_cell_indices=True,
            )
            ext_system = System.from_atoms(ext_system)
        else:
            ext_system = system
            cell_indices = np.zeros((len(system), 3), dtype=int)

        cmbtr = MBTRWrapper(self.atomic_number_to_index,
                            self._interaction_limit, cell_indices)

        # If radial cutoff is finite, use it to calculate the sparse distance
        # matrix to reduce computational complexity from O(n^2) to O(n log(n)).
        # If radial cutoff is not available, calculate full matrix.
        n_atoms_ext = len(ext_system)
        n_atoms_new = len(new_system)
        ext_pos = ext_system.get_positions()
        new_pos = new_system.get_positions()
        if radial_cutoff is not None:
            dmat = new_system.get_distance_matrix_within_radius(radial_cutoff,
                                                                pos=ext_pos)
            adj_list = dscribe.utils.geometry.get_adjacency_list(dmat)
            dmat_dense = np.full(
                (n_atoms_new, n_atoms_ext), sys.float_info.max
            )  # The non-neighbor values are treated as "infinitely far".
            dmat_dense[dmat.row, dmat.col] = dmat.data
        else:
            dmat_dense = scipy.spatial.distance.cdist(new_pos, ext_pos)
            adj_list = np.tile(np.arange(n_atoms_ext), (n_atoms_new, 1))

        # Form new indices that include the existing atoms and the newly added
        # ones
        indices = np.array(np.append(
            indices,
            [n_atoms_ext + i for i in range(n_atoms_new - len(indices))]),
                           dtype=int)

        k2_list = cmbtr.get_k2_local(
            indices,
            ext_system.get_atomic_numbers(),
            dmat_dense,
            adj_list,
            geom_func_name.encode(),
            weighting_function.encode(),
            parameters,
            start,
            stop,
            sigma,
            n,
        )
        k2_list = self._make_new_klist_local(k2_list)

        # Depending on flattening, use either a sparse matrix or a dense one.
        n_elem = self.n_elements
        n_loc = len(indices)
        if self.flatten:
            k2 = lil_matrix((n_loc, n_elem * n), dtype=np.float32)

            for i_loc, k2_map in enumerate(k2_list):
                for key, gaussian_sum in k2_map.items():
                    i = key[1]
                    m = i
                    start = int(m * n)
                    end = int((m + 1) * n)

                    # Denormalize if requested
                    if not self.normalize_gaussians:
                        max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                        gaussian_sum /= max_val

                    k2[i_loc, start:end] = gaussian_sum
        else:
            k2 = np.zeros((n_loc, n_elem, n), dtype=np.float32)
            for i_loc, k2_map in enumerate(k2_list):
                for key, gaussian_sum in k2_map.items():
                    i = key[1]

                    # Denormalize if requested
                    if not self.normalize_gaussians:
                        max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                        gaussian_sum /= max_val

                    k2[i_loc, i, :] = gaussian_sum

        return k2
Exemple #6
0
def system_stats(system_iterator):
    """
    Args:
        system_stats(iterable containing ASE.Atoms or System): The atomic
            systems for which to gather statistics.

    Returns:
        Dict: A dictionary of different statistics for the system. The
        dictionary will contain:

            n_atoms_max: The maximum number of atoms in a system.
            max_atomic_number: The highest atomic number
            min_atomic_number: The lowest atomic number
            atomic_numbers: List of present atomic numbers
            element_symbols: List of present atomic symbols
            min_distance: Minimum distance in the system
    """
    n_atoms_max = 0
    atomic_numbers = set()
    symbols = set()
    min_distance = None

    for system in system_iterator:
        n_atoms = len(system)

        # Make ASE.Atoms into a System object
        if isinstance(system, Atoms):
            system = System.from_atoms(system)

        i_atomic_numbers = set(system.get_atomic_numbers())
        i_symbols = set(system.get_chemical_symbols())
        distance_matrix = system.get_distance_matrix()

        # Gather atomic numbers and symbols
        atomic_numbers = atomic_numbers.union(i_atomic_numbers)
        symbols = symbols.union(i_symbols)

        # Gather maximum number of atoms
        if n_atoms > n_atoms_max:
            n_atoms_max = n_atoms

        # Gather min distance. For periodic systems we must also consider
        # distances from an atom to it's periodic copy, as given by
        # get_distance_matrix() on the diagonal.
        if np.any(system.get_pbc()):
            triu_indices = np.triu_indices(len(distance_matrix), k=0)
        else:
            triu_indices = np.triu_indices(len(distance_matrix), k=1)
        distances = distance_matrix[triu_indices]
        i_min_dist = distances.min()

        if min_distance is None or i_min_dist < min_distance:
            min_distance = i_min_dist

    return {
        "n_atoms_max": n_atoms_max,
        "max_atomic_number": max(list(atomic_numbers)),
        "min_atomic_number": min(list(atomic_numbers)),
        "atomic_numbers": list(atomic_numbers),
        "element_symbols": list(symbols),
        "min_distance": min_distance,
    }
Exemple #7
0
    def _get_k2(self, system):
        """Calculates the second order terms where the scalar mapping is the
        inverse distance between atoms.

        Returns:
            1D ndarray: flattened K2 values.
        """
        grid = self.k2["grid"]
        start = grid["min"]
        stop = grid["max"]
        n = grid["n"]
        sigma = grid["sigma"]
        # Determine the weighting function and possible radial cutoff
        radial_cutoff = None
        weighting = self.k2.get("weighting")
        parameters = {}
        if weighting is not None:
            weighting_function = weighting["function"]
            if weighting_function == "exponential" or weighting_function == "exp":
                scale = weighting["scale"]
                cutoff = weighting["cutoff"]
                if scale != 0:
                    radial_cutoff = -math.log(cutoff) / scale
                parameters = {b"scale": scale, b"cutoff": cutoff}
        else:
            weighting_function = "unity"

        # Determine the geometry function
        geom_func_name = self.k2["geometry"]["function"]

        # If needed, create the extended system
        if self.periodic:
            centers = system.get_positions()
            ext_system, cell_indices = dscribe.utils.geometry.get_extended_system(
                system, radial_cutoff, centers, return_cell_indices=True)
            ext_system = System.from_atoms(ext_system)
        else:
            ext_system = system
            cell_indices = np.zeros((len(system), 3), dtype=int)

        cmbtr = MBTRWrapper(self.atomic_number_to_index,
                            self._interaction_limit, cell_indices)

        # If radial cutoff is finite, use it to calculate the sparse
        # distance matrix to reduce computational complexity from O(n^2) to
        # O(n log(n))
        n_atoms = len(ext_system)
        if radial_cutoff is not None:
            dmat = ext_system.get_distance_matrix_within_radius(radial_cutoff)
            adj_list = dscribe.utils.geometry.get_adjacency_list(dmat)
            dmat_dense = np.full(
                (n_atoms, n_atoms), sys.float_info.max
            )  # The non-neighbor values are treated as "infinitely far".
            dmat_dense[dmat.row, dmat.col] = dmat.data
        # If no weighting is used, the full distance matrix is calculated
        else:
            dmat_dense = ext_system.get_distance_matrix()
            adj_list = np.tile(np.arange(n_atoms), (n_atoms, 1))

        k2_map = cmbtr.get_k2(
            ext_system.get_atomic_numbers(),
            dmat_dense,
            adj_list,
            geom_func_name.encode(),
            weighting_function.encode(),
            parameters,
            start,
            stop,
            sigma,
            n,
        )

        k2_map = self._make_new_kmap(k2_map)

        # Depending of flattening, use either a sparse matrix or a dense one.
        n_elem = self.n_elements
        if self.flatten:
            k2 = lil_matrix((1, int(n_elem * (n_elem + 1) / 2 * n)),
                            dtype=np.float32)
        else:
            k2 = np.zeros((self.n_elements, self.n_elements, n),
                          dtype=np.float32)

        for key, gaussian_sum in k2_map.items():
            i = key[0]
            j = key[1]

            # This is the index of the spectrum. It is given by enumerating the
            # elements of an upper triangular matrix from left to right and top
            # to bottom.
            m = int(j + i * n_elem - i * (i + 1) / 2)

            # Denormalize if requested
            if not self.normalize_gaussians:
                max_val = 1 / (sigma * math.sqrt(2 * math.pi))
                gaussian_sum /= max_val

            if self.flatten:
                start = m * n
                end = (m + 1) * n
                k2[0, start:end] = gaussian_sum
            else:
                k2[i, j, :] = gaussian_sum

        return k2