def create_slab(self, vacuum=12, thickness=10): """ set the vacuum spacing, slab thickness and call sd_flags for top 2 layers returns the poscar corresponding to the modified structure """ strt_structure = self.input_structure.copy() if self.from_ase: slab_struct = get_ase_slab(strt_structure, hkl=self.system['hkl'], min_thick=thickness, min_vac=vacuum) else: slab_struct = SlabGenerator(initial_structure=strt_structure, miller_index=self.system['hkl'], min_slab_size=thickness, min_vacuum_size=vacuum, lll_reduce=False, center_slab=True, primitive=False).get_slab() slab_struct.sort() sd = self.set_sd_flags(slab_struct) comment = 'VAC' + str(vacuum) + 'THICK' + str(thickness) return Poscar(slab_struct, comment=comment, selective_dynamics=sd)
def create_slap(initial_structure, miller_index, min_slab_size, min_vacuum_size=0, lll_reduce=False, center_slab=False, primitive=False, max_normal_search=1, reorient_lattice=True): """ wraps the pymatgen slab generator """ # minimum slab size is in Angstroem!!! pymat_struc = initial_structure.get_pymatgen_structure() slabg = SlabGenerator(pymat_struc, miller_index, min_slab_size, min_vacuum_size, lll_reduce=lll_reduce, center_slab=center_slab, primitive=primitive, max_normal_search=max_normal_search) slab = slabg.get_slab() #slab2 = slab.get_orthogonal_c_slab() film_struc = StructureData(pymatgen_structure=slab) film_struc.pbc = (True, True, False) # TODO: sort atoms after z-coordinate value, # TODO: Move all atoms that the middle atom is at [x,y,0] # film_struc2 = move_atoms_incell(film_struc, [0,0, z_of_middle atom]) return film_struc
def get_dimensionality_gorai(structure, max_hkl=2, el_radius_updates=None, min_slab_size=5, min_vacuum_size=5, standardize=True, bonds=None): """ This method returns whether a structure is 3D, 2D (layered), or 1D (linear chains or molecules) according to the algorithm published in Gorai, P., Toberer, E. & Stevanovic, V. Computational Identification of Promising Thermoelectric Materials Among Known Quasi-2D Binary Compounds. J. Mater. Chem. A 2, 4136 (2016). Note that a 1D structure detection might indicate problems in the bonding algorithm, particularly for ionic crystals (e.g., NaCl) Users can change the behavior of bonds detection by passing either el_radius_updates to update atomic radii for auto-detection of max bond distances, or bonds to explicitly specify max bond distances for atom pairs. Note that if you pass both, el_radius_updates are ignored. Args: structure: (Structure) structure to analyze dimensionality for max_hkl: (int) max index of planes to look for layers el_radius_updates: (dict) symbol->float to update atomic radii min_slab_size: (float) internal surface construction parameter min_vacuum_size: (float) internal surface construction parameter standardize (bool): whether to standardize the structure before analysis. Set to False only if you already have the structure in a convention where layers / chains will be along low <hkl> indexes. bonds ({(specie1, specie2): max_bond_dist}: bonds are specified as a dict of tuples: float of specie1, specie2 and the max bonding distance. For example, PO4 groups may be defined as {("P", "O"): 3}. Returns: (int) the dimensionality of the structure - 1 (molecules/chains), 2 (layered), or 3 (3D) """ if standardize: structure = SpacegroupAnalyzer(structure). \ get_conventional_standard_structure() if not bonds: bonds = get_max_bond_lengths(structure, el_radius_updates) num_surfaces = 0 for h in range(max_hkl): for k in range(max_hkl): for l in range(max_hkl): if max([h, k, l]) > 0 and num_surfaces < 2: sg = SlabGenerator(structure, (h, k, l), min_slab_size=min_slab_size, min_vacuum_size=min_vacuum_size) slabs = sg.get_slabs(bonds) for _ in slabs: num_surfaces += 1 return 3 - min(num_surfaces, 2)
def test_make_confs_0(self): if not os.path.exists(os.path.join(self.equi_path, 'CONTCAR')): with self.assertRaises(RuntimeError): self.surface.make_confs(self.target_path, self.equi_path) shutil.copy(os.path.join(self.source_path, 'mp-141.vasp'), os.path.join(self.equi_path, 'CONTCAR')) task_list = self.surface.make_confs(self.target_path, self.equi_path) self.assertEqual(len(task_list), 7) dfm_dirs = glob.glob(os.path.join(self.target_path, 'task.*')) incar0 = Incar.from_file(os.path.join('vasp_input', 'INCAR.rlx')) incar0['ISIF'] = 4 self.assertEqual( os.path.realpath(os.path.join(self.equi_path, 'CONTCAR')), os.path.realpath(os.path.join(self.target_path, 'POSCAR'))) ref_st = Structure.from_file(os.path.join(self.target_path, 'POSCAR')) dfm_dirs.sort() for ii in dfm_dirs: st_file = os.path.join(ii, 'POSCAR') self.assertTrue(os.path.isfile(st_file)) st0 = Structure.from_file(st_file) st1_file = os.path.join(ii, 'POSCAR.tmp') self.assertTrue(os.path.isfile(st1_file)) st1 = Structure.from_file(st1_file) miller_json_file = os.path.join(ii, 'miller.json') self.assertTrue(os.path.isfile(miller_json_file)) miller_json = loadfn(miller_json_file) sl = SlabGenerator(ref_st, miller_json, self.prop_param[0]["min_slab_size"], self.prop_param[0]["min_vacuum_size"]) slb = sl.get_slab() st2 = Structure(slb.lattice, slb.species, slb.frac_coords) self.assertEqual(len(st1), len(st2))
def apply_transformation(self, structure): sg = SlabGenerator(structure, self.miller_index, self.min_slab_size, self.min_vacuum_size, self.lll_reduce, self.center_slab, self.primitive, self.max_normal_search) slab = sg.get_slab(self.shift, self.tol) return slab
def generate_slabs(self, film_millers, substrate_millers): """ Generates the film/substrate slab combinations for a set of given miller indicies Args: film_millers(array): all miller indices to generate slabs for film substrate_millers(array): all miller indicies to generate slabs for substrate """ for f in film_millers: film_slab = SlabGenerator(self.film, f, 20, 15, primitive=False).get_slab() film_vectors = reduce_vectors(film_slab.lattice.matrix[0], film_slab.lattice.matrix[1]) film_area = vec_area(*film_vectors) for s in substrate_millers: substrate_slab = SlabGenerator(self.substrate, s, 20, 15, primitive=False).get_slab() substrate_vectors = reduce_vectors( substrate_slab.lattice.matrix[0], substrate_slab.lattice.matrix[1]) substrate_area = vec_area(*substrate_vectors) yield [ film_area, substrate_area, film_vectors, substrate_vectors, f, s ]
def _create_surface(self): ''' This method will create the surface structure to relax Returns: surface_atoms_constrained `ase.Atoms` object of the surface to submit to Fireworks for relaxation ''' # Get the bulk and convert to `pymatgen.Structure` object with open(self.input().path, 'rb') as file_handle: bulk_doc = pickle.load(file_handle) bulk_atoms = make_atoms_from_doc(bulk_doc) bulk_structure = AseAtomsAdaptor.get_structure(bulk_atoms) # Use pymatgen to turn the bulk into a surface sga = SpacegroupAnalyzer(bulk_structure, symprec=0.1) bulk_structure = sga.get_conventional_standard_structure() gen = SlabGenerator(initial_structure=bulk_structure, miller_index=self.miller_indices, min_slab_size=self.min_height, **self.slab_generator_settings) surface_structure = gen.get_slab(self.shift, tol=self.get_slab_settings['tol']) # Convert the surface back to an `ase.Atoms` object and constrain # subsurface atoms surface_atoms = AseAtomsAdaptor.get_atoms(surface_structure) surface_atoms_constrained = self.__constrain_surface(surface_atoms) return surface_atoms_constrained
def generate_slabs(self, film_millers, substrate_millers): """ Generates the film/substrate slab combinations for a set of given miller indicies Args: film_millers(array): all miller indices to generate slabs for film substrate_millers(array): all miller indicies to generate slabs for substrate """ for f in film_millers: film_slab = SlabGenerator(self.film, f, 20, 15, primitive=False).get_slab() film_vectors = reduce_vectors(film_slab.lattice_vectors()[0], film_slab.lattice_vectors()[1]) film_area = vec_area(*film_vectors) for s in substrate_millers: substrate_slab = SlabGenerator(self.substrate, s, 20, 15, primitive=False).get_slab() substrate_vectors = reduce_vectors( substrate_slab.lattice_vectors()[0], substrate_slab.lattice_vectors()[1]) substrate_area = vec_area(*substrate_vectors) yield [film_area, substrate_area, film_vectors, substrate_vectors, f, s]
def test_nonstoichiometric_symmetrized_slab(self): # For the (111) halite slab, sometimes a nonstoichiometric # system is preferred over the stoichiometric Tasker 2. slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10, max_normal_search=1) slabs = slabgen.get_slabs(symmetrize=True) # We should end up with two terminations, one with # an Mg rich surface and another O rich surface self.assertEqual(len(slabs), 2) for slab in slabs: self.assertTrue(slab.is_symmetric()) # For a low symmetry primitive_elemental system such as # R-3m, there should be some nonsymmetric slabs # without using nonstoichiometric_symmetrized_slab slabs = generate_all_slabs(self.Dy, 1, 30, 30, center_slab=True, symmetrize=True) for s in slabs: self.assertTrue(s.is_symmetric()) self.assertGreater(len(s), len(self.Dy))
def generate_surface_vectors(self, film_millers, substrate_millers): """ Generates the film/substrate slab combinations for a set of given miller indicies Args: film_millers(array): all miller indices to generate slabs for film substrate_millers(array): all miller indicies to generate slabs for substrate """ vector_sets = [] for f in film_millers: film_slab = SlabGenerator(self.film, f, 20, 15, primitive=False).get_slab() film_vectors = reduce_vectors(film_slab.lattice.matrix[0], film_slab.lattice.matrix[1]) for s in substrate_millers: substrate_slab = SlabGenerator(self.substrate, s, 20, 15, primitive=False).get_slab() substrate_vectors = reduce_vectors( substrate_slab.lattice.matrix[0], substrate_slab.lattice.matrix[1]) vector_sets.append((film_vectors, substrate_vectors, f, s)) return vector_sets
def __calculate_unit_slab(self): ''' Calculates the height of the smallest unit slab from a given bulk and Miller cut Saved attributes: unit A `pymatgen.Structure` instance for the unit slab unit_slab_height The height of the unit slab in Angstroms ''' # Luigi will probably call this method multiple times. We only need to # do it once though. if not hasattr(self, 'unit_slab_height'): # Delete some slab generator settings that we don't care about for a # unit slab slab_generator_settings = utils.unfreeze_dict( self.slab_generator_settings) del slab_generator_settings['min_vacuum_size'] del slab_generator_settings['min_slab_size'] # Instantiate a pymatgen `SlabGenerator` bulk_structure = AseAtomsAdaptor.get_structure(self.bulk_atoms) sga = SpacegroupAnalyzer(bulk_structure, symprec=0.1) bulk_structure = sga.get_conventional_standard_structure() gen = SlabGenerator(initial_structure=bulk_structure, miller_index=self.miller_indices, min_vacuum_size=0., min_slab_size=1., **slab_generator_settings) # Generate the unit slab and find its height self.unit_slab = gen.get_slab(self.shift, tol=self.get_slab_settings['tol']) self.unit_slab_height = gen._proj_height
def get_tasker2_slabs(self): # The uneven distribution of ions on the (111) facets of Halite # type slabs are typical examples of Tasker 3 structures. We # will test this algo to generate a Tasker 2 structure instead 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'] MgO = Structure(lattice, species, frac_coords) MgO.add_oxidation_state_by_element({"Mg": 2, "O": -6}) slabgen = SlabGenerator(MgO, (1, 1, 1), 10, 10, max_normal_search=1) # We generate the Tasker 3 structure first slab = slabgen.get_slabs()[0] self.assertFalse(slab.is_symmetric()) self.assertTrue(slab.is_polar()) # Now to generate the Tasker 2 structure, we must # ensure there are enough ions on top to move around slab.make_supercell([2, 1, 1]) slabs = slab.get_tasker2_slabs() # Check if our Tasker 2 slab is nonpolar and symmetric for slab in slabs: self.assertTrue(slab.is_symmetric()) self.assertFalse(slab.is_polar())
def make_super_cell_pymatgen (jdata) : make_unit_cell(jdata) out_dir = jdata['out_dir'] path_uc = os.path.join(out_dir, global_dirname_02) from_path = path_uc from_file = os.path.join(from_path, 'POSCAR.unit') ss = Structure.from_file(from_file) all_millers = jdata['millers'] path_sc = os.path.join(out_dir, global_dirname_02) z_min = jdata['z_min'] super_cell = jdata['super_cell'] cwd = os.getcwd() path_work = (path_sc) path_work = os.path.abspath(path_work) pcpy_cmd = os.path.join(cwd, 'tools') pcpy_cmd = os.path.join(pcpy_cmd, 'poscar_copy.py') os.chdir(path_work) for miller in all_millers: miller_str="" for ii in miller : miller_str += str(ii) path_cur_surf = create_path('surf-'+miller_str) os.chdir(path_cur_surf) slabgen = SlabGenerator(ss, miller, z_min, 1e-3) all_slabs = slabgen.get_slabs() print("Miller %s: The slab has %s termination, use the first one" %(str(miller), len(all_slabs))) all_slabs[0].to('POSCAR', 'POSCAR') if super_cell[0] > 1 or super_cell[1] > 1 : sp.check_call(pcpy_cmd + ' -n %d %d %d POSCAR POSCAR ' % (super_cell[0], super_cell[1], 1), shell = True) os.chdir(path_work) os.chdir(cwd)
def test_normal_search(self): fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) for miller in [(1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)]: gen = SlabGenerator(fcc, miller, 10, 10) gen_normal = SlabGenerator(fcc, miller, 10, 10, max_normal_search=max(miller)) slab = gen_normal.get_slab() self.assertAlmostEqual(slab.lattice.alpha, 90) self.assertAlmostEqual(slab.lattice.beta, 90) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) graphite = self.get_structure("Graphite") for miller in [(1, 0, 0), (1, 1, 0), (0, 0, 1), (2, 1, 1)]: gen = SlabGenerator(graphite, miller, 10, 10) gen_normal = SlabGenerator(graphite, miller, 10, 10, max_normal_search=max(miller)) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) sc = Structure(Lattice.hexagonal(3.32, 5.15), ["Sc", "Sc"], [[1/3, 2/3, 0.25], [2/3, 1/3, 0.75]]) gen = SlabGenerator(sc, (1, 1, 1), 10, 10, max_normal_search=1) self.assertAlmostEqual(gen.oriented_unit_cell.lattice.angles[1], 90)
def test_surface_sites_and_symmetry(self): # test if surfaces are equivalent by using # Laue symmetry and surface site equivalence for bool in [True, False]: # We will also set the slab to be centered and # off centered in order to test the center of mass slabgen = SlabGenerator(self.agfcc, (3, 1, 0), 10, 10, center_slab=bool) slab = slabgen.get_slabs()[0] surf_sites_dict = slab.get_surface_sites() self.assertEqual(len(surf_sites_dict["top"]), len(surf_sites_dict["bottom"])) total_surf_sites = sum([len(surf_sites_dict[key]) for key in surf_sites_dict.keys()]) self.assertTrue(slab.is_symmetric()) self.assertEqual(total_surf_sites / 2, 4) self.assertTrue(slab.have_equivalent_surfaces()) # Test if the ratio of surface sites per area is # constant, ie are the surface energies the same r1 = total_surf_sites / (2 * slab.surface_area) slabgen = SlabGenerator(self.agfcc, (3, 1, 0), 10, 10, primitive=False) slab = slabgen.get_slabs()[0] surf_sites_dict = slab.get_surface_sites() total_surf_sites = sum([len(surf_sites_dict[key]) for key in surf_sites_dict.keys()]) r2 = total_surf_sites / (2 * slab.surface_area) self.assertArrayEqual(r1, r2)
def test_get_orthogonal_c_slab(self): TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False) trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10) TeI_slabs = trclnc_TeI.get_slabs() slab = TeI_slabs[0] norm_slab = slab.get_orthogonal_c_slab() self.assertAlmostEqual(norm_slab.lattice.angles[0], 90) self.assertAlmostEqual(norm_slab.lattice.angles[1], 90)
def create_surface2(st, miller_index, min_slab_size=10, min_vacuum_size=10, surface_i=0, oxidation=None, suf='', primitive=None, symmetrize=False): """ INPUT: st (Structure) - Initial input structure. Note that to ensure that the miller indices correspond to usual crystallographic definitions, you should supply a conventional unit cell structure. miller_index ([h, k, l]): Miller index of plane parallel to surface. Note that this is referenced to the input structure. If you need this to be based on the conventional cell, you should supply the conventional structure. oxidation (dic) - dictionary of effective oxidation states, e. g. {'Y':'Y3+', 'Ba':'Ba2+', 'Co':'Co2.25+', 'O':'O2-'} allows to calculate dipole moment surface_i (int) - choose particular surface min_slab_size (float) - minimum slab size min_vacuum_size (float) - vacuum thicknes in A """ from pymatgen.core.surface import SlabGenerator from pymatgen.io.vasp.inputs import Poscar from geo import replic pm = st.convert2pymatgen(oxidation=oxidation) # pm = st.convert2pymatgen() slabgen = SlabGenerator(pm, miller_index, min_slab_size, min_vacuum_size, primitive=primitive) # print(slabgen.oriented_unit_cell) slabs = slabgen.get_slabs(symmetrize=symmetrize) printlog( len(slabs), 'surfaces were generated, choose required surface using *surface_i* argument\nWriting POSCARs to xyz', imp='y') for i, slab in enumerate(slabs): pos = Poscar(slab) pos.write_file('xyz/POSCAR_suf' + str(i) + str(suf)) return slabs
def __init__(self, strt, hkl=[1,1,1], min_thick=10, min_vac=10, supercell=[1,1,1], name=None, adsorb_on_species=None, adatom_on_lig=None, ligand=None, displacement=1.0, surface_coverage=None, scell_nmax=10, coverage_tol=0.25, solvent=None, start_from_slab=False, validate_proximity=False, to_unit_cell=False, coords_are_cartesian=False, primitive=True, from_ase=False, x_shift=0, y_shift=0, rot=[0,0,0], center_slab=True): self.from_ase = from_ase vac_extension = 0 if ligand is not None: vac_extension = ligand.max_dist if isinstance(strt, Structure) and not isinstance(strt, Slab): self.min_vac = min_vac + vac_extension if self.from_ase: strt = get_ase_slab(strt, hkl=hkl, min_thick=min_thick, min_vac=min_vac + vac_extension, center_slab=center_slab) else: strt = SlabGenerator(strt, hkl, min_thick, min_vac + vac_extension, center_slab=center_slab, primitive = primitive).get_slab() strt.make_supercell(supercell) else: self.min_vac = min_vac Slab.__init__(self, strt.lattice, strt.species_and_occu, strt.frac_coords, miller_index=strt.miller_index, oriented_unit_cell=strt.oriented_unit_cell, shift=strt.shift, scale_factor=strt.scale_factor, validate_proximity=validate_proximity, to_unit_cell=to_unit_cell, coords_are_cartesian=coords_are_cartesian, site_properties=strt.site_properties, energy=strt.energy ) self.strt= strt self.name = name self.hkl = hkl self.min_thick = min_thick self.supercell = supercell self.ligand = ligand self.slab = strt self.displacement = displacement self.solvent = solvent self.surface_coverage = surface_coverage self.adsorb_on_species = adsorb_on_species self.adatom_on_lig = adatom_on_lig self.scell_nmax = scell_nmax self.coverage_tol = coverage_tol self.x_shift = x_shift self.y_shift = y_shift self.rot = rot
def test_dipole_and_is_polar(self): self.assertArrayAlmostEqual(self.zno55.dipole, [0, 0, 0]) self.assertFalse(self.zno55.is_polar()) cscl = self.get_structure("CsCl") cscl.add_oxidation_state_by_element({"Cs": 1, "Cl": -1}) slab = SlabGenerator(cscl, [1, 0, 0], 5, 5, lll_reduce=False, center_slab=False).get_slab() self.assertArrayAlmostEqual(slab.dipole, [-4.209, 0, 0]) self.assertTrue(slab.is_polar())
def enumerate_surfaces(bulk_atoms, mpid, max_miller=MAX_MILLER): ''' Enumerate all the symmetrically distinct surfaces of a bulk structure. It will not enumerate surfaces with Miller indices above the `max_miller` argument. Note that we also look at the bottoms of slabs if they are distinct from the top. If they are distinct, we flip the slab so the bottom is pointing upwards. Args: bulk_atoms `ase.Atoms` object of the bulk you want to enumerate surfaces from. mpid String indicating the the Materials Project ID number of the bulk that was selected. max_miller An integer indicating the maximum Miller index of the surfaces you are willing to enumerate. Increasing this argument will increase the number of surfaces, but the surfaces will generally become larger. Returns: all_slabs_info A list of 4-tuples containing: `pymatgen.Structure` objects, 3-tuples for the Miller indices, floats for the shifts, and Booleans for "top". ''' all_slabs = CACHE.get(mpid) if all_slabs is None: bulk_struct = standardize_bulk(bulk_atoms) all_slabs_info = [] for millers in get_symmetrically_distinct_miller_indices( bulk_struct, MAX_MILLER): slab_gen = SlabGenerator(initial_structure=bulk_struct, miller_index=millers, min_slab_size=7., min_vacuum_size=20., lll_reduce=False, center_slab=True, primitive=True, max_normal_search=1) slabs = slab_gen.get_slabs(tol=0.3, bonds=None, max_broken_bonds=0, symmetrize=False) # If the bottoms of the slabs are different than the tops, then we want # to consider them, too flipped_slabs = [ flip_struct(slab) for slab in slabs if is_structure_invertible(slab) is False ] # Concatenate all the results together slabs_info = [(slab, millers, slab.shift, True) for slab in slabs] flipped_slabs_info = [(slab, millers, slab.shift, False) for slab in flipped_slabs] all_slabs_info.extend(slabs_info + flipped_slabs_info) CACHE.set(mpid, all_slabs_info) return all_slabs_info
def test_dipole_and_is_polar(self): self.assertArrayAlmostEqual(self.zno55.dipole, [0, 0, 0]) self.assertFalse(self.zno55.is_polar()) cscl = self.get_structure("CsCl") cscl.add_oxidation_state_by_element({"Cs": 1, "Cl": -1}) slab = SlabGenerator(cscl, [1, 0, 0], 5, 5, reorient_lattice=False, lll_reduce=False, center_slab=False).get_slab() self.assertArrayAlmostEqual(slab.dipole, [-4.209, 0, 0]) self.assertTrue(slab.is_polar())
def generate_selected_slab(structs,fnames,miller_index,min_slab_size,min_vac_size): for struct,fname in zip(structs,fnames): slab=SlabGenerator(struct,miller_index,min_slab_size=min_slab_size,min_vacuum_size=min_vac_size,lll_reduce=True) slab_struct=slab.get_slab() slab_struct.sort() miller_str=[str(i) for i in miller_index] filename='_'.join(miller_str)+"_"+fname+'.vasp' slab_struct.to(filename=filename,fmt='POSCAR')
def get_dimensionality(structure, max_hkl=2, el_radius_updates=None, min_slab_size=5, min_vacuum_size=5, standardize=True, bonds=None): """ This method returns whether a structure is 3D, 2D (layered), or 1D (linear chains or molecules) according to the algorithm published in Gorai, P., Toberer, E. & Stevanovic, V. Computational Identification of Promising Thermoelectric Materials Among Known Quasi-2D Binary Compounds. J. Mater. Chem. A 2, 4136 (2016). Note that a 1D structure detection might indicate problems in the bonding algorithm, particularly for ionic crystals (e.g., NaCl) Users can change the behavior of bonds detection by passing either el_radius_updates to update atomic radii for auto-detection of max bond distances, or bonds to explicitly specify max bond distances for atom pairs. Note that if you pass both, el_radius_updates are ignored. Args: structure: (Structure) structure to analyze dimensionality for max_hkl: (int) max index of planes to look for layers el_radius_updates: (dict) symbol->float to update atomic radii min_slab_size: (float) internal surface construction parameter min_vacuum_size: (float) internal surface construction parameter standardize (bool): whether to standardize the structure before analysis. Set to False only if you already have the structure in a convention where layers / chains will be along low <hkl> indexes. bonds ({(specie1, specie2): max_bond_dist}: bonds are specified as a dict of tuples: float of specie1, specie2 and the max bonding distance. For example, PO4 groups may be defined as {("P", "O"): 3}. Returns: (int) the dimensionality of the structure - 1 (molecules/chains), 2 (layered), or 3 (3D) """ if standardize: structure = SpacegroupAnalyzer(structure). \ get_conventional_standard_structure() if not bonds: bonds = get_max_bond_lengths(structure, el_radius_updates) num_surfaces = 0 for h in range(max_hkl): for k in range(max_hkl): for l in range(max_hkl): if max([h, k, l]) > 0 and num_surfaces < 2: sg = SlabGenerator(structure, (h, k, l), min_slab_size=min_slab_size, min_vacuum_size=min_vacuum_size) slabs = sg.get_slabs(bonds) for _ in slabs: num_surfaces += 1 return 3 - min(num_surfaces, 2)
def pmg_surfer(mpid='', vacuum=15, mat=None, max_index=1, min_slab_size=15): if mat == None: with MPRester() as mp: mat = mp.get_structure_by_material_id(mpid) if mpid == '': print('Provide structure') sg_mat = SpacegroupAnalyzer(mat) mat_cvn = sg_mat.get_conventional_standard_structure() mat_cvn.sort() indices = get_symmetrically_distinct_miller_indices(mat_cvn, max_index) #ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn) structures = [] pos = Poscar(mat_cvn) try: pos.comment = str('sbulk') + str('@') + str('vac') + str(vacuum) + str( '@') + str('size') + str(min_slab_size) except: pass structures.append(pos) mat_cvn.to(fmt='poscar', filename=str('POSCAR-') + str('cvn') + str('.vasp')) for i in indices: slab = SlabGenerator(initial_structure=mat_cvn, miller_index=i, min_slab_size=min_slab_size, min_vacuum_size=vacuum, lll_reduce=False, center_slab=True, primitive=False).get_slab() normal_slab = slab.get_orthogonal_c_slab() slab_pymatgen = Poscar(normal_slab).structure #ase_slab.center(vacuum=vacuum, axis=2) #slab_pymatgen = AseAtomsAdaptor().get_structure(ase_slab) xy_size = min_slab_size dim1 = int((float(xy_size) / float(max(abs(slab_pymatgen.lattice.matrix[0]))))) + 1 dim2 = int( float(xy_size) / float(max(abs(slab_pymatgen.lattice.matrix[1])))) + 1 slab_pymatgen.make_supercell([dim1, dim2, 1]) slab_pymatgen.sort() surf_name = '_'.join(map(str, i)) pos = Poscar(slab_pymatgen) try: pos.comment = str("Surf-") + str(surf_name) + str('@') + str( 'vac') + str(vacuum) + str('@') + str('size') + str( min_slab_size) except: pass pos.write_file(filename=str('POSCAR-') + str("Surf-") + str(surf_name) + str('.vasp')) structures.append(pos) return structures
def make_surface(file, facet=(1, 1, 1), verbose=True): # read in structure st = Structure.from_file(file) # generate slabs with the given facet slabgen = SlabGenerator(st, facet, 6, 16, center_slab=True) all_slabs = slabgen.get_slabs(symmetrize=True) if verbose: print("The slab has %s termination." % (len(all_slabs))) # save surfaces for i, slab in enumerate(all_slabs): slab.to('POSCAR', 'POSCAR_SURF_' + str(i).zfill(2))
def test_from_slabs(self): si_struct = self.get_structure("Si") sio2_struct = self.get_structure("SiO2") si_conventional = SpacegroupAnalyzer(si_struct).get_conventional_standard_structure() sio2_conventional = SpacegroupAnalyzer(sio2_struct).get_conventional_standard_structure() si_slab = SlabGenerator(si_conventional, (1, 1, 1), 5, 10, reorient_lattice=True).get_slab() sio2_slab = SlabGenerator(sio2_conventional, (1, 0, 0), 5, 10, reorient_lattice=True).get_slab() interface = Interface.from_slabs(film_slab=si_slab, substrate_slab=sio2_slab) assert isinstance(interface, Interface)
def test_normal_search(self): fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) for miller in [(1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)]: gen = SlabGenerator(fcc, miller, 10, 10) gen_normal = SlabGenerator(fcc, miller, 10, 10, max_normal_search=max(miller)) slab = gen_normal.get_slab() self.assertAlmostEqual(slab.lattice.alpha, 90) self.assertAlmostEqual(slab.lattice.beta, 90) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell))
def test_triclinic_TeI(self): # Test case for a triclinic structure of TeI. Only these three # Miller indices are used because it is easier to identify which # atoms should be in a surface together. The closeness of the sites # in other Miller indices can cause some ambiguity when choosing a # higher tolerance. numb_slabs = {(0, 0, 1): 5, (0, 1, 0): 3, (1, 0, 0): 7} TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False) for k, v in numb_slabs.items(): trclnc_TeI = SlabGenerator(TeI, k, 10, 10) TeI_slabs = trclnc_TeI.get_slabs() self.assertEqual(v, len(TeI_slabs))
def enumerate_surfaces(self, max_miller=MAX_MILLER): ''' Enumerate all the symmetrically distinct surfaces of a bulk structure. It will not enumerate surfaces with Miller indices above the `max_miller` argument. Note that we also look at the bottoms of surfaces if they are distinct from the top. If they are distinct, we flip the surface so the bottom is pointing upwards. Args: bulk_atoms `ase.Atoms` object of the bulk you want to enumerate surfaces from. max_miller An integer indicating the maximum Miller index of the surfaces you are willing to enumerate. Increasing this argument will increase the number of surfaces, but the surfaces will generally become larger. Returns: all_slabs_info A list of 4-tuples containing: `pymatgen.Structure` objects for surfaces we have enumerated, the Miller indices, floats for the shifts, and Booleans for "top". ''' bulk_struct = self.standardize_bulk(self.bulk_atoms) all_slabs_info = [] for millers in get_symmetrically_distinct_miller_indices(bulk_struct, MAX_MILLER): slab_gen = SlabGenerator(initial_structure=bulk_struct, miller_index=millers, min_slab_size=7., min_vacuum_size=20., lll_reduce=False, center_slab=True, primitive=True, max_normal_search=1) slabs = slab_gen.get_slabs(tol=0.3, bonds=None, max_broken_bonds=0, symmetrize=False) # Additional filtering for the 2D materials' slabs if self.mpid in COVALENT_MATERIALS_MPIDS: slabs = [slab for slab in slabs if is_2D_slab_reasonsable(slab) is True] # If the bottoms of the slabs are different than the tops, then we want # to consider them, too if len(slabs) != 0: flipped_slabs_info = [(self.flip_struct(slab), millers, slab.shift, False) for slab in slabs if self.is_structure_invertible(slab) is False] # Concatenate all the results together slabs_info = [(slab, millers, slab.shift, True) for slab in slabs] all_slabs_info.extend(slabs_info + flipped_slabs_info) return all_slabs_info
def setUp(self): if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = test_dir s = PymatgenTest.get_structure("Li2O") gen = SlabGenerator(s, (1, 0, 0), 10, 10) self.slab = gen.get_slab() self.bulk = self.slab.oriented_unit_cell vis_bulk = MVLSlabSet(self.bulk, bulk=True) vis = MVLSlabSet(self.slab) self.d_bulk = vis_bulk.all_input self.d_slab = vis.all_input
def nonstoichiometric_symmetrized_slab(self): # For the (111) halite slab, sometimes a nonstoichiometric # system is preferred over the stoichiometric Tasker 2. slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10, max_normal_search=1) slabs = slabgen.get_slabs(symmetrize=True) # We should end up with two terminations, one with # an Mg rich surface and another O rich surface self.assertEqual(len(slabs), 2) for slab in slabs: self.assertTrue(slab.is_symmetric())
def test_normal_search(self): fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) for miller in [(1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)]: gen = SlabGenerator(fcc, miller, 10, 10) gen_normal = SlabGenerator(fcc, miller, 10, 10, max_normal_search=max(miller)) slab = gen_normal.get_slab() self.assertAlmostEqual(slab.lattice.alpha, 90) self.assertAlmostEqual(slab.lattice.beta, 90) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) graphite = self.get_structure("Graphite") for miller in [(1, 0, 0), (1, 1, 0), (0, 0, 1), (2, 1, 1)]: gen = SlabGenerator(graphite, miller, 10, 10) gen_normal = SlabGenerator(graphite, miller, 10, 10, max_normal_search=max(miller)) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) sc = Structure(Lattice.hexagonal(3.32, 5.15), ["Sc", "Sc"], [[1 / 3, 2 / 3, 0.25], [2 / 3, 1 / 3, 0.75]]) gen = SlabGenerator(sc, (1, 1, 1), 10, 10, max_normal_search=1) self.assertAlmostEqual(gen.oriented_unit_cell.lattice.angles[1], 90)
def setUp(self): s = self.get_structure("Li2O") gen = SlabGenerator(s, (1, 0, 0), 10, 10) self.slab = gen.get_slab() self.bulk = self.slab.oriented_unit_cell vis_bulk = MVLSlabSet(self.bulk, bulk=True) vis = MVLSlabSet(self.slab) vis_dipole = MVLSlabSet(self.slab, auto_dipole=True) self.d_bulk = vis_bulk.get_vasp_input() self.d_slab = vis.get_vasp_input() self.d_dipole = vis_dipole.get_vasp_input() self.vis = vis warnings.simplefilter("ignore")
def setUp(self): if "VASP_PSP_DIR" not in os.environ: os.environ["VASP_PSP_DIR"] = test_dir s = PymatgenTest.get_structure("Li2O") gen = SlabGenerator(s, (1, 0, 0), 10, 10) vis_bulk = MVLSlabSet(bulk=True) vis = MVLSlabSet() vis_bulk_gpu = MVLSlabSet(bulk=True, gpu=True) self.slab = gen.get_slab() self.bulk = self.slab.oriented_unit_cell self.d_bulk = vis_bulk.get_all_vasp_input(self.bulk) self.d_slab = vis.get_all_vasp_input(self.slab) self.d_bulk_gpu = vis_bulk_gpu.get_all_vasp_input(self.bulk)
def generate_selected_slab(in_str): tmp_list = in_str.split('|') miller_index = [int(x) for x in tmp_list[0].strip().split()] min_slab_size = float(tmp_list[1]) min_vac_size = float(tmp_list[2]) slab = SlabGenerator(struct, miller_index, min_slab_size=min_slab_size, min_vacuum_size=min_vac_size, lll_reduce=True) slab_struct = slab.get_slab() slab_struct.sort() miller_str = [str(i) for i in miller_index] filename = '_'.join(miller_str) + '.vasp' slab_struct.to(filename=filename, fmt='POSCAR')
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 setUp(self): zno1 = Structure.from_file(get_path("ZnO-wz.cif"), primitive=False) zno55 = SlabGenerator(zno1, [1, 0, 0], 5, 5, lll_reduce=False, center_slab=False).get_slab() Ti = Structure( Lattice.hexagonal(4.6, 2.82), ["Ti", "Ti", "Ti"], [[0.000000, 0.000000, 0.000000], [0.333333, 0.666667, 0.500000], [0.666667, 0.333333, 0.500000]]) Ag_fcc = Structure( Lattice.cubic(4.06), ["Ag", "Ag", "Ag", "Ag"], [[0.000000, 0.000000, 0.000000], [0.000000, 0.500000, 0.500000], [0.500000, 0.000000, 0.500000], [0.500000, 0.500000, 0.000000]]) self.ti = Ti self.agfcc = Ag_fcc self.zno1 = zno1 self.zno55 = zno55 self.h = Structure(Lattice.cubic(3), ["H"], [[0, 0, 0]]) self.libcc = Structure(Lattice.cubic(3.51004), ["Li", "Li"], [[0, 0, 0], [0.5, 0.5, 0.5]])
def setUp(self): if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = test_dir s = PymatgenTest.get_structure("Li2O") gen = SlabGenerator(s, (1, 0, 0), 10, 10) self.slab = gen.get_slab() self.bulk = self.slab.oriented_unit_cell vis_bulk = MVLSlabSet(self.bulk, bulk=True) vis = MVLSlabSet(self.slab) vis_dipole = MVLSlabSet(self.slab, auto_dipole=True) self.d_bulk = vis_bulk.all_input self.d_slab = vis.all_input self.d_dipole = vis_dipole.all_input self.vis = vis warnings.simplefilter("ignore")
def test_get_orthogonal_c_slab_site_props(self): TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False) trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10) TeI_slabs = trclnc_TeI.get_slabs() slab = TeI_slabs[0] # Add site property to slab sd_list = [[True, True, True] for site in slab.sites] new_sp = slab.site_properties new_sp['selective_dynamics'] = sd_list slab_with_site_props = slab.copy(site_properties=new_sp) # Get orthogonal slab norm_slab = slab_with_site_props.get_orthogonal_c_slab() # Check if site properties is consistent (or kept) self.assertEqual(slab_with_site_props.site_properties, norm_slab.site_properties)
def test_get_slab(self): s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) s = gen.get_slab(0.25) self.assertAlmostEqual(s.lattice.abc[2], 20.820740000000001) fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) gen = SlabGenerator(fcc, [1, 1, 1], 10, 10) slab = gen.get_slab() gen = SlabGenerator(fcc, [1, 1, 1], 10, 10, primitive=False) slab_non_prim = gen.get_slab() self.assertEqual(len(slab), 6) self.assertEqual(len(slab_non_prim), len(slab) * 4) #Some randomized testing of cell vectors for i in range(1, 231): i = random.randint(1, 230) sg = SpaceGroup.from_int_number(i) if sg.crystal_system == "hexagonal" or (sg.crystal_system == \ "trigonal" and sg.symbol.endswith("H")): latt = Lattice.hexagonal(5, 10) else: #Cubic lattice is compatible with all other space groups. latt = Lattice.cubic(5) s = Structure.from_spacegroup(i, latt, ["H"], [[0, 0, 0]]) miller = (0, 0, 0) while miller == (0, 0, 0): miller = (random.randint(0, 6), random.randint(0, 6), random.randint(0, 6)) gen = SlabGenerator(s, miller, 10, 10) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0)
def test_get_tasker2_slabs(self): # The uneven distribution of ions on the (111) facets of Halite # type slabs are typical examples of Tasker 3 structures. We # will test this algo to generate a Tasker 2 structure instead slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10, max_normal_search=1) # We generate the Tasker 3 structure first slab = slabgen.get_slabs()[0] self.assertFalse(slab.is_symmetric()) self.assertTrue(slab.is_polar()) # Now to generate the Tasker 2 structure, we must # ensure there are enough ions on top to move around slab.make_supercell([2, 1, 1]) slabs = slab.get_tasker2_slabs() # Check if our Tasker 2 slab is nonpolar and symmetric for slab in slabs: self.assertTrue(slab.is_symmetric()) self.assertFalse(slab.is_polar())
def test_get_slab_regions(self): # If a slab layer in the slab cell is not completely inside # the cell (noncontiguous), check that get_slab_regions will # be able to identify where the slab layers are located s = self.get_structure("LiFePO4") slabgen = SlabGenerator(s, (0, 0, 1), 15, 15) slab = slabgen.get_slabs()[0] slab.translate_sites([i for i, site in enumerate(slab)], [0, 0, -0.25]) bottom_c, top_c = [], [] for site in slab: if site.frac_coords[2] < 0.5: bottom_c.append(site.frac_coords[2]) else: top_c.append(site.frac_coords[2]) ranges = get_slab_regions(slab) self.assertEqual(tuple(ranges[0]), (0, max(bottom_c))) self.assertEqual(tuple(ranges[1]), (min(top_c), 1))
def test_move_to_other_side(self): # Tests to see if sites are added to opposite side s = self.get_structure("LiFePO4") slabgen = SlabGenerator(s, (0, 0, 1), 10, 10, center_slab=True) slab = slabgen.get_slab() surface_sites = slab.get_surface_sites() # check if top sites are moved to the bottom top_index = [ss[1] for ss in surface_sites["top"]] slab = slabgen.move_to_other_side(slab, top_index) all_bottom = [slab[i].frac_coords[2] < slab.center_of_mass[2] for i in top_index] self.assertTrue(all(all_bottom)) # check if bottom sites are moved to the top bottom_index = [ss[1] for ss in surface_sites["bottom"]] slab = slabgen.move_to_other_side(slab, bottom_index) all_top = [slab[i].frac_coords[2] > slab.center_of_mass[2] for i in bottom_index] self.assertTrue(all(all_top))
def test_apply_transformation(self): s = self.get_structure("LiFePO4") trans = SlabTransformation([0, 0, 1], 10, 10, shift = 0.25) gen = SlabGenerator(s, [0, 0, 1], 10, 10) slab_from_gen = gen.get_slab(0.25) slab_from_trans = trans.apply_transformation(s) self.assertArrayAlmostEqual(slab_from_gen.lattice.matrix, slab_from_trans.lattice.matrix) self.assertArrayAlmostEqual(slab_from_gen.cart_coords, slab_from_trans.cart_coords) fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) trans = SlabTransformation([1, 1, 1], 10, 10) slab_from_trans = trans.apply_transformation(fcc) gen = SlabGenerator(fcc, [1, 1, 1], 10, 10) slab_from_gen = gen.get_slab() self.assertArrayAlmostEqual(slab_from_gen.lattice.matrix, slab_from_trans.lattice.matrix) self.assertArrayAlmostEqual(slab_from_gen.cart_coords, slab_from_trans.cart_coords)
def test_get_slabs(self): gen = SlabGenerator(self.get_structure("CsCl"), [0, 0, 1], 10, 10) #Test orthogonality of some internal variables. a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) self.assertEqual(len(gen.get_slabs()), 1) s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) self.assertEqual(len(gen.get_slabs()), 5) self.assertEqual(len(gen.get_slabs(bonds={("P", "O"): 3})), 2) # There are no slabs in LFP that does not break either P-O or Fe-O # bonds for a miller index of [0, 0, 1]. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3})), 0) #If we allow some broken bonds, there are a few slabs. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3}, max_broken_bonds=2)), 2) # At this threshold, only the origin and center Li results in # clustering. All other sites are non-clustered. So the of # slabs is of sites in LiFePO4 unit cell - 2 + 1. self.assertEqual(len(gen.get_slabs(tol=1e-4)), 15) LiCoO2 = Structure.from_file(get_path("icsd_LiCoO2.cif"), primitive=False) gen = SlabGenerator(LiCoO2, [0, 0, 1], 10, 10) lco = gen.get_slabs(bonds={("Co", "O"): 3}) self.assertEqual(len(lco), 1) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0)
def create_slab(self, vacuum=12, thickness=10): """ set the vacuum spacing, slab thickness and call sd_flags for top 2 layers returns the poscar corresponding to the modified structure """ strt_structure = self.input_structure.copy() if self.from_ase: slab_struct = get_ase_slab(strt_structure, hkl=self.system['hkl'], min_thick=thickness, min_vac=vacuum) else: slab_struct= SlabGenerator(initial_structure= strt_structure, miller_index= self.system['hkl'], min_slab_size= thickness, min_vacuum_size=vacuum, lll_reduce=False, center_slab=True, primitive=False).get_slab() slab_struct.sort() sd = self.set_sd_flags(slab_struct) comment = 'VAC'+str(vacuum)+'THICK'+str(thickness) return Poscar(slab_struct, comment=comment, selective_dynamics=sd)
def run_task(self, fw_spec): """ Required Parameters: folder (str path): Location where vasp inputs are to be written custodian_params (dict **kwargs): Contains the job and the scratch directory for a custodian run vaspdbinsert_parameters (dict **kwargs): Contains informations needed to acess a DB, eg, host, port, password etc. Optional Parameters: min_vac_size (float): Size of vacuum layer of slab in Angstroms min_slab_size (float): Size of slab layer of slab in Angstroms angle_tolerance (int): See SpaceGroupAnalyzer in analyzer.py user_incar_settings (dict): See launch_workflow() method in CreateSurfaceWorkflow class k_product (dict): See launch_workflow() method in CreateSurfaceWorkflow class potcar_functional (dict): See launch_workflow() method in CreateSurfaceWorkflow class symprec (float): See SpaceGroupAnalyzer in analyzer.py terminations (bool): Determines whether or not to consider different terminations in a slab. If true, each slab with a specific shift value will have its own Firework and each of the slab calculations will run in parallel. Defaults to false which sets the shift value to 0. """ dec = MontyDecoder() folder = dec.process_decoded(self.get("folder")) cwd = dec.process_decoded(self.get("cwd")) symprec = dec.process_decoded(self.get("symprec", 0.001)) angle_tolerance = dec.process_decoded(self.get("angle_tolerance", 5)) terminations = dec.process_decoded(self.get("terminations", False)) custodian_params = dec.process_decoded(self.get("custodian_params")) vaspdbinsert_parameters = \ dec.process_decoded(self.get("vaspdbinsert_parameters")) user_incar_settings = \ dec.process_decoded(self.get("user_incar_settings", MPSlabVaspInputSet().incar_settings)) k_product = \ dec.process_decoded(self.get("k_product", 50)) potcar_functional = \ dec.process_decoded(self.get("potcar_fuctional", 'PBE')) min_slab_size = dec.process_decoded(self.get("min_slab_size", 10)) min_vacuum_size = dec.process_decoded(self.get("min_vacuum_size", 10)) miller_index = dec.process_decoded(self.get("miller_index")) print 'about to make mplb' mplb = MPSlabVaspInputSet(user_incar_settings=user_incar_settings, k_product=k_product, potcar_functional=potcar_functional, ediff_per_atom=False) # Create slabs from the relaxed oriented unit cell. Since the unit # cell is already oriented with the miller index, entering (0,0,1) # into SlabGenerator is the same as obtaining a slab in the # orienetation of the original miller index. print 'about to copy contcar' contcar = Poscar.from_file("%s/CONTCAR.relax2.gz" %(cwd+folder)) relax_orient_uc = contcar.structure print 'made relaxed oriented structure' print relax_orient_uc print 'making slab' slabs = SlabGenerator(relax_orient_uc, (0,0,1), min_slab_size=min_slab_size, min_vacuum_size=min_vacuum_size, max_normal_search=max(miller_index)) # Whether or not to create a list of Fireworks # based on different slab terminations print 'deciding terminations' slab_list = slabs.get_slabs() if terminations else [slabs.get_slab()] qe = QueryEngine(**vaspdbinsert_parameters) optional_data = ["state"] print 'query bulk entry for job completion' bulk_entry = qe.get_entries({'chemsys': relax_orient_uc.composition.reduced_formula, 'structure_type': 'oriented_unit_cell', 'miller_index': miller_index}, optional_data=optional_data) print 'chemical formula', relax_orient_uc.composition.reduced_formula print 'fomular data type is ', type(relax_orient_uc.composition.reduced_formula) print 'checking job completion' print bulk_entry for entry in bulk_entry: print 'for loop' print entry.data['state'] if entry.data['state'] != 'successful': print "%s bulk calculations were incomplete, cancelling FW" \ %(relax_orient_uc.composition.reduced_formula) return FWAction() else: print entry.data['state'] FWs = [] for slab in slab_list: print slab new_folder = folder.replace('bulk', 'slab')+'_shift%s' \ %(slab.shift) # Will continue an incomplete job from a previous contcar file if it exists print 'cwd is %s' %(os.getcwd()) print 'the folder is %s' %(new_folder) print os.path.join(os.getcwd(), new_folder) print cwd+'/'+new_folder path = cwd+'/'+new_folder # path = os.path.join(os.getcwd(), folder) newfolder = os.path.join(path, 'prev_run') # print 'check if conditions for continuing calculations have been satisfied' # print 'check for the following path: %s' %(path) # print os.path.exists(path) # print os.path.exists(os.path.join(path, 'CONTCAR.gz')) # print os.stat(os.path.join(path, 'CONTCAR.gz')).st_size !=0 def continue_vasp(contcar): print folder, 'already exists, will now continue calculation' print 'making prev_run folder' os.system('mkdir %s' %(newfolder)) print 'moving outputs to prev_run' os.system('mv %s/* %s/prev_run' %(path, path)) print 'moving outputs as inputs for next calculation' os.system('cp %s/%s %s/INCAR %s/POTCAR %s/KPOINTS %s' %(newfolder, contcar, newfolder, newfolder, newfolder, path)) print 'unzipping new inputs' os.system('gunzip %s/*' %(path)) print 'copying contcar as new poscar' if contcar == 'CONTCAR.relax1.gz': os.system('mv %s/CONTCAR.relax1 %s/POSCAR' %(path , path)) else: os.system('mv %s/CONTCAR %s/POSCAR' %(path , path)) if os.path.exists(path) and \ os.path.exists(os.path.join(path, 'CONTCAR')) and \ os.stat(os.path.join(path, 'CONTCAR')).st_size !=0: continue_vasp('CONTCAR') elif os.path.exists(path) and \ os.path.exists(os.path.join(path, 'CONTCAR.gz')) \ and os.stat(os.path.join(path, 'CONTCAR.gz')).st_size !=0: continue_vasp('CONTCAR.gz') elif os.path.exists(path) and \ os.path.exists(os.path.join(path, 'CONTCAR.relax1.gz')) and \ os.stat(os.path.join(path, 'CONTCAR.relax1.gz')).st_size !=0: continue_vasp('CONTCAR.relax1.gz') else: mplb.write_input(slab, cwd+new_folder) # Writes new INCAR file based on changes made by custodian on the bulk's INCAR. # Only change in parameters between slab and bulk should be MAGMOM and ISIF if os.path.exists("%s/INCAR.relax2.gz" %(cwd+folder)): incar = Incar.from_file(cwd+folder +'/INCAR.relax2.gz') else: incar = Incar.from_file(cwd+folder +'/INCAR.relax2') if os.path.exists("%s/OUTCAR.relax2.gz" %(cwd+folder)): out = Outcar(cwd+folder+'/OUTCAR.relax2.gz') else: out = Outcar(cwd+folder+'/OUTCAR.relax2') out_mag = out.magnetization tot_mag = [mag['tot'] for mag in out_mag] magmom = np.mean(tot_mag) mag= [magmom for i in slab] incar.__setitem__('MAGMOM', mag) incar.__setitem__('ISIF', 2) incar.__setitem__('AMIN', 0.01) incar.__setitem__('AMIX', 0.2) incar.__setitem__('BMIX', 0.001) incar.__setitem__('NELMIN', 8) incar.__setitem__('ISTART', 0) incar.write_file(cwd+new_folder+'/INCAR') fw = Firework([RunCustodianTask(dir=new_folder, cwd=cwd, **custodian_params), VaspSlabDBInsertTask(struct_type="slab_cell", loc=new_folder, cwd=cwd, shift=slab.shift, surface_area=slab.surface_area, vsize=slabs.min_vac_size, ssize=slabs.min_slab_size, miller_index=miller_index, **vaspdbinsert_parameters)], name=new_folder) FWs.append(fw) return FWAction(additions=FWs)
def test_get_slabs(self): gen = SlabGenerator(self.get_structure("CsCl"), [0, 0, 1], 10, 10) # Test orthogonality of some internal variables. a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) self.assertEqual(len(gen.get_slabs()), 1) s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) self.assertEqual(len(gen.get_slabs()), 5) self.assertEqual(len(gen.get_slabs(bonds={("P", "O"): 3})), 2) # There are no slabs in LFP that does not break either P-O or Fe-O # bonds for a miller index of [0, 0, 1]. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3})), 0) # If we allow some broken bonds, there are a few slabs. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3}, max_broken_bonds=2)), 2) # At this threshold, only the origin and center Li results in # clustering. All other sites are non-clustered. So the of # slabs is of sites in LiFePO4 unit cell - 2 + 1. self.assertEqual(len(gen.get_slabs(tol=1e-4, ftol=1e-4)), 15) LiCoO2 = Structure.from_file(get_path("icsd_LiCoO2.cif"), primitive=False) gen = SlabGenerator(LiCoO2, [0, 0, 1], 10, 10) lco = gen.get_slabs(bonds={("Co", "O"): 3}) self.assertEqual(len(lco), 1) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) scc = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) gen = SlabGenerator(scc, [0, 0, 1], 10, 10) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) gen = SlabGenerator(scc, [1, 1, 1], 10, 10, max_normal_search=1) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) # Test whether using units of hkl planes instead of Angstroms for # min_slab_size and min_vac_size will give us the same number of atoms natoms = [] for a in [1, 1.4, 2.5, 3.6]: s = Structure.from_spacegroup("Im-3m", Lattice.cubic(a), ["Fe"], [[0, 0, 0]]) slabgen = SlabGenerator(s, (1, 1, 1), 10, 10, in_unit_planes=True, max_normal_search=2) natoms.append(len(slabgen.get_slab())) n = natoms[0] for i in natoms: self.assertEqual(n, i)