def test_init(self): zno_slab = Slab(self.zno55.lattice, self.zno55.species, self.zno55.frac_coords, self.zno55.miller_index, self.zno55.oriented_unit_cell, 0, self.zno55.scale_factor) m = self.zno55.lattice.matrix area = np.linalg.norm(np.cross(m[0], m[1])) self.assertAlmostEqual(zno_slab.surface_area, area) self.assertEqual(zno_slab.lattice.parameters, self.zno55.lattice.parameters) self.assertEqual(zno_slab.oriented_unit_cell.composition, self.zno1.composition) self.assertEqual(len(zno_slab), 8) # check reorient_lattice. get a slab not oriented and check that orientation # works even with cartesian coordinates. zno_not_or = SlabGenerator(self.zno1, [1, 0, 0], 5, 5, lll_reduce=False, center_slab=False, reorient_lattice=False).get_slab() zno_slab_cart = Slab(zno_not_or.lattice, zno_not_or.species, zno_not_or.cart_coords, zno_not_or.miller_index, zno_not_or.oriented_unit_cell, 0, zno_not_or.scale_factor, coords_are_cartesian=True, reorient_lattice=True) self.assertArrayAlmostEqual(zno_slab.frac_coords, zno_slab_cart.frac_coords) c = zno_slab_cart.lattice.matrix[2] self.assertArrayAlmostEqual([0, 0, np.linalg.norm(c)], c)
def slab_from_file(structure, hkl): """ Reads in structure from the file and returns slab object. Args: structure (str): Structure file in any format supported by pymatgen. Will accept a pymatgen.Structure object directly. hkl (tuple): Miller index of the slab in the input file. Returns: Slab object """ if type(structure) == str: slab_input = Structure.from_file(structure) else: slab_input = structure return Slab( slab_input.lattice, slab_input.species_and_occu, slab_input.frac_coords, hkl, Structure.from_sites(slab_input, to_unit_cell=True), # this OUC is not correct, need to get it from slabgenerator shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=slab_input.site_properties)
def get_ase_slab(pmg_struct, hkl=(1, 1, 1), min_thick=10, min_vac=10): """ takes in the intial structure as pymatgen Structure object uses ase to generate the slab returns pymatgen Slab object Args: pmg_struct: pymatgen structure object hkl: hkl index of surface of slab to be created min_thick: minimum thickness of slab in Angstroms min_vac: minimum vacuum spacing """ ase_atoms = AseAtomsAdaptor().get_atoms(pmg_struct) pmg_slab_gen = SlabGenerator(pmg_struct, hkl, min_thick, min_vac) h = pmg_slab_gen._proj_height nlayers = int(math.ceil(pmg_slab_gen.min_slab_size / h)) ase_slab = surface(ase_atoms, hkl, nlayers) ase_slab.center(vacuum=min_vac / 2, axis=2) pmg_slab_structure = AseAtomsAdaptor().get_structure(ase_slab) return Slab(lattice=pmg_slab_structure.lattice, species=pmg_slab_structure.species_and_occu, coords=pmg_slab_structure.frac_coords, site_properties=pmg_slab_structure.site_properties, miller_index=hkl, oriented_unit_cell=pmg_slab_structure, shift=0., scale_factor=None, energy=None)
def test_as_dict(self): slabs = generate_all_slabs( self.ti, 1, 10, 10, bonds=None, tol=1e-3, max_broken_bonds=0, lll_reduce=False, center_slab=False, primitive=True, ) slab = slabs[0] s = json.dumps(slab.as_dict()) d = json.loads(s) self.assertEqual(slab, Slab.from_dict(d)) # test initialising with a list scale_factor slab = Slab( self.zno55.lattice, self.zno55.species, self.zno55.frac_coords, self.zno55.miller_index, self.zno55.oriented_unit_cell, 0, self.zno55.scale_factor.tolist(), ) s = json.dumps(slab.as_dict()) d = json.loads(s) self.assertEqual(slab, Slab.from_dict(d))
def get_shear_reduced_slab(slab): """ Reduce the vectors of the slab plane according to the algorithm in substrate_analyzer, then make a new Slab with a Lattice with those reduced vectors. Args: slab (Slab): Slab to reduce Returns: Slab object of identical structure to the input slab but rduced in-plane lattice vectors """ original_vectors = [slab.lattice.matrix[0], slab.lattice.matrix[1]] reduced_vectors = reduce_vectors(slab.lattice.matrix[0], slab.lattice.matrix[1]) new_lattice = Lattice( [reduced_vectors[0], reduced_vectors[1], slab.lattice.matrix[2]]) return Slab(lattice=new_lattice, species=slab.species, coords=slab.cart_coords, miller_index=slab.miller_index, oriented_unit_cell=slab.oriented_unit_cell, shift=slab.shift, scale_factor=slab.scale_factor, coords_are_cartesian=True, energy=slab.energy, reorient_lattice=slab.reorient_lattice, to_unit_cell=True)
def apply_transformation(self, structure, matrix): """ Make a supercell of structure using matrix Args: structure (Slab): Slab to make supercell of matrix (3x3 np.ndarray): supercell matrix Returns: (Slab) The supercell of structure """ modified_substrate_structure = structure.copy() # Apply scaling modified_substrate_structure.make_supercell(matrix) # Reduce vectors new_lattice = modified_substrate_structure.lattice.matrix.copy() new_lattice[:2, :] = reduce_vectors( *modified_substrate_structure.lattice.matrix[:2, :]) modified_substrate_structure = Slab( lattice=Lattice(new_lattice), species=modified_substrate_structure.species, coords=modified_substrate_structure.cart_coords, miller_index=modified_substrate_structure.miller_index, oriented_unit_cell=modified_substrate_structure.oriented_unit_cell, shift=modified_substrate_structure.shift, scale_factor=modified_substrate_structure.scale_factor, coords_are_cartesian=True, energy=modified_substrate_structure.energy, reorient_lattice=modified_substrate_structure.reorient_lattice, to_unit_cell=True) return modified_substrate_structure
def test_init(self): zno_slab = Slab(self.zno55.lattice, self.zno55.species, self.zno55.frac_coords, self.zno55.miller_index, self.zno55.oriented_unit_cell, 0, self.zno55.scale_factor) m = self.zno55.lattice.matrix area = np.linalg.norm(np.cross(m[0], m[1])) self.assertAlmostEqual(zno_slab.surface_area, area) self.assertEqual(zno_slab.lattice.parameters, self.zno55.lattice.parameters) self.assertEqual(zno_slab.oriented_unit_cell.composition, self.zno1.composition) self.assertEqual(len(zno_slab), 8)
def trajToMG(self): from pymatgen.io.ase import AseAtomsAdaptor from pymatgen.core.surface import Slab atoms = self.trajInput() struct = AseAtomsAdaptor.get_structure(atoms) return Slab(struct.lattice, struct.species, struct.cart_coords, (1, 1, 0), struct, 3, (1, 1, 1), coords_are_cartesian=True, to_unit_cell=True)
def merge_slabs(substrate, film, slab_offset, x_offset, y_offset, vacuum=20, **kwargs): """ Given substrate and film supercells (oriented to match as closely as possible), strain the film to match the substrate lattice and combine the slabs. Args: slab_offset: spacing between the substrate and film x_offset y_offset: in-plane displacement of the film in Cartesian coordinates vacuum: vacuum buffer above the film Returns: combined_structure (Slab): A structure with the strained film and substrate combined into one structure """ # strain film to match substrate new_latt = film.lattice.matrix.copy() new_latt[:2, :2] = substrate.lattice.matrix[:2, :2] film.lattice = Lattice(new_latt) combined_species = [*substrate.species, *film.species] if kwargs.get('cell_height'): height = kwargs.get('cell_height') else: added_height = vacuum + slab_offset + film.lattice.c height = added_height + substrate.lattice.matrix[2, 2] combined_lattice = substrate.lattice.matrix.copy() combined_lattice[2, :] *= height / substrate.lattice.matrix[2, 2] max_substrate = np.max(substrate.cart_coords[:, 2]) min_substrate = np.min(film.cart_coords[:, 2]) offset = max_substrate - min_substrate + slab_offset offset_film_coords = [np.add(coord, [x_offset, y_offset, offset]) for coord in film.cart_coords] combined_coords = [*substrate.cart_coords, *offset_film_coords] combined_site_properties = {} for key, item in substrate.site_properties.items(): combined_site_properties[key] = [*substrate.site_properties[key], *film.site_properties[key]] labels = ['substrate'] * len(substrate) + ['film'] * len(film) combined_site_properties['interface_label'] = labels combined_structure = Slab(lattice=Lattice(combined_lattice), species=combined_species, coords=combined_coords, miller_index=substrate.miller_index, oriented_unit_cell=substrate, shift=substrate.shift, scale_factor=substrate.scale_factor, coords_are_cartesian=True, energy=substrate.energy, reorient_lattice=False, to_unit_cell=True, site_properties=combined_site_properties) return combined_structure
def test_add_adsorbate_atom(self): zno_slab = Slab(self.zno55.lattice, self.zno55.species, self.zno55.frac_coords, self.zno55.miller_index, self.zno55.oriented_unit_cell, 0, self.zno55.scale_factor) zno_slab.add_adsorbate_atom([1], 'H', 1) self.assertEqual(len(zno_slab), 9) self.assertEqual(str(zno_slab[8].specie), 'H') self.assertAlmostEqual(zno_slab.get_distance(1, 8), 1.0) self.assertTrue(zno_slab[8].c > zno_slab[0].c) m = self.zno55.lattice.matrix area = np.linalg.norm(np.cross(m[0], m[1])) self.assertAlmostEqual(zno_slab.surface_area, area) self.assertEqual(zno_slab.lattice.lengths_and_angles, self.zno55.lattice.lengths_and_angles)
def makePMGSlabFromASE(a, facet): lattice = a.get_cell() species = a.get_chemical_symbols() coords = a.get_positions() miller_index = facet oriented_unit_cell = AseAtomsAdaptor.get_structure(a) shift = 0 scale_factor = None #??????? return Slab(lattice, species, coords, miller_index, oriented_unit_cell, shift, scale_factor, coords_are_cartesian=True)
def slab_from_file(hkl, filename): """ reads in structure from the file and returns slab object. useful for reading in 2d/substrate structures from file. Args: hkl: miller index of the slab in the input file. filename: structure file in any format supported by pymatgen Returns: Slab object """ slab_input = Structure.from_file(filename) return Slab(slab_input.lattice, slab_input.species_and_occu, slab_input.frac_coords, hkl, Structure.from_sites(slab_input, to_unit_cell=True), shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=slab_input.site_properties)
def slab_from_file(structure, hkl): """ Reads in structure from the file and returns slab object. Args: structure (str): structure file in any format supported by pymatgen hkl (tuple): Miller index of the slab in the input file. Returns: Slab object """ slab_input = Structure.from_file(structure) return Slab(slab_input.lattice, slab_input.species_and_occu, slab_input.frac_coords, hkl, Structure.from_sites(slab_input, to_unit_cell=True), shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=slab_input.site_properties)
def add_vacuum_padding(slab, vacuum, hkl=[0, 0, 1]): """ add vacuum spacing to the given structure Args: slab: sructure/slab object to be padded vacuum: in angstroms hkl: miller index Returns: Structure object """ min_z = np.min([fcoord[2] for fcoord in slab.frac_coords]) slab.translate_sites(list(range(len(slab))), [0, 0, -min_z]) a, b, c = slab.lattice.matrix z = [coord[2] for coord in slab.cart_coords] zmax = np.max(z) zmin = np.min(z) thickness = zmax - zmin new_c = c / np.linalg.norm(c) * (thickness + vacuum) new_lattice = Lattice(np.array([a, b, new_c])) new_sites = [] for site in slab: new_sites.append( PeriodicSite(site.species_and_occu, site.coords, new_lattice, properties=site.properties, coords_are_cartesian=True)) new_struct = Structure.from_sites(new_sites) # center the slab avg_z = np.average([fcoord[2] for fcoord in new_struct.frac_coords]) new_struct.translate_sites(list(range(len(new_struct))), [0, 0, 0.5 - avg_z]) return Slab(new_struct.lattice, new_struct.species_and_occu, new_struct.frac_coords, hkl, Structure.from_sites(new_struct, to_unit_cell=True), shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=new_struct.site_properties)
with open(orgdir + 'structure_collection_bulk.json') as f: st_bulk_json = json.load(f) f.close() bulkkeys = list(st_bulk_json.keys()) surface_sites_dict = {} for matkey in st_json.keys(): tmp_st = st_json[matkey] st = Structure.from_dict(tmp_st['st_after']) bulkkey = ext_bulk_soap(matkey, bulkkeys) st_bulk = Structure.from_dict(st_bulk_json[bulkkey]) slab = Slab(lattice=st.lattice, species=st.species, coords=st.frac_coords, miller_index=[1, 1, 1], oriented_unit_cell=st_bulk, shift=0, scale_factor=1, reorient_lattice=False) print(slab.is_symmetric(), slab.have_equivalent_surfaces()) surface_sites = [] surface_sites_prop = [] for tol in [0.0, 0.25, 0.5]: tmp_surface_sites = get_surface_sites(slab, tol=tol) surface_sites_prop.append(slab.site_properties['is_surf_site']) surface_sites_prop = np.asarray(surface_sites_prop) surface_sites_prop = np.any(surface_sites_prop, axis=0) surface_sites = np.arange(0, slab.num_sites, 1)[surface_sites_prop] surface_sites = [
def adsorb_both_surfaces(slab, molecule, selective_dynamics=False, height=0.9, mi_vec=None, repeat=None, min_lw=5.0, reorient=True, find_args={}): """ Function that generates all adsorption structures for a given molecular adsorbate on both surfaces of a slab. Args: slab (Slab): slab object for which to find adsorbate sites selective_dynamics (bool): flag for whether to assign non-surface sites as fixed for selective dynamics molecule (Molecule): molecule corresponding to adsorbate selective_dynamics (bool): flag for whether to assign non-surface sites as fixed for selective dynamics height (float): height criteria for selection of surface sites mi_vec (3-D array-like): vector corresponding to the vector concurrent with the miller index, this enables use with slabs that have been reoriented, but the miller vector must be supplied manually repeat (3-tuple or list): repeat argument for supercell generation min_lw (float): minimum length and width of the slab, only used if repeat is None reorient (bool): flag on whether or not to reorient adsorbate along the miller index find_args (dict): dictionary of arguments to be passed to the call to self.find_adsorption_sites, e.g. {"distance":2.0} """ matcher = StructureMatcher() # Get adsorption on top adsgen_top = AdsorbateSiteFinder(slab, selective_dynamics=selective_dynamics, height=height, mi_vec=mi_vec, top_surface=True) structs = adsgen_top.generate_adsorption_structures(molecule, repeat=repeat, min_lw=min_lw, reorient=reorient, find_args=find_args) adslabs = [g[0] for g in matcher.group_structures(structs)] # Get adsorption on bottom adsgen_bottom = AdsorbateSiteFinder(slab, selective_dynamics=selective_dynamics, height=height, mi_vec=mi_vec, top_surface=False) structs = adsgen_bottom.generate_adsorption_structures(molecule, repeat=repeat, min_lw=min_lw, reorient=reorient, find_args=find_args) adslabs.extend([g[0] for g in matcher.group_structures(structs)]) # Group symmetrically similar slabs adsorbed_slabs = [] for group in matcher.group_structures(adslabs): # Further group each group by which surface adsorbed top_ads, bottom_ads = [], [] for s in group: sites = sorted(s, key=lambda site: site.frac_coords[2]) if sites[0].surface_properties == "adsorbate": bottom_ads.append(s) else: top_ads.append(s) if not top_ads or not bottom_ads: warnings.warn("There are not enough sites at the bottom or " "top to generate a symmetric adsorbed slab") continue # Combine the adsorbates of both top and bottom slabs # into one slab with one adsorbate on each side coords = list(top_ads[0].frac_coords) species = top_ads[0].species lattice = top_ads[0].lattice coords.extend([ site.frac_coords for site in bottom_ads[0] if site.surface_properties == "adsorbate" ]) species.extend([ site.specie for site in bottom_ads[0] if site.surface_properties == "adsorbate" ]) slab = Slab(lattice, species, coords, slab.miller_index, slab.oriented_unit_cell, slab.shift, slab.scale_factor) adsorbed_slabs.append(slab) return adsorbed_slabs