Source code for catkit.gen.analysis.classifier

from catkit import Gratoms
from .. import utils
import networkx as nx
import numpy as np
import ase


[docs]class Classifier(): """Class for classification of various aspects of an an atomic unit cell. Currently, a tool for classification of adsorbates on surface environments and the active sites they rest on. """ def __init__(self, atoms): """Return unique coordinate values of a given atoms object for a specified axis. Parameters ---------- atoms : atoms object """ self.atoms = atoms self.ads_atoms = None self.slab_atoms = None self.surface_atoms = None
[docs] def id_slab_atoms( self, classifier='trivial', tag=False, rtol=1e-3): """Return the indices of the slab atoms using select characterization techniques. Parameters ---------- classifier : str Classification technique to identify slab atoms. 'trivial': Slab atoms assumed to have atomic number == 13 or >= 21. tag : bool Return adsorbate atoms with tags of 2. rtol : float Relative cutoff distance for tagging layers. Returns ------- slab_atoms : ndarray (n,) Index of slab atoms found. """ atoms = self.atoms if classifier == 'trivial': slab_atoms = np.where((atoms.numbers == 13) | (atoms.numbers >= 21))[0] if tag: zpos = np.sort(atoms.positions[slab_atoms][:, -1]) new_tags = np.zeros_like(zpos, dtype=int) tag = 1 for i, z in enumerate(zpos): if new_tags[i] != 0: continue layer = np.isclose(z, zpos, rtol=rtol) new_tags[layer] = tag tag += 1 tags = self.atoms.get_tags() tags[slab_atoms] = new_tags[::-1] self.atoms.set_tags(tags) self.slab_atoms = slab_atoms return slab_atoms
[docs] def id_adsorbate_atoms(self, classifier='trivial', tag=False): """Identify adsorbed atoms in a given atoms object. Parameters ---------- classifier : str Classification technique to identify adsorbate atoms. 'trivial': Adsorbate atoms assumed to have atomic number != 13 or < 21. tag : bool Return adsorbate atoms with tags of -2. Returns ------- ads_atoms : ndarray (n,) Index of adsorbate atoms found. """ atoms = self.atoms if classifier == 'trivial': ads_atoms = np.where((atoms.numbers != 13) & (atoms.numbers < 21))[0] if tag: tags = self.atoms.get_tags() tags[ads_atoms] = -2 self.atoms.set_tags(tags) self.ads_atoms = ads_atoms return ads_atoms
[docs] def id_surface_atoms(self, classifier='voronoi_sweep'): """Identify surface atoms of an atoms object. This will require that adsorbate atoms have already been identified. Parameters ---------- classifier : str Classification technique to identify surface atoms. 'voronoi_sweep': Create a sweep of proxy atoms above surface. Surface atoms are those which are most frequent neighbors of the sweep. Returns ------- surface_atoms : ndarray (n,) Index of the surface atoms in the object. """ atoms = self.atoms.copy() # Remove adsorbates before analysis ads_atoms = self.ads_atoms if ads_atoms is None: ads_atoms = self.id_adsorbate_atoms() del atoms[ads_atoms] if classifier == 'voronoi_sweep': spos = atoms.get_scaled_positions() zmax = np.max(spos[:, -1]) # Create a distribution of points to screen with # 2.5 angstrom defines the absolute separation dvec = (np.linalg.norm(atoms.cell[:-1], axis=1) / 2.5) ** -1 xy = np.mgrid[0:1:dvec[0], 0:1:dvec[1]].reshape(2, -1) z = np.ones_like(xy[0]) * zmax xyz = np.vstack((xy, z)).T screen = np.dot(xyz, atoms.cell) n = len(atoms) m = len(screen) ind = np.arange(n, n + m) slab_atoms = np.arange(n) satoms = [] # 2 - 3 Angstroms seems to work for a large range of indices. for k in np.linspace(2, 3, 10): wall = screen.copy() + [0, 0, k] atm = ase.Atoms(['X'] * m, positions=wall) test_atoms = atoms + atm con = utils.get_voronoi_neighbors(test_atoms) surf_atoms = np.where(con[ind].sum(axis=0)[slab_atoms])[0] satoms += [surf_atoms] len_surf_atoms = [len(_) for _ in satoms] uni, ind, cnt = np.unique( len_surf_atoms, return_counts=True, return_index=True) max_cnt = np.argmax(cnt) surf_atoms = satoms[ind[max_cnt]] self.surface_atoms = surf_atoms return surf_atoms
[docs] def id_adsorbates(self, classifier='radial', return_atoms=False): """Return a list of Gratoms objects for each adsorbate classified on a surface. Requires classification of adsorbate atoms. Parameters ---------- classifier : str Classification technique to identify individual adsorbates. 'radial': Use standard cutoff distances to identify neighboring atoms. return_atoms : bool Return Gratoms objects instead of adsorbate indices. Returns ------- adsorbates : list (n,) Adsorbate indices of adsorbates in unit cell. """ atoms = self.atoms.copy() # Remove the slab atoms ads_atoms = self.ads_atoms if ads_atoms is None: ads_atoms = self.id_adsorbate_atoms() if classifier == 'radial': con = utils.get_cutoff_neighbors(atoms) ads_con = con[ads_atoms][:, ads_atoms] G = nx.Graph() G.add_nodes_from(ads_atoms) edges = utils.connectivity_to_edges(ads_con, indices=ads_atoms) G.add_weighted_edges_from(edges, weight='bonds') SG = nx.connected_component_subgraphs(G) adsorbates = [] for sg in SG: nodes = list(sg.nodes) if return_atoms: edges = list(sg.edges) ads = Gratoms( numbers=atoms.numbers[nodes], positions=atoms.positions[nodes], edges=edges) ads.center(vacuum=5) else: ads = nodes adsorbates += [ads] return adsorbates