def _get_k1(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.k1["grid"] start = grid["min"] stop = grid["max"] n = grid["n"] sigma = grid["sigma"] # Determine the geometry function geom_func_name = self.k1["geometry"]["function"] cmbtr = MBTRWrapper( self.atomic_number_to_index, self._interaction_limit, np.zeros((len(system), 3), dtype=int), ) k1_map = cmbtr.get_k1( system.get_atomic_numbers(), geom_func_name.encode(), b"unity", {}, start, stop, sigma, n, ) k1_map = self._make_new_k1map(k1_map) # Depending on flattening, use either a sparse matrix or a dense one. n_elem = self.n_elements if self.flatten: k1 = sparse.DOK((n_elem * n), dtype=np.float32) else: k1 = np.zeros((n_elem, n), dtype=np.float32) for key, gaussian_sum in k1_map.items(): i = key[0] # 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 = i * n end = (i + 1) * n k1[start:end] = gaussian_sum else: k1[i, :] = gaussian_sum if self.flatten: k1 = k1.to_coo() return k1
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
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
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
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