Beispiel #1
0
    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)
Beispiel #2
0
    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"
Beispiel #4
0
    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))
Beispiel #5
0
    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)
Beispiel #7
0
    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)
Beispiel #8
0
    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
Beispiel #9
0
    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)
Beispiel #10
0
    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
Beispiel #12
0
    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
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #17
0
 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))
Beispiel #18
0
    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
Beispiel #19
0
    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
Beispiel #20
0
    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)
Beispiel #21
0
    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)
Beispiel #22
0
 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')
Beispiel #23
0
    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")
Beispiel #24
0
    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")
Beispiel #25
0
    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")
Beispiel #26
0
    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")
Beispiel #27
0
    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))
Beispiel #28
0
    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,
            ))
Beispiel #30
0
    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)
Beispiel #31
0
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)