Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
    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
Esempio n. 5
0
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
Esempio n. 6
0
    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)))