def get_sites_dict(self, atoms, excludes=[]): ''' get the surface site for adsorption 1) for OER_pourbaix, ['O', 'N'] sites are generally excluded. 2) for OER_site metal: Ti, La -> OH -> O -> OOH -> O2 O: O -> OOH -> O2 -> OH H: OH -> O -> OOH -> O2 Return sites: dict e.g. {'Pt': [0.0, 0.0, 5.0]} ''' from scipy.spatial.distance import squareform, pdist slabs = AseAtomsAdaptor.get_structure(atoms) asf_slabs = AdsorbateSiteFinder(slabs) ads_sites = asf_slabs.find_adsorption_sites(distance=0.0) sites = {} count = 0 # view(atoms) for pos in ads_sites['ontop']: poss = atoms.get_positions() poss = np.append(poss, pos).reshape(-1, 3) ind = np.argsort(squareform(pdist(poss))[-1])[1] - 1 # print(ind) symbol = atoms[ind].symbol if symbol in excludes: continue sites['site-%s-%s' % (count, symbol)] = pos + np.array([0, 0, 2.0]) return sites
def adsorbedSurface(self): """ Adds adsorbates to bare surface """ from pymatgen.analysis.adsorption import AdsorbateSiteFinder, get_rot slab = copy.deepcopy(self.bareSurface()) asf = AdsorbateSiteFinder(slab) b_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['bridge'] o_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['ontop'] h_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['hollow'] for ads, sites in self.adsorbates.items(): a = molDict[ads] for (kind, num) in map(alphaNumSplit, sites): asf = AdsorbateSiteFinder(slab) if kind == 'B': slab = asf.add_adsorbate(a, b_sites[int(num)]) elif kind == 'O': slab = asf.add_adsorbate(a, o_sites[int(num)]) elif kind == 'H': slab = asf.add_adsorbate(a, h_sites[int(num)]) else: raise ValueError, "Bad site character in " + str(sites) return slab
def setUp(self): self.structure = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.5), ["Ni"], [[0, 0, 0]]) lattice = Lattice.cubic(3.010) frac_coords = [ [0.00000, 0.00000, 0.00000], [0.00000, 0.50000, 0.50000], [0.50000, 0.00000, 0.50000], [0.50000, 0.50000, 0.00000], [0.50000, 0.00000, 0.00000], [0.50000, 0.50000, 0.50000], [0.00000, 0.00000, 0.50000], [0.00000, 0.50000, 0.00000], ] species = ["Mg", "Mg", "Mg", "Mg", "O", "O", "O", "O"] self.MgO = Structure(lattice, species, frac_coords) slabs = generate_all_slabs( self.structure, max_index=2, min_slab_size=6.0, min_vacuum_size=15.0, max_normal_search=1, center_slab=True, ) self.slab_dict = {"".join([str(i) for i in slab.miller_index]): slab for slab in slabs} self.asf_211 = AdsorbateSiteFinder(self.slab_dict["211"]) self.asf_100 = AdsorbateSiteFinder(self.slab_dict["100"]) self.asf_111 = AdsorbateSiteFinder(self.slab_dict["111"]) self.asf_110 = AdsorbateSiteFinder(self.slab_dict["110"]) self.asf_struct = AdsorbateSiteFinder(Structure.from_sites(self.slab_dict["111"].sites))
def freeze_center(slab): sf = AdsorbateSiteFinder(slab) surf_sites = sf.find_surface_sites_by_height(slab, height=5, bottom=True) sd_lst = [] for site in slab: if site in surf_sites: sd_lst.append([True, True, True]) else: sd_lst.append([False, False, False]) slab.add_site_property('selective_dynamics', sd_lst) return slab
def adsorptionSites(slab, **kwargs): """ :code:`AdsorptionSites` can help us visualize the position and tag of each adsorption sites, then we can determine where we want to put the adsorbates. :param slab: This is our slab, and we want to find how we can put the adsorbates on the slab. :type slab: aiida.orm.StructureData :param kwargs: * distance: the distance between adsorption site and the surface * symm_reduce: the symmetry reduce (default = 0.01) * near_reduce: the near reduce (default = 0.01) :returns: Dictionary contains the dictionary of adsorption sites. :rtype: aiida.orm.Dict object """ # the inspiration for this function was from pymatgen.analysis.adsorption.AdsorbateSiteFinder.plot_slab() # function, which is really intuitive way of showing the structure and the adsorption sites. # since this function does not create any useful data, so it doesn't be decorated with @calfunction decorator # get the structure and adsorption sites slab = slab.get_pymatgen_structure() # end of the conversion asf = AdsorbateSiteFinder(slab, selective_dynamics=False) if 'distance' in kwargs.keys(): distance = kwargs['distance'] else: distance = 1.2 if 'symm_reduce' in kwargs.keys(): symm_reduce = kwargs['symm_reduce'] else: symm_reduce = 0.01 if 'near_reduce' in kwargs.keys(): near_reduce = kwargs['near_reduce'] else: near_reduce = 0.01 adsorption_sites = asf.find_adsorption_sites(distance=distance, symm_reduce=symm_reduce, near_reduce=near_reduce) dictGenerator = Dict() dictGenerator.set_dict(adsorption_sites) return dictGenerator
def proc_adsorb(cryst, mol, data): if data['method'] == 1: asf_slab = AdsorbateSiteFinder(cryst) ads_sites = asf_slab.find_adsorption_sites() ads_structs = asf_slab.generate_adsorption_structures( mol, repeat=data['repeat']) for i in range(len(ads_structs)): ads_struct = ads_structs[i] try: miller_str = [str(j) for j in cryst.miller_index] except: miller_str = ['adsorb'] filename = '_'.join(miller_str) + '-' + str(i) + '.vasp' ads_struct.to(filename=filename, fmt='POSCAR') else: slabs = generate_all_slabs(cryst, max_index=data['max_index'], min_slab_size=data['min_slab'], min_vacuum_size=data['min_vacum'], lll_reduce=True) for slab in slabs: asf_slab = AdsorbateSiteFinder(slab) ads_sites = asf_slab.find_adsorption_sites() ads_structs = asf_slab.generate_adsorption_structures( mol, repeat=data['repeat']) for i in range(len(ads_structs)): ads_struct = ads_structs[i] miller_str = [str(j) for j in slab.miller_index] filename = 'adsorb' + '_'.join(miller_str) + '-' + str( i) + '.vasp' ads_struct.to(filename=filename, fmt='POSCAR')
def test_AdsorbateSiteFinder_find_adsorption_site_types(slab_name): ''' Verify that ASF finds the same exact site types (e.g., ontop, bride, hollow) ''' # Use pymatgen to find the sites structure = test_cases.get_slab_structure(slab_name) sites_dict = AdsorbateSiteFinder(structure).find_adsorption_sites( put_inside=True) site_types = sites_dict.keys() # Load the baseline sites slab_name_no_extension = slab_name.split('.')[0] file_name = REGRESSION_BASELINES_LOCATION + 'adsorption_sites_for_' + slab_name_no_extension + '.pkl' with open(file_name, 'rb') as file_handle: expected_sites_dict = pickle.load(file_handle) expected_site_types = expected_sites_dict.keys() assert site_types == expected_site_types
def enumerate_adsorption_sites(atoms, mpid, millers, shift, top): ''' A wrapper for pymatgen to get all of the adsorption sites of a slab. Arg: atoms The slab where you are trying to find adsorption sites in `ase.Atoms` format mpid String indicating the the Materials Project ID number of the bulk that was selected. millers A 3-tuple of integers indicating the Miller indices of the chosen surface shift The y-direction shift used to determination the termination/cutoff of the surface top A Boolean indicating whether the chose surfaces was the top or the bottom of the originally enumerated surface. Output: sites A `numpy.ndarray` object that contains the x-y-z coordinates of the adsorptions sites ''' sites = CACHE.get((mpid, millers, shift, top)) if sites is None: struct = AseAtomsAdaptor.get_structure(atoms) sites_dict = AdsorbateSiteFinder(struct).find_adsorption_sites( put_inside=True) sites = sites_dict['all'] CACHE.set((mpid, millers, shift, top), sites) return sites
def get_shifts_based_on_adsorbate_sites(self, tolerance: float = 0.1 ) -> List[Tuple[float, float]]: """ Computes possible in-plane shifts based on an adsorbate site algorithm Args: tolerance: tolerance for "uniqueness" for shifts in Cartesian unit This is usually Angstroms. """ substrate = self.substrate film = self.film substrate_surface_sites = np.dot( list( chain.from_iterable( AdsorbateSiteFinder( substrate).find_adsorption_sites().values())), substrate.lattice.inv_matrix, ) # Film gets forced into substrate lattice anyways, so shifts can be computed in fractional coords film_surface_sites = np.dot( list( chain.from_iterable( AdsorbateSiteFinder( film).find_adsorption_sites().values())), film.lattice.inv_matrix, ) pos_shift = np.array([ np.add(np.multiply(-1, film_shift), sub_shift) for film_shift, sub_shift in product( film_surface_sites, substrate_surface_sites) ]) def _base_round(x, base=0.05): return base * (np.array(x) / base).round() # Round shifts to tolerance pos_shift[:, 0] = _base_round(pos_shift[:, 0], base=tolerance / substrate.lattice.a) pos_shift[:, 1] = _base_round(pos_shift[:, 1], base=tolerance / substrate.lattice.b) # C-axis is not useful pos_shift = pos_shift[:, 0:2] return list(np.unique(pos_shift, axis=0))
def adsorbedSurface(surfpckl, facet, adsorbates): """ Adds adsorbates to bare surface """ magmomInit = 3 magElems = ['Fe', 'Mn', 'Cr', 'Co', 'Ni'] initASE = pickle.loads(surfpckl) constrnts = initASE.constraints tags = initASE.get_tags().tolist() slab = makePMGSlabFromASE(initASE, facet) asf = AdsorbateSiteFinder(slab) b_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['bridge'] o_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['ontop'] h_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['hollow'] for ads, sites in adsorbates.items(): a = gas.molDict[ads] for (kind, num) in [printParse.alphaNumSplit(x) for x in sites]: asf = AdsorbateSiteFinder(slab) if kind == 'B': slab = asf.add_adsorbate(a, b_sites[int(num) - 1]) elif kind == 'O': slab = asf.add_adsorbate(a, o_sites[int(num) - 1]) elif kind == 'H': slab = asf.add_adsorbate(a, h_sites[int(num) - 1]) else: raise ValueError, "Bad site character in " + str(sites) aseSlab = AseAtomsAdaptor.get_atoms(slab) magmoms = [ magmomInit if (magmomInit and e in magElems) else 0 for e in aseSlab.get_chemical_symbols() ] newtags = np.zeros(len(aseSlab)) for i, t in enumerate(tags): newtags[i] = t aseSlab.set_tags(newtags) aseSlab.set_constraint(constrnts) aseSlab.set_pbc([1, 1, 1]) aseSlab.set_initial_magnetic_moments(magmoms) aseSlab.wrap() return aseSlab
def test_from_bulk_and_miller(self): # Standard site finding asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 1)) sites = asf.find_adsorption_sites() self.assertEqual(len(sites["hollow"]), 2) self.assertEqual(len(sites["bridge"]), 1) self.assertEqual(len(sites["ontop"]), 1) self.assertEqual(len(sites["all"]), 4) asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 0, 0)) sites = asf.find_adsorption_sites() self.assertEqual(len(sites["all"]), 3) self.assertEqual(len(sites["bridge"]), 2) asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 0), undercoord_threshold=0.1) self.assertEqual(len(asf.surface_sites), 1) # Subsurface site finding asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 1)) sites = asf.find_adsorption_sites(positions=["ontop", "subsurface", "bridge"]) self.assertEqual(len(sites["all"]), 5) self.assertEqual(len(sites["subsurface"]), 3)
def test_to_create_adsorption_sites(slab_name): structure = test_cases.get_slab_structure(slab_name) sites_dict = AdsorbateSiteFinder(structure).find_adsorption_sites( put_inside=True) slab_name_no_extension = slab_name.split('.')[0] file_name = REGRESSION_BASELINES_LOCATION + 'adsorption_sites_for_' + slab_name_no_extension + '.pkl' with open(file_name, 'wb') as file_handle: pickle.dump(sites_dict, file_handle) assert True
def save_site_combos(slab, adsorbate, path, coverage, height=0.9, dist_reduce=2.1, symm_reduce=False, ref_species=None,no_bridge=False): coord_combos = create_coord_combos(slab, coverage, ref_species=ref_species, height=height, dist_reduce=dist_reduce) if symm_reduce: coord_combos = combo_symm_reduce(slab,coord_combos) if no_bridge: coord_combos = [name for name in coord_combos if 'bridge' not in list(name.keys())[0]] print(len(coord_combos)) for combo in coord_combos: fin_slab = slab.copy() sites = list(combo.values())[0] dirname = list(combo.keys())[0] #sf = AdsorbateSiteFinder(fin_slab) for site in sites: sf = AdsorbateSiteFinder(fin_slab) fin_slab = sf.add_adsorbate(adsorbate,site,reorient=False) fin_slab = fin_slab.get_sorted_structure() #appends the specified adsorbate to the slab in the selected sites if the distance between the sites is more than a specified number of angstroms if not os.path.exists('%s\\%s' %(path, dirname)): os.makedirs('%s\\%s' %(path, dirname)) fin_slab.to('poscar','%s\\%s\\POSCAR'%(path, dirname))
def determine_coverage(slab, coverage, ref_species=None, height=2.1): sf = AdsorbateSiteFinder(slab) surf_sites = sf.find_surface_sites_by_height(slab, height=height) if ref_species == None: n_surf_atoms = len(surf_sites) else: n_surf_atoms = 0 for site in surf_sites: if site.species_string == ref_species: n_surf_atoms += 1 # print(n_surf_atoms) n_sites_init = n_surf_atoms*coverage n_sites = np.round(n_sites_init) if n_sites != n_sites_init: actual_coverage = n_sites/n_surf_atoms print('Warning: the number of sites used does not exactly match the specified coverage, the actual coverage is %s' %actual_coverage) else: actual_coverage = coverage n_sites = int(n_sites) return n_sites, actual_coverage
def adsorbedSurface(baresurface, adsorbates): """ Adds adsorbates to bare surface """ slab = baresurface.copy() asf = AdsorbateSiteFinder(slab) b_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['bridge'] o_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['ontop'] h_sites = asf.find_adsorption_sites(distance=1.1, symm_reduce=0)['hollow'] for ads, sites in adsorbates.items(): a = molDict[ads] for (kind, num) in [alphaNumSplit(x) for x in sites]: asf = AdsorbateSiteFinder(slab) if kind == 'B': slab = asf.add_adsorbate(a, b_sites[int(num) - 1]) elif kind == 'O': slab = asf.add_adsorbate(a, o_sites[int(num) - 1]) elif kind == 'H': slab = asf.add_adsorbate(a, h_sites[int(num) - 1]) else: raise ValueError, "Bad site character in " + str(sites) return slab
def find_adsorption_sites(atoms): ''' A wrapper for pymatgen to get all of the adsorption sites of a slab. Arg: atoms The slab where you are trying to find adsorption sites in `ase.Atoms` format Output: sites A `numpy.ndarray` object that contains the x-y-z coordinates of the adsorptions sites ''' struct = AseAtomsAdaptor.get_structure(atoms) sites_dict = AdsorbateSiteFinder(struct).find_adsorption_sites( put_inside=True) sites = sites_dict['all'] return sites
def test_generate_substitution_structures(self): # Test this for a low miller index halite structure slabs = generate_all_slabs(self.MgO, 1, 10, 10, center_slab=True, max_normal_search=1) for slab in slabs: adsgen = AdsorbateSiteFinder(slab) adslabs = adsgen.generate_substitution_structures("Ni") # There should be 2 configs (sub O and sub # Mg) for (110) and (100), 1 for (111) if tuple(slab.miller_index) != (1, 1, 1): self.assertEqual(len(adslabs), 2) else: self.assertEqual(len(adslabs), 1) # Test out whether it can correctly dope both # sides. Avoid (111) because it is not symmetric if tuple(slab.miller_index) != (1, 1, 1): adslabs = adsgen.generate_substitution_structures("Ni", sub_both_sides=True, target_species=["Mg"]) # Test if default parameters dope the surface site for i, site in enumerate(adslabs[0]): if adsgen.slab[i].surface_properties == "surface" and site.species_string == "Mg": print( adslabs[0][i].surface_properties, adsgen.slab[i].surface_properties, ) self.assertTrue(adslabs[0][i].surface_properties == "substitute") self.assertTrue(adslabs[0].is_symmetric()) # Correctly dope the target species self.assertEqual( adslabs[0].composition.as_dict()["Mg"], slab.composition.as_dict()["Mg"] - 2, ) # There should be one config (sub Mg) self.assertEqual(len(adslabs), 1)
def test_AdsorbateSiteFinder_find_adsorption_site_locations(slab_name): ''' Verify that ASF finds the same cartesion site locations for each site type ''' # Use pymatgen to find the sites structure = test_cases.get_slab_structure(slab_name) sites_dict = AdsorbateSiteFinder(structure).find_adsorption_sites( put_inside=True) # Load the baseline sites slab_name_no_extension = slab_name.split('.')[0] file_name = REGRESSION_BASELINES_LOCATION + 'adsorption_sites_for_' + slab_name_no_extension + '.pkl' with open(file_name, 'rb') as file_handle: expected_sites_dict = pickle.load(file_handle) # The output we're checking is a dictionary of lists of numpy arrays. # Let's check convert each list to an array and then check each array one at a time. for site_type, expected_sites in expected_sites_dict.items(): sites = np.array(sites_dict[site_type]) expected_sites = np.array(expected_sites) npt.assert_allclose(sites, expected_sites, rtol=1e-5, atol=1e-7)
def generate_site_lst(slab, height=0.9): sf = AdsorbateSiteFinder(slab) #creates an AdsorbateSiteFinder object from pymatgen.analysis.adsorption #to identify the possible sites for adsorbates slab_corrected_surf = sf.assign_site_properties(slab, height=height) sf = AdsorbateSiteFinder(slab_corrected_surf) dict_ads_site = sf.find_adsorption_sites(distance=1.7, symm_reduce=False) #a dictionary with all possible adsorption sites on the surface site_lst = [] for site_type in dict_ads_site.keys(): i = 0 if site_type != 'all': # if site_type != 'bridge': for site in dict_ads_site[site_type]: i += 1 site_name = site_type + '_' + str(i) site_lst.append(site_name) #a list of all the sites with somewhat intuitive names, like hollow_1, ontop_2, or bridge_3 return site_lst, dict_ads_site
def plot_slab(slab, ax, scale=0.8, repeat=3, window=1, decay=0.2): """ Function that helps visualize the slab in a 2-D plot, for convenient viewing of output of AdsorbateSiteFinder. Args: slab (slab): Slab object to be visualized ax (axes): matplotlib axes with which to visualize scale (float): radius scaling for sites repeat (int): number of repeating unit cells to visualize window (float): window for setting the axes limits, is essentially a fraction of the unit cell limits decay (float): how the alpha-value decays along the z-axis """ orig_slab = slab.copy() slab = reorient_z(slab) orig_cell = slab.lattice.matrix.copy() if repeat: slab.make_supercell([repeat, repeat, 1]) coords = np.array(sorted(slab.cart_coords, key=lambda x: x[2])) sites = sorted(slab.sites, key=lambda x: x.coords[2]) alphas = 1 - decay * (np.max(coords[:, 2]) - coords[:, 2]) alphas = alphas.clip(min=0) corner = [0, 0, cart_to_frac(slab.lattice, coords[-1])[-1]] corner = frac_to_cart(slab.lattice, corner)[:2] verts = orig_cell[:2, :2] lattsum = verts[0] + verts[1] # Draw circles at sites and stack them accordingly for n, coord in enumerate(coords): r = sites[n].specie.atomic_radius * scale ax.add_patch( patches.Circle(coord[:2] - lattsum * (repeat // 2), r, color='w', zorder=2 * n)) color = color_dict[sites[n].species_string] ax.add_patch( patches.Circle(coord[:2] - lattsum * (repeat // 2), r, facecolor=color, alpha=alphas[n], edgecolor='k', lw=0.3, zorder=2 * n + 1)) # Adsorption sites asf = AdsorbateSiteFinder(orig_slab) ads_sites = asf.find_adsorption_sites(symm_reduce=0)['all'] sop = get_rot(orig_slab) ads_sites = [sop.operate(ads_site)[:2].tolist() for ads_site in ads_sites] b_sites = asf.find_adsorption_sites(symm_reduce=0)['bridge'] b_sites = [(sop.operate(ads_site)[:2].tolist(), 'B' + str(i)) for i, ads_site in enumerate(b_sites)] o_sites = asf.find_adsorption_sites(symm_reduce=0)['ontop'] o_sites = [(sop.operate(ads_site)[:2].tolist(), 'O' + str(i)) for i, ads_site in enumerate(o_sites)] h_sites = asf.find_adsorption_sites(symm_reduce=0)['hollow'] h_sites = [(sop.operate(ads_site)[:2].tolist(), 'H' + str(i)) for i, ads_site in enumerate(h_sites)] for site in b_sites + o_sites + h_sites: ax.text(site[0][0], site[0][1], site[1], zorder=10000, ha='center', va='center') # Draw unit cell verts = np.insert(verts, 1, lattsum, axis=0).tolist() verts += [[0., 0.]] verts = [[0., 0.]] + verts codes = [ Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY ] verts = [(np.array(vert) + corner).tolist() for vert in verts] path = Path(verts, codes) patch = patches.PathPatch(path, facecolor='none', lw=2, alpha=0.5, zorder=2 * n + 2) ax.add_patch(patch) ax.set_aspect("equal") center = corner + lattsum / 2. extent = np.max(lattsum) lim_array = [center - extent * window, center + extent * window] x_lim = [ele[0] for ele in lim_array] y_lim = [ele[1] for ele in lim_array] ax.set_xlim(x_lim) ax.set_ylim(y_lim) return ax
def enumerate_ads_chains(self, adsorbate, bond_length, path_length, percent_error=10, include_cycles=True, mode='exact', symmetry_tol=0.01): bond_length = float(bond_length) asf = AdsorbateSiteFinder(self.blank_slab_pym) ads_sites = asf.find_adsorption_sites() site_list = [] for stype in ads_sites: if stype != 'all': site_list.extend([s for s in ads_sites[stype]]) duplications = self.duplications unit_cell = self.minimal_unit_cell repeat_unit_cell = self.blank_slab_ase.get_cell().T plane_string = ''.join(map(str, self.plane)) dists = [] for pair in itertools.combinations(site_list, 2): s0, s1 = pair s0 = np.dot(np.linalg.inv(unit_cell), s0) s1 = np.dot(np.linalg.inv(unit_cell), s1) fdist, sym = PBC3DF_sym(s0, s1) dist = np.round(np.linalg.norm(np.dot(unit_cell, fdist)), 3) dists.append((dist, pair)) dists = [ d for d in dists if abs(d[0] - bond_length) / bond_length * 100 < percent_error ] duplications = [[j for j in range(i)] for i in duplications] translation_vecs = list( itertools.product(duplications[0], duplications[1], duplications[2])) path_dict = {} ptype_counter = 0 for d in dists: ptype_counter += 1 fingerprints = dict((s, dict((k, []) for k in range(1, 231))) for s in range(path_length + 1)) sg_counts = dict((k, 0) for k in range(1, 231)) lattice = [] for s in d[1]: for trans in translation_vecs: trans = np.asarray(trans) cart_trans = np.dot(unit_cell, trans) lattice.append(s + cart_trans) G = nx.Graph() for i in range(len(lattice)): G.add_node(i, coords=lattice[i]) for i in range(len(lattice)): s0 = lattice[i] for j in range(i + 1, len(lattice)): s1 = lattice[j] dist = np.linalg.norm(s0 - s1) if np.round(dist, 3) == d[0]: G.add_edge(i, j, length=dist) def neighborhood(G, node, n): path_lengths = nx.single_source_dijkstra_path_length(G, node) return [ node for node, length in path_lengths.items() if length == n ] all_paths = [] all_cycles = [] used = [] for i in G.nodes(): used.append(i) nborhood = [ neighborhood(G, i, n) for n in range(path_length + 1) ] nborhood = [nbor for nbors in nborhood for nbor in nbors] for j in nborhood: if j not in used: paths = list( nx.all_simple_paths(G, source=i, target=j, cutoff=path_length)) for p in paths: if mode == 'leq': if len(p) <= path_length: all_paths.append(p) elif mode == 'exact': if len(p) == path_length: all_paths.append(p) if include_cycles: G = G.to_directed() if mode == 'leq': cycles = [ cy for cy in nx.simple_cycles(G) if len(cy) <= path_length ] elif mode == 'exact': cycles = [ cy for cy in nx.simple_cycles(G) if len(cy) == path_length ] used = [] for cy in cycles: if len(cy) > 2: cy_set = set(sorted(cy)) if cy_set not in used: all_cycles.append(cy) used.append(cy_set) all_paths = all_paths + cycles for path in all_paths: fp_dict = fingerprints[len(path)] path_coords = [ n[1]['coords'] for n in G.nodes(data=True) if n[0] in path ] adsorbate_combinations = itertools.product(adsorbate, repeat=len(path)) for ads_comb in adsorbate_combinations: adsonly_positions = [] slab_coords = [(sl.symbol, sl.position) for sl in self.blank_slab_ase] for p, a in zip(path_coords, ads_comb): ads, shift_ind = a elems = [] positions = [] for atom in ads: elems.append(atom.symbol) positions.append(atom.position) elems = np.asarray(elems) positions = np.asarray(positions) trans = np.dot(unit_cell, np.asarray(s)) positions -= positions[shift_ind] positions += p for e, c in zip(elems, positions): slab_coords.append((e, c)) adsonly_positions.append(c) advance, index, sgs, sgn, dists, atoms, formula = redundancy_check( slab_coords, adsonly_positions, fp_dict, repeat_unit_cell, symmetry_tol) if advance: sg_counts[sgn] += 1 fp_dict[sgn].append(dists) path_dict[formula + '_' + plane_string + '_' + str(len(path)) + '_' + str(ptype_counter) + '_' + sgs + '_' + index] = atoms self.path_configuration_dict = path_dict
def enumerate_ads_config(self, adsorbate, loading=1, name='all', interaction_dist=2.0, symmetry_tol=0.01): asf = AdsorbateSiteFinder(self.blank_slab_pym) ads_sites = asf.find_adsorption_sites(distance=interaction_dist) duplications = self.duplications unit_cell = self.minimal_unit_cell repeat_unit_cell = self.blank_slab_ase.get_cell().T plane_string = ''.join(map(str, self.plane)) duplications = [[j for j in range(i)] for i in duplications] translation_vecs = list( itertools.product(duplications[0], duplications[1], duplications[2])) all_combinations = [ s for s in itertools.combinations(translation_vecs, loading) ] site_list = [] if name == 'all': for stype in ads_sites: if stype != 'all': site_list.extend([s for s in ads_sites[stype]]) else: site_list.extend([s for s in ads_sites[name]]) ads_dict = {} atype_counter = 0 fingerprints = dict((len(s), dict((k, []) for k in range(1, 231))) for s in all_combinations) sg_counts = dict((k, 0) for k in range(1, 231)) for pos in site_list: atype_counter += 1 for subset in all_combinations: loading = len(subset) fp_dict = fingerprints[loading] adsorbate_combinations = itertools.product(adsorbate, repeat=loading) for ads_comb in adsorbate_combinations: adsonly_positions = [] slab_coords = [(sl.symbol, sl.position) for sl in self.blank_slab_ase] for s, a in zip(subset, ads_comb): ads, shift_ind = a elems = [] positions = [] for atom in ads: elems.append(atom.symbol) positions.append(atom.position) elems = np.asarray(elems) positions = np.asarray(positions) trans = np.dot(unit_cell, np.asarray(s)) positions -= positions[shift_ind] positions += pos positions += trans for e, c in zip(elems, positions): slab_coords.append((e, c)) adsonly_positions.append(c) advance, index, sgs, sgn, dists, atoms, formula = redundancy_check( slab_coords, adsonly_positions, fp_dict, repeat_unit_cell, symmetry_tol) if advance: sg_counts[sgn] += 1 fp_dict[sgn].append(dists) ads_dict[formula + '_' + plane_string + '_' + str(loading) + '_' + name + str(atype_counter) + '_' + sgs + '_' + index] = atoms self.adsorbate_configuration_dict = ads_dict
def test_init(self): AdsorbateSiteFinder(self.slab_dict["100"]) AdsorbateSiteFinder(self.slab_dict["111"])
def fix_absorbed(self, need_miller_index, mole, num, selective_dynamic, min_slab_size_1=8.0, min_vacuum_size_1=15, judge='fuchdi', appendage=""): from pymatgen import Structure, Lattice, MPRester, Molecule import pymatgen.core.structure import pymatgen.core.sites from pymatgen.analysis.adsorption import AdsorbateSiteFinder, reorient_z, plot_slab from pymatgen.core.surface import generate_all_slabs from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from matplotlib import pyplot as plt from pymatgen.ext.matproj import MPRester from pymatgen.io.vasp.inputs import Poscar from pymatgen.io.vasp.sets import MVLSlabSet from pymatgen.io.cif import CifWriter import os import shutil from openbabel import openbabel from pymatgen.core.surface import Slab, SlabGenerator, generate_all_slabs, Structure, Lattice, ReconstructionGenerator mp_id = self.mp_id os.chdir(r"F:\VASP practical\Input") print(os.getcwd()) # Note that you must provide your own API Key, which can # be accessed via the Dashboard at materialsproject.org mpr = MPRester() struct = mpr.get_structure_by_material_id(mp_id) struct = SpacegroupAnalyzer( struct).get_conventional_standard_structure() # fcc_ni = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.5), ["Ni", "Ni"], # [[0, 0, 0], [0.5, 0.5, 0.5]]) slab = SlabGenerator(struct, miller_index=need_miller_index, min_slab_size=min_slab_size_1, min_vacuum_size=min_vacuum_size_1, center_slab=True) for n, slabs in enumerate(slab.get_slabs()): if str(n) in str(num): slabs_bak = slabs.copy() #可能的晶面 slabs.make_supercell(self.supercell) print(n) #晶胞扩充 asf_ni_111 = AdsorbateSiteFinder( slabs, selective_dynamics=selective_dynamic) ads_sites = asf_ni_111.find_adsorption_sites() # print(ads_sites) assert len(ads_sites) == 4 fig0 = plt.figure() ax = fig0.add_subplot(111) plot_slab(slabs, ax, adsorption_sites=False) fig1 = plt.figure() ax = fig1.add_subplot(111) os.chdir(r"D:\Desktop\VASP practical\Cif library") print(os.getcwd()) obConversion = openbabel.OBConversion() obConversion.SetInAndOutFormats("pdb", "gjf") mol = openbabel.OBMol() print(mol) c = obConversion.ReadFile(mol, "CH3OH.pdb") obConversion.WriteFile(mol, "CH3OH.pdb" + '1.gjf') adsorbate = Molecule.from_file("CH3OH.pdb" + '.gjf') os.chdir(r"F:\VASP practical\Input") print(os.getcwd()) print(adsorbate.sites) ads_structs = asf_ni_111.add_adsorbate( adsorbate, (20, 20, 20), translate=False, ) # ads_structs = asf_ni_111.generate_adsorption_structures(adsorbate, # repeat=[1, 1, 1]) # A = Poscar(ads_structs[0]) A = Poscar(reorient_z(ads_structs)) #将切面转换为Poscar open('POSCAR', 'w').write(str(A)) p = Poscar.from_file('POSCAR') # w = CifWriter(A.struct) # w.write_file('mystructure.cif') path = r'F:\VASP practical\Input\POSCAR' # 文件路径 if os.path.exists(path): # 如果文件存在 # 删除文件,可使用以下两种方法。 os.remove(path) #os.unlink(path) else: print('no such file:%s' % my_file) # 则返回文件不存在 # w = CifWriter(A.struct) # w.write_file('mystructure.cif') relax = p.structure #将Poscar 转换为结构信息 custom_settings = {"NPAR": 4} # 用户的INCAR 设置 relaxs = MVLSlabSet(relax, user_incar_settings=custom_settings) # Vasp输入文件生成器 dire = str(mp_id) + str(selective_dynamic) + str(mole) + str( need_miller_index).replace(" ", "") + str(n) # print (relax) relaxs.write_input(dire) os.chdir("./" + dire) print(os.getcwd()) fig0.savefig('slab.png', bbox_inches='tight', transparent=True, dpi=600, format='png') plot_slab(ads_structs, ax, adsorption_sites=False, decay=0.09) fig1.savefig('slab_adsobate.png', bbox_inches='tight', transparent=True, dpi=600, format='png') #定义一个更改当前目录的变量 dire2 = './vaspstd_sub' #确立脚本名称 shutil.copy(r"C:\Users\41958\.spyder-py3\vaspstd_sub", dire2) eb = appendage #添加其他INCAR参数 with open('INCAR', 'r') as f1: lines = f1.readlines() with open('INCAR', 'w') as f2: for line in lines: if judge in line: continue f2.write(line) with open('INCAR', 'a') as f3: f3.write(eb) # open('POSCAR001', 'w').write(str(Poscar(reorient_z(ads_structs[0])))) os.chdir(r"D:\Desktop\VASP practical\workdir") print(os.getcwd()) print('finished') # my_lattace = Lattace('mp-698074')#半水石膏 # # my_lattace.phase_out()#生成晶胞优化的输入文件 # go = my_lattace.phase_sol(66,judge='LWAVE', appendage= '\nLWAVE = Ture') # print('yoo')
def absorbed(self, millerindex_1, absorbate_1, absorba, judge='', appendage=""): from pymatgen import Structure, Lattice, MPRester, Molecule import pymatgen.core.structure import pymatgen.core.sites from pymatgen.analysis.adsorption import AdsorbateSiteFinder, reorient_z, plot_slab from pymatgen.core.surface import generate_all_slabs from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from matplotlib import pyplot as plt from pymatgen.io.cif import CifParser from pymatgen.io.vasp.inputs import Poscar from pymatgen.io.vasp.sets import MVLSlabSet from pymatgen.io.cif import CifWriter import os import shutil ass = self.cif_route print(ass) # os.chdir(r"E:\VASP practical\Input") # print (os.getcwd()) # Note that you must provide your own API Key, which can # be accessed via the Dashboard at materialsproject.org #mpr = MPRester()#密钥 struct = CifParser(ass) structure = struct.get_structures()[0] print(structure) os.chdir(r"E:\VASP practical\Input") print(os.getcwd()) # fcc_ni = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.5), ["Ni", "Ni"], # [[0, 0, 0], [0.5, 0.5, 0.5]]) slabs = generate_all_slabs(structure, max_index=1, min_slab_size=8.0, min_vacuum_size=10.0) millerindex = millerindex_1 struct_111 = [ slab for slab in slabs if slab.miller_index == millerindex_1 ][0] asf_ni_111 = AdsorbateSiteFinder(struct_111) ads_sites = asf_ni_111.find_adsorption_sites() # print(ads_sites) assert len(ads_sites) == 4 fig = plt.figure() ax = fig.add_subplot(111) plot_slab(struct_111, ax, adsorption_sites=True) fig = plt.figure() ax = fig.add_subplot(111) adsorbate = Molecule(absorbate_1, absorba) ads_structs = asf_ni_111.generate_adsorption_structures( adsorbate, repeat=[1, 1, 1]) A = Poscar(reorient_z(ads_structs[0])) #将切面转换为Poscar open('POSCAR', 'w').write(str(A)) p = Poscar.from_file('POSCAR') # w = CifWriter(A.struct) # w.write_file('mystructure.cif') path = r'E:\VASP practical\Input\POSCAR' # 文件路径 if os.path.exists(path): # 如果文件存在 # 删除文件,可使用以下两种方法。 os.remove(path) #os.unlink(path) else: print('no such file:%s' % my_file) # 则返回文件不存在 # w = CifWriter(A.struct) # w.write_file('mystructure.cif') relax = p.structure #将Poscar 转换为结构信息 custom_settings = {"NPAR": 4} # 用户的INCAR 设置 relaxs = MVLSlabSet(relax, user_incar_settings=custom_settings) # Vasp输入文件生成器 dire = str(ass) + "---" + str(absorbate_1) + str(millerindex_1) # print (relax) relaxs.write_input(dire) os.chdir("./" + dire) print(os.getcwd()) #定义一个更改当前目录的变量 dire2 = './vaspstd_sub' #确立脚本名称 shutil.copy(r"C:\Users\41958\.spyder-py3\vaspstd_sub", dire2) eb = appendage #添加其他INCAR参数 with open('INCAR', 'r') as f1: lines = f1.readlines() with open('INCAR', 'w') as f2: for line in lines: if judge in line: continue f2.write(line) with open('INCAR', 'a') as f3: f3.write(eb) plot_slab(ads_structs[0], ax, adsorption_sites=False, decay=0.09) # open('POSCAR001', 'w').write(str(Poscar(reorient_z(ads_structs[0])))) os.chdir(r"D:\Desktop\VASP practical\workdir") print(os.getcwd()) print('finished') # my_lattace = Lattace('mp-698074')#半水石膏 # # my_lattace.phase_out()#生成晶胞优化的输入文件 # go = my_lattace.phase_sol(66,judge='LWAVE', appendage= '\nLWAVE = Ture') # print('yoo')
ax = fig.add_subplot(1, 1, 1) plot_slab(Cu_111, ax, adsorption_sites=False) ax.set_title("Cu (1, 1, 1) surface") ax.set_xticks([]) ax.set_yticks([]) plt.show() # ### Verificando os locais de adsorção no plano e entre as camadas # In[24]: from pymatgen.analysis.adsorption import AdsorbateSiteFinder # In[25]: asf = AdsorbateSiteFinder(Cu_111) # In[26]: add_sites = asf.find_adsorption_sites() add_sites # In[27]: fig = plt.figure() ax = fig.add_subplot(1, 1, 1) plot_slab(Cu_111, ax, adsorption_sites=True) ax.set_title("Cu (1, 1, 1) adsorption points") ax.set_xticks([]) ax.set_yticks([]) plt.show()
def get_wf_surface(slabs, molecules=[], bulk_structure=None, slab_gen_params=None, vasp_cmd="vasp", db_file=None, ads_structures_params={}, add_molecules_in_box=False): """ Args: slabs (list of Slabs or Structures): slabs to calculate molecules (list of Molecules): molecules to place as adsorbates bulk_structure (Structure): bulk structure from which generate slabs after reoptimization. If supplied, workflow will begin with bulk structure optimization. slab_gen_params (dict): dictionary of slab generation parameters used to generate the slab, necessary to get the slab that corresponds to the bulk structure if in that mode ads_structures_params (dict): parameters to be supplied as kwargs to AdsorbateSiteFinder.generate_adsorption_structures add_molecules_in_box (boolean): flag to add calculation of molecule energies to the workflow db_file (string): path to database file vasp_cmd (string): vasp command Returns: Workflow """ fws, parents = [], [] if bulk_structure: vis = MVLSlabSet(bulk_structure, bulk=True) fws.append( OptimizeFW(bulk_structure, vasp_input_set=vis, vasp_cmd="vasp", db_file=db_file)) parents = fws[0] for slab in slabs: name = slab.composition.reduced_formula if getattr(slab, "miller_index", None): name += "_{}".format(slab.miller_index) fws.append( get_slab_fw(slab, bulk_structure, slab_gen_params, db_file=db_file, vasp_cmd=vasp_cmd, parents=parents, name=name + " slab optimization")) for molecule in molecules: ads_slabs = AdsorbateSiteFinder( slab).generate_adsorption_structures(molecule, **ads_structures_params) for n, ads_slab in enumerate(ads_slabs): ads_name = "{}-{} adsorbate optimization {}".format( molecule.composition.formula, name, n) fws.append( get_slab_fw(ads_slab, bulk_structure, slab_gen_params, db_file=db_file, vasp_cmd=vasp_cmd, parents=parents, name=ads_name)) if add_molecules_in_box: for molecule in molecules: # molecule in box m_struct = Structure(Lattice.cubic(10), molecule.species_and_occu, molecule.cart_coords, coords_are_cartesian=True) m_struct.translate_sites( list(range(len(m_struct))), np.array([0.5] * 3) - np.average(m_struct.frac_coords, axis=0)) vis = MVLSlabSet(m_struct) fws.append( OptimizeFW(molecule, job_type="normal", vasp_input_set=vis, db_file=db_file, vasp_cmd=vasp_cmd)) # TODO: add analysis framework return Workflow(fws, name="")
def get_wf_slab(slab, include_bulk_opt=False, adsorbates=None, ads_structures_params=None, ads_site_finder_params=None, vasp_cmd="vasp", db_file=None, add_molecules_in_box=False, user_incar_settings=None): """ Gets a workflow corresponding to a slab calculation along with optional adsorbate calcs and precursor oriented unit cell optimization Args: slabs (list of Slabs or Structures): slabs to calculate include_bulk_opt (bool): whether to include bulk optimization, this flag sets the slab fireworks to be TransmuterFWs based on bulk optimization of oriented unit cells adsorbates ([Molecule]): list of molecules to place as adsorbates ads_structures_params (dict): parameters to be supplied as kwargs to AdsorbateSiteFinder.generate_adsorption_structures add_molecules_in_box (boolean): flag to add calculation of adsorbate molecule energies to the workflow db_file (string): path to database file vasp_cmd (string): vasp command Returns: Workflow """ fws, parents = [], [] if adsorbates is None: adsorbates = [] if ads_structures_params is None: ads_structures_params = {} # Add bulk opt firework if specified if include_bulk_opt: oriented_bulk = slab.oriented_unit_cell vis = MPSurfaceSet(oriented_bulk, bulk=True) fws.append( OptimizeFW(structure=oriented_bulk, vasp_input_set=vis, vasp_cmd=vasp_cmd, db_file=db_file)) parents = fws[-1] name = slab.composition.reduced_formula if getattr(slab, "miller_index", None): name += "_{}".format(slab.miller_index) # Create slab fw and add it to list of fws slab_fw = get_slab_fw(slab, include_bulk_opt, db_file=db_file, vasp_cmd=vasp_cmd, parents=parents, name="{} slab optimization".format(name)) fws.append(slab_fw) for adsorbate in adsorbates: ads_slabs = AdsorbateSiteFinder( slab, **ads_site_finder_params).generate_adsorption_structures( adsorbate, **ads_structures_params) for n, ads_slab in enumerate(ads_slabs): # Create adsorbate fw ads_name = "{}-{} adsorbate optimization {}".format( adsorbate.composition.formula, name, n) adsorbate_fw = get_slab_fw(ads_slab, include_bulk_opt, db_file=db_file, vasp_cmd=vasp_cmd, parents=parents, name=ads_name, user_incar_settings=user_incar_settings) fws.append(adsorbate_fw) if isinstance(slab, Slab): name = "{}_{} slab workflow".format( slab.composition.reduced_composition, slab.miller_index) else: name = "{} slab workflow".format(slab.composition.reduced_composition) wf = Workflow(fws, name=name) # Add optional molecules workflow if add_molecules_in_box: molecule_wf = get_wf_molecules(adsorbates, db_file=db_file, vasp_cmd=vasp_cmd) wf.append_wf(molecule_wf) return wf
class AdsorbateSiteFinderTest(PymatgenTest): def setUp(self): self.structure = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.5), ["Ni"], [[0, 0, 0]]) lattice = Lattice.cubic(3.010) frac_coords = [ [0.00000, 0.00000, 0.00000], [0.00000, 0.50000, 0.50000], [0.50000, 0.00000, 0.50000], [0.50000, 0.50000, 0.00000], [0.50000, 0.00000, 0.00000], [0.50000, 0.50000, 0.50000], [0.00000, 0.00000, 0.50000], [0.00000, 0.50000, 0.00000], ] species = ["Mg", "Mg", "Mg", "Mg", "O", "O", "O", "O"] self.MgO = Structure(lattice, species, frac_coords) slabs = generate_all_slabs( self.structure, max_index=2, min_slab_size=6.0, min_vacuum_size=15.0, max_normal_search=1, center_slab=True, ) self.slab_dict = {"".join([str(i) for i in slab.miller_index]): slab for slab in slabs} self.asf_211 = AdsorbateSiteFinder(self.slab_dict["211"]) self.asf_100 = AdsorbateSiteFinder(self.slab_dict["100"]) self.asf_111 = AdsorbateSiteFinder(self.slab_dict["111"]) self.asf_110 = AdsorbateSiteFinder(self.slab_dict["110"]) self.asf_struct = AdsorbateSiteFinder(Structure.from_sites(self.slab_dict["111"].sites)) def test_init(self): AdsorbateSiteFinder(self.slab_dict["100"]) AdsorbateSiteFinder(self.slab_dict["111"]) def test_from_bulk_and_miller(self): # Standard site finding asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 1)) sites = asf.find_adsorption_sites() self.assertEqual(len(sites["hollow"]), 2) self.assertEqual(len(sites["bridge"]), 1) self.assertEqual(len(sites["ontop"]), 1) self.assertEqual(len(sites["all"]), 4) asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 0, 0)) sites = asf.find_adsorption_sites() self.assertEqual(len(sites["all"]), 3) self.assertEqual(len(sites["bridge"]), 2) asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 0), undercoord_threshold=0.1) self.assertEqual(len(asf.surface_sites), 1) # Subsurface site finding asf = AdsorbateSiteFinder.from_bulk_and_miller(self.structure, (1, 1, 1)) sites = asf.find_adsorption_sites(positions=["ontop", "subsurface", "bridge"]) self.assertEqual(len(sites["all"]), 5) self.assertEqual(len(sites["subsurface"]), 3) def test_find_adsorption_sites(self): sites = self.asf_100.find_adsorption_sites() self.assertEqual(len(sites["all"]), 3) self.assertEqual(len(sites["hollow"]), 0) self.assertEqual(len(sites["bridge"]), 2) self.assertEqual(len(sites["ontop"]), 1) sites = self.asf_111.find_adsorption_sites() self.assertEqual(len(sites["all"]), 4) sites = self.asf_110.find_adsorption_sites() self.assertEqual(len(sites["all"]), 4) sites = self.asf_211.find_adsorption_sites() # Test on structure sites = self.asf_struct.find_adsorption_sites() def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures(co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual( len([site for site in structures[0] if site.properties["surface_properties"] != "adsorbate"]), 4 * len(self.asf_111.slab), ) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites["all"][n]) find_args = {"positions": ["hollow"]} structures_hollow = self.asf_111.generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites["hollow"])) for n, structure in enumerate(structures_hollow): self.assertTrue(in_coord_list(sites["hollow"], structure[-2].coords, 1e-4)) # Check molecule not changed after rotation when added to surface co = Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]]) structures = self.asf_211.generate_adsorption_structures(co) self.assertEqual(co, Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]])) # Check translation sites = self.asf_211.find_adsorption_sites() ads_site_coords = sites["all"][0] c_site = structures[0].sites[-2] self.assertEqual(str(c_site.specie), "C") self.assertArrayAlmostEqual(c_site.coords, sites["all"][0]) # Check no translation structures = self.asf_111.generate_adsorption_structures(co, translate=False) self.assertEqual(co, Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]])) sites = self.asf_111.find_adsorption_sites() ads_site_coords = sites["all"][0] c_site = structures[0].sites[-2] self.assertArrayAlmostEqual(c_site.coords, ads_site_coords + np.array([1.0, -0.5, 3])) def test_adsorb_both_surfaces(self): # Test out for monatomic adsorption o = Molecule("O", [[0, 0, 0]]) adslabs = self.asf_100.adsorb_both_surfaces(o) adslabs_one = self.asf_100.generate_adsorption_structures(o) self.assertEqual(len(adslabs), len(adslabs_one)) for adslab in adslabs: sg = SpacegroupAnalyzer(adslab) sites = sorted(adslab, key=lambda site: site.frac_coords[2]) self.assertTrue(sites[0].species_string == "O") self.assertTrue(sites[-1].species_string == "O") self.assertTrue(sg.is_laue()) # Test out for molecular adsorption oh = Molecule(["O", "H"], [[0, 0, 0], [0, 0, 1]]) adslabs = self.asf_100.adsorb_both_surfaces(oh) adslabs_one = self.asf_100.generate_adsorption_structures(oh) self.assertEqual(len(adslabs), len(adslabs_one)) for adslab in adslabs: sg = SpacegroupAnalyzer(adslab) sites = sorted(adslab, key=lambda site: site.frac_coords[2]) self.assertTrue(sites[0].species_string in ["O", "H"]) self.assertTrue(sites[-1].species_string in ["O", "H"]) self.assertTrue(sg.is_laue()) def test_generate_substitution_structures(self): # Test this for a low miller index halite structure slabs = generate_all_slabs(self.MgO, 1, 10, 10, center_slab=True, max_normal_search=1) for slab in slabs: adsgen = AdsorbateSiteFinder(slab) adslabs = adsgen.generate_substitution_structures("Ni") # There should be 2 configs (sub O and sub # Mg) for (110) and (100), 1 for (111) if tuple(slab.miller_index) != (1, 1, 1): self.assertEqual(len(adslabs), 2) else: self.assertEqual(len(adslabs), 1) # Test out whether it can correctly dope both # sides. Avoid (111) because it is not symmetric if tuple(slab.miller_index) != (1, 1, 1): adslabs = adsgen.generate_substitution_structures("Ni", sub_both_sides=True, target_species=["Mg"]) # Test if default parameters dope the surface site for i, site in enumerate(adslabs[0]): if adsgen.slab[i].surface_properties == "surface" and site.species_string == "Mg": print( adslabs[0][i].surface_properties, adsgen.slab[i].surface_properties, ) self.assertTrue(adslabs[0][i].surface_properties == "substitute") self.assertTrue(adslabs[0].is_symmetric()) # Correctly dope the target species self.assertEqual( adslabs[0].composition.as_dict()["Mg"], slab.composition.as_dict()["Mg"] - 2, ) # There should be one config (sub Mg) self.assertEqual(len(adslabs), 1) def test_functions(self): slab = self.slab_dict["111"] get_rot(slab) reorient_z(slab)