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 _find_matches(self) -> None: """ Finds and stores the ZSL matches """ self.zsl_matches = [] film_sg = SlabGenerator( self.film_structure, self.film_miller, min_slab_size=1, min_vacuum_size=3, in_unit_planes=True, center_slab=True, primitive=True, reorient_lattice= False, # This is necessary to not screw up the lattice ) sub_sg = SlabGenerator( self.substrate_structure, self.substrate_miller, min_slab_size=1, min_vacuum_size=3, in_unit_planes=True, center_slab=True, primitive=True, reorient_lattice= False, # This is necessary to not screw up the lattice ) film_slab = film_sg.get_slab(shift=0) sub_slab = sub_sg.get_slab(shift=0) film_vectors = film_slab.lattice.matrix substrate_vectors = sub_slab.lattice.matrix # Generate all possible interface matches self.zsl_matches = list( self.zslgen(film_vectors[:2], substrate_vectors[:2], lowest=False)) for match in self.zsl_matches: xform = get_2d_transform(film_vectors, match.film_vectors) strain, rot = polar(xform) assert np.allclose( strain, np.round(strain) ), "Film lattice vectors changed during ZSL match, check your ZSL Generator parameters" xform = get_2d_transform(substrate_vectors, match.substrate_vectors) strain, rot = polar(xform) assert np.allclose( strain, strain.astype(int) ), "Substrate lattice vectors changed during ZSL match, check your ZSL Generator parameters"
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 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_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_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 _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 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) 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)
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 __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 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.in_unit_planes, self.primitive, self.max_normal_search) slab = sg.get_slab(self.shift, self.tol) return slab
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 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 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_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 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 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 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 "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_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 get_interfaces( self, termination: Tuple[str, str], gap: float = 2.0, vacuum_over_film: float = 20.0, film_thickness: Union[float, int] = 1, substrate_thickness: Union[float, int] = 1, in_layers: bool = True, ) -> Iterator[Interface]: """ Generates interface structures given the film and substrate structure as well as the desired terminations Args: terminations: termination from self.termination list gap: gap between film and substrate vacuum_over_film: vacuum over the top of the film film_thickness: the film thickness substrate_thickness: substrate thickness in_layers: set the thickness in layer units """ film_sg = SlabGenerator( self.film_structure, self.film_miller, min_slab_size=film_thickness, min_vacuum_size=3, in_unit_planes=in_layers, center_slab=True, primitive=True, reorient_lattice= False, # This is necessary to not screw up the lattice ) sub_sg = SlabGenerator( self.substrate_structure, self.substrate_miller, min_slab_size=substrate_thickness, min_vacuum_size=3, in_unit_planes=in_layers, center_slab=True, primitive=True, reorient_lattice= False, # This is necessary to not screw up the lattice ) film_shift, sub_shift = self._terminations[termination] film_slab = film_sg.get_slab(shift=film_shift) sub_slab = sub_sg.get_slab(shift=sub_shift) for match in self.zsl_matches: # Build film superlattice super_film_transform = np.round( from_2d_to_3d( get_2d_transform(film_slab.lattice.matrix[:2], match.film_sl_vectors))).astype(int) film_sl_slab = film_slab.copy() film_sl_slab.make_supercell(super_film_transform) assert np.allclose( film_sl_slab.lattice.matrix[2], film_slab.lattice.matrix[2] ), "2D transformation affected C-axis for Film transformation" assert np.allclose( film_sl_slab.lattice.matrix[:2], match.film_sl_vectors ), "Transformation didn't make proper supercell for film" # Build substrate superlattice super_sub_transform = np.round( from_2d_to_3d( get_2d_transform(sub_slab.lattice.matrix[:2], match.substrate_sl_vectors))).astype(int) sub_sl_slab = sub_slab.copy() sub_sl_slab.make_supercell(super_sub_transform) assert np.allclose( sub_sl_slab.lattice.matrix[2], sub_slab.lattice.matrix[2] ), "2D transformation affected C-axis for Film transformation" assert np.allclose( sub_sl_slab.lattice.matrix[:2], match.substrate_sl_vectors ), "Transformation didn't make proper supercell for substrate" # Add extra info match_dict = match.as_dict() interface_properties = { k: match_dict[k] for k in match_dict.keys() if not k.startswith("@") } dfm = Deformation(match.match_transformation) strain = dfm.green_lagrange_strain interface_properties["strain"] = strain interface_properties["von_mises_strain"] = strain.von_mises_strain interface_properties["termination"] = termination interface_properties["film_thickness"] = film_thickness interface_properties["substrate_thickness"] = substrate_thickness yield (Interface.from_slabs( substrate_slab=sub_sl_slab, film_slab=film_sl_slab, gap=gap, vacuum_over_film=vacuum_over_film, interface_properties=interface_properties, ))
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)
class InterfaceBuilder: """ This class constructs the epitaxially matched interfaces between two crystalline slabs """ def __init__(self, substrate_structure, film_structure): """ Args: substrate_structure (Structure): structure of substrate film_structure (Structure): structure of film """ # Bulk structures self.original_substrate_structure = substrate_structure self.original_film_structure = film_structure self.matches = [] self.match_index = None # SlabGenerator objects for the substrate and film self.sub_sg = None self.substrate_layers = None self.film_sg = None self.film_layers = None # Structures with no vacuum self.substrate_structures = [] self.film_structures = [] # "slab" structure (with no vacuum) oriented with a direction along x-axis and ab plane normal aligned with z-axis self.oriented_substrate = None self.oriented_film = None # Strained structures with no vacuum self.strained_substrate = None self.strained_film = None # Substrate with transformation/matches applied self.modified_substrate_structures = [] self.modified_film_structures = [] # Non-stoichiometric slabs with symmetric surfaces, as generated by pymatgen. Please check, this is highly # unreliable from tests. self.sym_modified_substrate_structures = [] self.sym_modified_film_structures = [] # Interface structures self.interfaces = [] self.interface_labels = [] def get_summary_dict(self): """ Return dictionary with information about the InterfaceBuilder, with currently generated structures included. """ d = {'match': self.matches[0]} d['substrate_layers'] = self.substrate_layers d['film_layers'] = self.film_layers d['bulk_substrate'] = self.original_substrate_structure d['bulk_film'] = self.original_film_structure d['strained_substrate'] = self.strained_substrate d['strained_film'] = self.strained_film d['slab_substrates'] = self.modified_substrate_structures d['slab_films'] = self.modified_film_structures d['interfaces'] = self.interfaces d['interface_labels'] = self.interface_labels return d def write_all_structures(self): """ Write all of the structures relevant for the interface calculation to VASP POSCAR files. """ _poscar = Poscar(self.original_substrate_structure) _poscar.write_file('bulk_substrate_POSCAR') _poscar = Poscar(self.original_film_structure) _poscar.write_file('bulk_film_POSCAR') _poscar = Poscar(self.strained_substrate) _poscar.write_file('strained_substrate_POSCAR') _poscar = Poscar(self.strained_film) _poscar.write_file('strained_film_POSCAR') for i, interface in enumerate(self.modified_substrate_structures): _poscar = Poscar(interface) _poscar.write_file('slab_substrate_%d_POSCAR' % i) for i, interface in enumerate(self.modified_film_structures): _poscar = Poscar(interface) _poscar.write_file('slab_film_%d_POSCAR' % i) for i, interface in enumerate(self.film_structures): _poscar = Poscar(interface) _poscar.write_file('slab_unit_film_%d_POSCAR' % i) for label, interface in zip(self.interface_labels, self.interfaces): _poscar = Poscar(interface) _poscar.write_file('interface_%s_POSCAR' % label.replace("/", "-")) return def generate_interfaces(self, film_millers=None, substrate_millers=None, film_layers=3, substrate_layers=3, **kwargs): """ Generate a list of Interface (Structure) objects and store them to self.interfaces. Args: film_millers (list of [int]): list of film surfaces substrate_millers (list of [int]): list of substrate surfaces film_layers (int): number of layers of film to include in Interface structures. substrate_layers (int): number of layers of substrate to include in Interface structures. """ self.get_oriented_slabs(lowest=True, film_millers=film_millers, substrate_millers=substrate_millers, film_layers=film_layers, substrate_layers=substrate_layers) self.combine_slabs(**kwargs) return def get_oriented_slabs(self, film_layers=3, substrate_layers=3, match_index=0, **kwargs): """ Get a list of oriented slabs for constructing interfaces and put them in self.film_structures, self.substrate_structures, self.modified_film_structures, and self.modified_substrate_structures. Currently only uses first match (lowest SA) in the list of matches Args: film_layers (int): number of layers of film to include in Interface structures. substrate_layers (int): number of layers of substrate to include in Interface structures. match_index (int): ZSL match from which to construct slabs. """ self.match_index = match_index self.substrate_layers = substrate_layers self.film_layers = film_layers if 'zslgen' in kwargs.keys(): sa = SubstrateAnalyzer(zslgen=kwargs.get('zslgen')) del kwargs['zslgen'] else: sa = SubstrateAnalyzer() # Generate all possible interface matches self.matches = list( sa.calculate(self.original_film_structure, self.original_substrate_structure, **kwargs)) match = self.matches[match_index] # Generate substrate slab and align x axis to (100) and slab normal to (001) ## Get no-vacuum structure for strained bulk calculation self.sub_sg = SlabGenerator(self.original_substrate_structure, match['sub_miller'], substrate_layers, 0, in_unit_planes=True, reorient_lattice=False, primitive=False) no_vac_sub_slab = self.sub_sg.get_slab() no_vac_sub_slab = get_shear_reduced_slab(no_vac_sub_slab) self.oriented_substrate = align_x(no_vac_sub_slab) self.oriented_substrate.sort() ## Get slab with vacuum self.sub_sg = SlabGenerator(self.original_substrate_structure, match['sub_miller'], substrate_layers, 1, in_unit_planes=True, reorient_lattice=False, primitive=False) sub_slabs = self.sub_sg.get_slabs() for i, sub_slab in enumerate(sub_slabs): sub_slab = get_shear_reduced_slab(sub_slab) sub_slab = align_x(sub_slab) sub_slab.sort() sub_slabs[i] = sub_slab self.substrate_structures = sub_slabs # Generate film slab and align x axis to (100) and slab normal to (001) ## Get no-vacuum structure for strained bulk calculation self.film_sg = SlabGenerator(self.original_film_structure, match['film_miller'], film_layers, 0, in_unit_planes=True, reorient_lattice=False, primitive=False) no_vac_film_slab = self.film_sg.get_slab() no_vac_film_slab = get_shear_reduced_slab(no_vac_film_slab) self.oriented_film = align_x(no_vac_film_slab) self.oriented_film.sort() ## Get slab with vacuum self.film_sg = SlabGenerator(self.original_film_structure, match['film_miller'], film_layers, 1, in_unit_planes=True, reorient_lattice=False, primitive=False) film_slabs = self.film_sg.get_slabs() for i, film_slab in enumerate(film_slabs): film_slab = get_shear_reduced_slab(film_slab) film_slab = align_x(film_slab) film_slab.sort() film_slabs[i] = film_slab self.film_structures = film_slabs # Apply transformation to produce matched area and a & b vectors self.apply_transformations(match) # Get non-stoichioimetric substrate slabs sym_sub_slabs = [] for sub_slab in self.modified_substrate_structures: sym_sub_slab = self.sub_sg.nonstoichiometric_symmetrized_slab( sub_slab) for slab in sym_sub_slab: if not slab == sub_slab: sym_sub_slabs.append(slab) self.sym_modified_substrate_structures = sym_sub_slabs # Get non-stoichioimetric film slabs sym_film_slabs = [] for film_slab in self.modified_film_structures: sym_film_slab = self.film_sg.nonstoichiometric_symmetrized_slab( film_slab) for slab in sym_film_slab: if not slab == film_slab: sym_film_slabs.append(slab) self.sym_modified_film_structures = sym_film_slabs # Strained film structures (No Vacuum) self.strained_substrate, self.strained_film = strain_slabs( self.oriented_substrate, self.oriented_film) return 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 apply_transformations(self, match): """ Using ZSL match, transform all of the film_structures by the ZSL supercell transformation. Args: match (dict): ZSL match returned by ZSLGenerator.__call__ """ film_transformation = match["film_transformation"] sub_transformation = match["substrate_transformation"] modified_substrate_structures = [ struct.copy() for struct in self.substrate_structures ] modified_film_structures = [ struct.copy() for struct in self.film_structures ] # Match angles in lattices with 𝛾=θ° and 𝛾=(180-θ)° if np.isclose(180 - modified_film_structures[0].lattice.gamma, modified_substrate_structures[0].lattice.gamma, atol=3): reflection = SymmOp.from_rotation_and_translation( ((-1, 0, 0), (0, 1, 0), (0, 0, 1)), (0, 0, 1)) for modified_film_structure in modified_film_structures: modified_film_structure.apply_operation(reflection, fractional=True) self.oriented_film.apply_operation(reflection, fractional=True) # ------------------------------------------------------------------------------------------------------------------------ sub_scaling = np.diag(np.diag(sub_transformation)) sub_shearing = np.dot(np.linalg.inv(sub_scaling), sub_transformation) # Turn into 3x3 Arrays sub_scaling = np.diag(np.append(np.diag(sub_scaling), 1)) temp_matrix = np.diag([1, 1, 1]) temp_matrix[:2, :2] = sub_transformation sub_shearing = temp_matrix for modified_substrate_structure in modified_substrate_structures: modified_substrate_structure = self.apply_transformation( modified_substrate_structure, temp_matrix) self.modified_substrate_structures.append( modified_substrate_structure) self.oriented_substrate = self.apply_transformation( self.oriented_substrate, temp_matrix) # ------------------------------------------------------------------------------------------------------------------------ film_scaling = np.diag(np.diag(film_transformation)) film_shearing = np.dot(np.linalg.inv(film_scaling), film_transformation) # Turn into 3x3 Arrays film_scaling = np.diag(np.append(np.diag(film_scaling), 1)) temp_matrix = np.diag([1, 1, 1]) temp_matrix[:2, :2] = film_transformation film_shearing = temp_matrix for modified_film_structure in modified_film_structures: modified_film_structure = self.apply_transformation( modified_film_structure, temp_matrix) self.modified_film_structures.append(modified_film_structure) self.oriented_film = self.apply_transformation(self.oriented_film, temp_matrix) return def combine_slabs(self): """ Combine the slabs generated by get_oriented_slabs into interfaces """ all_substrate_variants = [] sub_labels = [] for i, slab in enumerate(self.modified_substrate_structures): all_substrate_variants.append(slab) sub_labels.append(str(i)) sg = SpacegroupAnalyzer(slab, symprec=1e-3) if not sg.is_laue(): mirrored_slab = slab.copy() reflection_z = SymmOp.from_rotation_and_translation( ((1, 0, 0), (0, 1, 0), (0, 0, -1)), (0, 0, 0)) mirrored_slab.apply_operation(reflection_z, fractional=True) translation = [0, 0, -min(mirrored_slab.frac_coords[:, 2])] mirrored_slab.translate_sites(range(mirrored_slab.num_sites), translation) all_substrate_variants.append(mirrored_slab) sub_labels.append('%dm' % i) all_film_variants = [] film_labels = [] for i, slab in enumerate(self.modified_film_structures): all_film_variants.append(slab) film_labels.append(str(i)) sg = SpacegroupAnalyzer(slab, symprec=1e-3) if not sg.is_laue(): mirrored_slab = slab.copy() reflection_z = SymmOp.from_rotation_and_translation( ((1, 0, 0), (0, 1, 0), (0, 0, -1)), (0, 0, 0)) mirrored_slab.apply_operation(reflection_z, fractional=True) translation = [0, 0, -min(mirrored_slab.frac_coords[:, 2])] mirrored_slab.translate_sites(range(mirrored_slab.num_sites), translation) all_film_variants.append(mirrored_slab) film_labels.append('%dm' % i) # substrate first index, film second index self.interfaces = [] self.interface_labels = [] # self.interfaces = [[None for j in range(len(all_film_variants))] for i in range(len(all_substrate_variants))] for i, substrate in enumerate(all_substrate_variants): for j, film in enumerate(all_film_variants): self.interfaces.append(self.make_interface(substrate, film)) self.interface_labels.append('%s/%s' % (film_labels[j], sub_labels[i])) def make_interface(self, slab_substrate, slab_film, offset=None): """ Strain a film to fit a substrate and generate an interface. Args: slab_substrate (Slab): substrate structure supercell slab_film (Slab): film structure supercell offset ([int]): separation vector of film and substrate """ # Check if lattices are equal. If not, strain them to match # NOTE: CHANGED THIS TO MAKE COPY OF SUBSTRATE/FILM, self.modified_film_structures NO LONGER STRAINED unstrained_slab_substrate = slab_substrate.copy() slab_substrate = slab_substrate.copy() unstrained_slab_film = slab_film.copy() slab_film = slab_film.copy() latt_1 = slab_substrate.lattice.matrix.copy() latt_1[2, :] = [0, 0, 1] latt_2 = slab_film.lattice.matrix.copy() latt_2[2, :] = [0, 0, 1] if not Lattice(latt_1) == Lattice(latt_2): # Calculate lattice strained to match: matched_slab_substrate, matched_slab_film = strain_slabs( slab_substrate, slab_film) else: matched_slab_substrate = slab_substrate matched_slab_film = slab_film # Ensure substrate has positive c-direction: if matched_slab_substrate.lattice.matrix[2, 2] < 0: latt = matched_slab_substrate.lattice.matrix.copy() latt[2, 2] *= -1 new_struct = matched_slab_substrate.copy() new_struct.lattice = Lattice(latt) matched_slab_substrate = new_struct # Ensure film has positive c-direction: if matched_slab_film.lattice.matrix[2, 2] < 0: latt = matched_slab_film.lattice.matrix.copy() latt[2, 2] *= -1 new_struct = matched_slab_film.copy() new_struct.lattice = Lattice(latt) matched_slab_film = new_struct if offset is None: offset = (2.5, 0.0, 0.0) _structure = merge_slabs(matched_slab_substrate, matched_slab_film, *offset) orthogonal_structure = _structure.get_orthogonal_c_slab() orthogonal_structure.sort() if not orthogonal_structure.is_valid(tol=1): warnings.warn( "Check generated structure, it may contain atoms too closely placed" ) #offset_vector = (offset[1], offset[2], offset[0]) interface = Interface( orthogonal_structure.lattice.copy(), orthogonal_structure.species, orthogonal_structure.frac_coords, slab_substrate.miller_index, slab_film.miller_index, self.original_substrate_structure, self.original_film_structure, unstrained_slab_substrate, unstrained_slab_film, slab_substrate, slab_film, init_inplane_shift=offset[1:], site_properties=orthogonal_structure.site_properties) return interface def visualize_interface(self, interface_index=0, show_atoms=False, n_uc=2): """ Plot the film-substrate superlattice match, the film superlattice, and the substrate superlattice in three separate plots and show them. Args: interface_index (int, 0): Choice of interface to plot show_atoms (bool, False): Whether to plot atomic sites n_uc (int, 2): Number of 2D unit cells of the interface in each direction. (The unit cell of the interface is the supercell of th substrate that matches a supercel of the film.) """ film_index = int(self.interface_labels[interface_index][0]) sub_index = int(self.interface_labels[interface_index][2]) visualize_interface(self.interfaces[interface_index], show_atoms, n_uc) visualize_superlattice(self.film_structures[film_index], self.modified_film_structures[film_index], film=True, show_atoms=show_atoms, n_uc=n_uc) visualize_superlattice(self.substrate_structures[sub_index], self.modified_substrate_structures[sub_index], film=False, show_atoms=show_atoms, n_uc=n_uc)