def hydrogenate(atoms, bins, copy=True): """Add hydrogens to a gratoms object via provided bins""" h_index = len(atoms) edges = [] for i, j in enumerate(bins): for _ in range(j): edges += [(i, h_index)] h_index += 1 if copy: atoms = atoms.copy() atoms += catkit.Gratoms('H{}'.format(sum(bins))) atoms.graph.add_edges_from(edges) return atoms
def convert_adsorbate_atoms_to_gratoms(adsorbate): """ Convert adsorbate atoms object into graphic atoms object, so the adsorbate can be placed onto the surface with optimal configuration. Set tags for adsorbate atoms to 2, to distinguish them from surface atoms. Args: adsorbate An `ase.Atoms` object of the adsorbate Returns: adsorbate_gratoms An graphic atoms object of the adsorbate. """ connectivity = get_connectivity(adsorbate) adsorbate_gratoms = catkit.Gratoms(adsorbate, edges=connectivity) adsorbate_gratoms.set_tags([2] * len(adsorbate_gratoms)) return adsorbate_gratoms
def add_adsorbate_onto_surface(surface, adsorbate, bond_indices): ''' There are a lot of small details that need to be considered when adding an adsorbate onto a surface. This function will take care of those details for you. Args: surface An `ase.Atoms` object of the surface adsorbate An `ase.Atoms` object of the adsorbate bond_indices A list of integers indicating the indices of the binding atoms of the adsorbate Returns: ads_surface An `ase graphic Atoms` object containing the adsorbate and surface. The bulk atoms will be tagged with `0`; the surface atoms will be tagged with `1`, and the the adsorbate atoms will be tagged with `2` or above. ''' # convert surface atoms into graphic atoms object surface_gratoms = catkit.Gratoms(surface) surface_atom_indices = [ i for i, atom in enumerate(surface) if atom.tag == 1 ] surface_gratoms.set_surface_atoms(surface_atom_indices) surface_gratoms.pbc = np.array([True, True, False]) # set up the adsorbate into graphic atoms object # with its connectivity matrix adsorbate_gratoms = convert_adsorbate_atoms_to_gratoms(adsorbate) # generate all possible adsorption configurations on that surface. # The "bonds" argument automatically take care of mono vs. # bidentate adsorption configuration. builder = catkit.gen.adsorption.Builder(surface_gratoms) adsorbed_surfaces = builder.add_adsorbate(adsorbate_gratoms, bonds=bond_indices, index=-1) # Filter out unreasonable structures. # Then pick one from the reasonable configurations list as an output. reasonable_adsorbed_surfaces = [ surface for surface in adsorbed_surfaces if is_config_reasonable(surface) is True ] adsorbed_surface = random.choice(reasonable_adsorbed_surfaces) return adsorbed_surface
def convert_adsorbate_atoms_to_gratoms(self, adsorbate, bond_indices): """ Convert adsorbate atoms object into graphic atoms object, so the adsorbate can be placed onto the surface with optimal configuration. Set tags for adsorbate atoms to 2, to distinguish them from surface atoms. Args: adsorbate An `ase.Atoms` object of the adsorbate bond_indices A list of integers indicating the indices of the binding atoms of the adsorbate Returns: adsorbate_gratoms An graphic atoms object of the adsorbate. """ connectivity = self.get_connectivity(adsorbate) adsorbate_gratoms = catkit.Gratoms(adsorbate, edges=connectivity) # tag adsorbate atoms: non-binding atoms as 2, the binding atom(s) as 3 for now to # track adsorption site for analyzing if adslab configuration is reasonable. adsorbate_gratoms.set_tags([ 3 if idx in bond_indices else 2 for idx in range(len(adsorbate_gratoms)) ]) return adsorbate_gratoms
def get_topologies(symbols, saturate=False): """Return the possible topologies of a given chemical species. Parameters ---------- symbols : str Atomic symbols to construct the topologies from. saturate : bool Saturate the molecule with hydrogen based on the default.radicals set. Returns ------- molecules : list (N,) Gratoms objects with unique connectivity matrix attached. No 3D positions will be provided for these structures. """ num, cnt = catkit.gen.utils.get_atomic_numbers(symbols, True) mcnt = cnt[num != 1] mnum = num[num != 1] if cnt[num == 1]: hcnt = cnt[num == 1][0] else: hcnt = 0 elements = np.repeat(mnum, mcnt) max_degree = catkit.gen.defaults.get('radicals')[elements] n = mcnt.sum() hmax = int(max_degree.sum() - (n - 1) * 2) if hcnt > hmax: hcnt = hmax if saturate: hcnt = hmax if n == 1: atoms = catkit.Gratoms(elements, cell=[1, 1, 1]) hatoms = hydrogenate(atoms, np.array([hcnt])) return [hatoms] elif n == 0: hatoms = catkit.Gratoms('H{}'.format(hcnt)) if hcnt == 2: hatoms.graph.add_edge(0, 1, bonds=1) return [hatoms] ln = np.arange(n).sum() il = np.tril_indices(n, -1) backbones, molecules = [], [] combos = itertools.combinations(np.arange(ln), n - 1) for c in combos: # Construct the connectivity matrix ltm = np.zeros(ln) ltm[np.atleast_2d(c)] = 1 connectivity = np.zeros((n, n)) connectivity[il] = ltm connectivity = np.maximum(connectivity, connectivity.T) degree = connectivity.sum(axis=0) # Not fully connected (subgraph) if np.any(degree == 0) or not \ nx.is_connected(nx.from_numpy_matrix(connectivity)): continue # Overbonded atoms. remaining_bonds = (max_degree - degree).astype(int) if np.any(remaining_bonds < 0): continue atoms = catkit.Gratoms(numbers=elements, edges=connectivity, cell=[1, 1, 1]) isomorph = False for G0 in backbones: if atoms.is_isomorph(G0): isomorph = True break if not isomorph: backbones += [atoms] # The backbone is saturated, do not enumerate if hcnt == hmax: hatoms = hydrogenate(atoms, remaining_bonds) molecules += [hatoms] continue # Enumerate hydrogens across backbone for bins in bin_hydrogen(hcnt, n): if not np.all(bins <= remaining_bonds): continue hatoms = hydrogenate(atoms, bins) isomorph = False for G0 in molecules: if hatoms.is_isomorph(G0): isomorph = True break if not isomorph: molecules += [hatoms] return molecules
def add_adsorbate_onto_surface(self, adsorbate, surface, bond_indices): ''' There are a lot of small details that need to be considered when adding an adsorbate onto a surface. This function will take care of those details for you. Args: adsorbate: An `ase.Atoms` object of the adsorbate surface: An `ase.Atoms` object of the surface bond_indices: A list of integers indicating the indices of the binding atoms of the adsorbate Sets these values: adsorbed_surface_atoms: An `ase graphic Atoms` object containing the adsorbate and surface. The bulk atoms will be tagged with `0`; the surface atoms will be tagged with `1`, and the the adsorbate atoms will be tagged with `2` or above. adsorbed_surface_sampling_strs: String specifying the sample, [index]/[total] of reasonable adsorbed surfaces ''' # convert surface atoms into graphic atoms object surface_gratoms = catkit.Gratoms(surface) surface_atom_indices = [ i for i, atom in enumerate(surface) if atom.tag == 1 ] surface_gratoms.set_surface_atoms(surface_atom_indices) surface_gratoms.pbc = np.array([True, True, False]) # set up the adsorbate into graphic atoms object # with its connectivity matrix adsorbate_gratoms = self.convert_adsorbate_atoms_to_gratoms( adsorbate, bond_indices) # generate all possible adsorption configurations on that surface. # The "bonds" argument automatically take care of mono vs. # bidentate adsorption configuration. builder = catkit.gen.adsorption.Builder(surface_gratoms) with warnings.catch_warnings( ): # suppress potential square root warnings warnings.simplefilter('ignore') adsorbed_surfaces = builder.add_adsorbate(adsorbate_gratoms, bonds=bond_indices, index=-1) # Filter out unreasonable structures. # Then pick one from the reasonable configurations list as an output. reasonable_adsorbed_surfaces = [ surface for surface in adsorbed_surfaces if self.is_config_reasonable(surface) ] self.adsorbed_surface_atoms = [] self.adsorbed_surface_sampling_strs = [] if self.enumerate_all_configs: self.num_configs = len(reasonable_adsorbed_surfaces) for ind, reasonable_config in enumerate( reasonable_adsorbed_surfaces): self.adsorbed_surface_atoms.append(reasonable_config) self.adsorbed_surface_sampling_strs.append( str(ind) + '/' + str(len(reasonable_adsorbed_surfaces))) else: self.num_configs = 1 reasonable_adsorbed_surface_index = np.random.choice( len(reasonable_adsorbed_surfaces)) self.adsorbed_surface_atoms.append( reasonable_adsorbed_surfaces[reasonable_adsorbed_surface_index] ) self.adsorbed_surface_sampling_strs.append( str(reasonable_adsorbed_surface_index) + '/' + str(len(reasonable_adsorbed_surfaces)))