Exemplo n.º 1
0
    def test_surface_sites_and_symmetry(self):
        # test if surfaces are equivalent by using
        # Laue symmetry and surface site equivalence

        for bool in [True, False]:
            # We will also set the slab to be centered and
            # off centered in order to test the center of mass
            slabgen = SlabGenerator(self.agfcc, (3, 1, 0),
                                    10,
                                    10,
                                    center_slab=bool)
            slab = slabgen.get_slabs()[0]
            surf_sites_dict = slab.get_surface_sites()
            self.assertEqual(len(surf_sites_dict["top"]),
                             len(surf_sites_dict["bottom"]))
            total_surf_sites = sum(
                [len(surf_sites_dict[key]) for key in surf_sites_dict.keys()])
            self.assertTrue(slab.is_symmetric())
            self.assertEqual(total_surf_sites / 2, 4)
            self.assertTrue(slab.have_equivalent_surfaces())

            # Test if the ratio of surface sites per area is
            # constant, ie are the surface energies the same
            r1 = total_surf_sites / (2 * slab.surface_area)
            slabgen = SlabGenerator(self.agfcc, (3, 1, 0),
                                    10,
                                    10,
                                    primitive=False)
            slab = slabgen.get_slabs()[0]
            surf_sites_dict = slab.get_surface_sites()
            total_surf_sites = sum(
                [len(surf_sites_dict[key]) for key in surf_sites_dict.keys()])
            r2 = total_surf_sites / (2 * slab.surface_area)
            self.assertArrayEqual(r1, r2)
Exemplo n.º 2
0
    def test_surface_sites_and_symmetry(self):
        # test if surfaces are equivalent by using
        # Laue symmetry and surface site equivalence

        for bool in [True, False]:
            # We will also set the slab to be centered and
            # off centered in order to test the center of mass
            slabgen = SlabGenerator(self.agfcc, (3, 1, 0), 10, 10,
                                    center_slab=bool)
            slab = slabgen.get_slabs()[0]
            surf_sites_dict = slab.get_surface_sites()
            self.assertEqual(len(surf_sites_dict["top"]),
                             len(surf_sites_dict["bottom"]))
            total_surf_sites = sum([len(surf_sites_dict[key])
                                    for key in surf_sites_dict.keys()])
            self.assertTrue(slab.is_symmetric())
            self.assertEqual(total_surf_sites / 2, 4)
            self.assertTrue(slab.have_equivalent_surfaces())

            # Test if the ratio of surface sites per area is
            # constant, ie are the surface energies the same
            r1 = total_surf_sites / (2 * slab.surface_area)
            slabgen = SlabGenerator(self.agfcc, (3, 1, 0), 10, 10,
                                    primitive=False)
            slab = slabgen.get_slabs()[0]
            surf_sites_dict = slab.get_surface_sites()
            total_surf_sites = sum([len(surf_sites_dict[key])
                                    for key in surf_sites_dict.keys()])
            r2 = total_surf_sites / (2 * slab.surface_area)
            self.assertArrayEqual(r1, r2)
Exemplo n.º 3
0
 def make_surface(self,
                  miller_index=[0, 0, 1],
                  bonds=None,
                  min_slab_size=8,
                  min_vacuum_size=20,
                  max_normal_search=None,
                  primitive=False):
     slabs = SlabGenerator(self.poscar.structure.copy(),
                           miller_index=miller_index,
                           min_slab_size=min_slab_size,
                           min_vacuum_size=min_vacuum_size,
                           max_normal_search=max_normal_search,
                           primitive=primitive,
                           lll_reduce=True)
     if bonds is not None:
         slabs = slabs.get_slabs(tol=0.1, bonds=bonds)
     else:
         slabs = slabs.get_slabs(tol=0.1)
     suflist = []
     i = 0
     for slab in slabs:
         i += 1
         newname = str(miller_index[0]) + str(miller_index[1]) + str(
             miller_index[2]) + '-' + str(i)
         suflist.append(easyjob(slab, os.path.join(self.path, newname)))
     return suflist
Exemplo n.º 4
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)
Exemplo n.º 5
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)
Exemplo n.º 6
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)
Exemplo n.º 7
0
def make_super_cell_pymatgen (jdata) :
    make_unit_cell(jdata)

    out_dir = jdata['out_dir']
    path_uc = os.path.join(out_dir, global_dirname_02)
    from_path = path_uc
    from_file = os.path.join(from_path, 'POSCAR.unit')
    ss = Structure.from_file(from_file)

    all_millers = jdata['millers']
    path_sc = os.path.join(out_dir, global_dirname_02)
    z_min = jdata['z_min']
    super_cell = jdata['super_cell']

    cwd = os.getcwd()    
    path_work = (path_sc)    
    path_work = os.path.abspath(path_work)
    pcpy_cmd = os.path.join(cwd, 'tools')
    pcpy_cmd = os.path.join(pcpy_cmd, 'poscar_copy.py')
    os.chdir(path_work)
    for miller in all_millers:
        miller_str=""
        for ii in miller :
            miller_str += str(ii)        
        path_cur_surf = create_path('surf-'+miller_str)
        os.chdir(path_cur_surf)
        slabgen = SlabGenerator(ss, miller, z_min, 1e-3)
        all_slabs = slabgen.get_slabs() 
        print("Miller %s: The slab has %s termination, use the first one" %(str(miller), len(all_slabs)))
        all_slabs[0].to('POSCAR', 'POSCAR')
        if super_cell[0] > 1 or super_cell[1] > 1 :
            sp.check_call(pcpy_cmd + ' -n %d %d %d POSCAR POSCAR ' % (super_cell[0], super_cell[1], 1), 
                          shell = True)
        os.chdir(path_work)
    os.chdir(cwd)        
Exemplo n.º 8
0
    def test_nonstoichiometric_symmetrized_slab(self):
        # For the (111) halite slab, sometimes a nonstoichiometric
        # system is preferred over the stoichiometric Tasker 2.
        slabgen = SlabGenerator(self.MgO, (1, 1, 1),
                                10,
                                10,
                                max_normal_search=1)
        slabs = slabgen.get_slabs(symmetrize=True)

        # We should end up with two terminations, one with
        # an Mg rich surface and another O rich surface
        self.assertEqual(len(slabs), 2)
        for slab in slabs:
            self.assertTrue(slab.is_symmetric())

        # For a low symmetry primitive_elemental system such as
        # R-3m, there should be some nonsymmetric slabs
        # without using nonstoichiometric_symmetrized_slab
        slabs = generate_all_slabs(self.Dy,
                                   1,
                                   30,
                                   30,
                                   center_slab=True,
                                   symmetrize=True)
        for s in slabs:
            self.assertTrue(s.is_symmetric())
            self.assertGreater(len(s), len(self.Dy))
Exemplo n.º 9
0
 def get_tasker2_slabs(self):
     # The uneven distribution of ions on the (111) facets of Halite
     # type slabs are typical examples of Tasker 3 structures. We
     # will test this algo to generate a Tasker 2 structure instead
     lattice = Lattice.cubic(3.010)
     frac_coords = [[0.00000, 0.00000,
                     0.00000], [0.00000, 0.50000, 0.50000],
                    [0.50000, 0.00000,
                     0.50000], [0.50000, 0.50000, 0.00000],
                    [0.50000, 0.00000,
                     0.00000], [0.50000, 0.50000, 0.50000],
                    [0.00000, 0.00000, 0.50000],
                    [0.00000, 0.50000, 0.00000]]
     species = ['Mg', 'Mg', 'Mg', 'Mg', 'O', 'O', 'O', 'O']
     MgO = Structure(lattice, species, frac_coords)
     MgO.add_oxidation_state_by_element({"Mg": 2, "O": -6})
     slabgen = SlabGenerator(MgO, (1, 1, 1), 10, 10, max_normal_search=1)
     # We generate the Tasker 3 structure first
     slab = slabgen.get_slabs()[0]
     self.assertFalse(slab.is_symmetric())
     self.assertTrue(slab.is_polar())
     # Now to generate the Tasker 2 structure, we must
     # ensure there are enough ions on top to move around
     slab.make_supercell([2, 1, 1])
     slabs = slab.get_tasker2_slabs()
     # Check if our Tasker 2 slab is nonpolar and symmetric
     for slab in slabs:
         self.assertTrue(slab.is_symmetric())
         self.assertFalse(slab.is_polar())
Exemplo n.º 10
0
def get_dimensionality(structure,
                       max_hkl=2,
                       el_radius_updates=None,
                       min_slab_size=5,
                       min_vacuum_size=5,
                       standardize=True,
                       bonds=None):
    """
    This method returns whether a structure is 3D, 2D (layered), or 1D (linear
    chains or molecules) according to the algorithm published in Gorai, P.,
    Toberer, E. & Stevanovic, V. Computational Identification of Promising
    Thermoelectric Materials Among Known Quasi-2D Binary Compounds. J. Mater.
    Chem. A 2, 4136 (2016).

    Note that a 1D structure detection might indicate problems in the bonding
    algorithm, particularly for ionic crystals (e.g., NaCl)

    Users can change the behavior of bonds detection by passing either
    el_radius_updates to update atomic radii for auto-detection of max bond
    distances, or bonds to explicitly specify max bond distances for atom pairs.
    Note that if you pass both, el_radius_updates are ignored.

    Args:
        structure: (Structure) structure to analyze dimensionality for
        max_hkl: (int) max index of planes to look for layers
        el_radius_updates: (dict) symbol->float to update atomic radii
        min_slab_size: (float) internal surface construction parameter
        min_vacuum_size: (float) internal surface construction parameter
        standardize (bool): whether to standardize the structure before
            analysis. Set to False only if you already have the structure in a
            convention where layers / chains will be along low <hkl> indexes.
        bonds ({(specie1, specie2): max_bond_dist}: bonds are
                specified as a dict of tuples: float of specie1, specie2
                and the max bonding distance. For example, PO4 groups may be
                defined as {("P", "O"): 3}.

    Returns: (int) the dimensionality of the structure - 1 (molecules/chains),
        2 (layered), or 3 (3D)

    """
    if standardize:
        structure = SpacegroupAnalyzer(structure). \
            get_conventional_standard_structure()

    if not bonds:
        bonds = get_max_bond_lengths(structure, el_radius_updates)

    num_surfaces = 0
    for h in range(max_hkl):
        for k in range(max_hkl):
            for l in range(max_hkl):
                if max([h, k, l]) > 0 and num_surfaces < 2:
                    sg = SlabGenerator(structure, (h, k, l),
                                       min_slab_size=min_slab_size,
                                       min_vacuum_size=min_vacuum_size)
                    slabs = sg.get_slabs(bonds)
                    for _ in slabs:
                        num_surfaces += 1

    return 3 - min(num_surfaces, 2)
    def _find_terminations(self):
        """
        Finds all terminations
        """

        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_slabs = film_sg.get_slabs()
        sub_slabs = sub_sg.get_slabs()

        film_shits = [s.shift for s in film_slabs]
        film_terminations = [label_termination(s) for s in film_slabs]

        sub_shifts = [s.shift for s in sub_slabs]
        sub_terminations = [label_termination(s) for s in sub_slabs]

        self._terminations = {
            (film_label, sub_label): (film_shift, sub_shift)
            for (film_label, film_shift), (
                sub_label,
                sub_shift) in product(zip(film_terminations, film_shits),
                                      zip(sub_terminations, sub_shifts))
        }
        self.terminations = list(self._terminations.keys())
Exemplo n.º 12
0
 def test_get_orthogonal_c_slab(self):
     TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False)
     trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10)
     TeI_slabs = trclnc_TeI.get_slabs()
     slab = TeI_slabs[0]
     norm_slab = slab.get_orthogonal_c_slab()
     self.assertAlmostEqual(norm_slab.lattice.angles[0], 90)
     self.assertAlmostEqual(norm_slab.lattice.angles[1], 90)
Exemplo n.º 13
0
def create_surface2(st,
                    miller_index,
                    min_slab_size=10,
                    min_vacuum_size=10,
                    surface_i=0,
                    oxidation=None,
                    suf='',
                    primitive=None,
                    symmetrize=False):
    """
    INPUT:
        st (Structure) - Initial input structure. Note that to
                ensure that the miller indices correspond to usual
                crystallographic definitions, you should supply a conventional
                unit cell structure.

        miller_index ([h, k, l]): Miller index of plane parallel to
                        surface. Note that this is referenced to the input structure. If
                        you need this to be based on the conventional cell,
                        you should supply the conventional structure.


        oxidation (dic) - dictionary of effective oxidation states, e. g. {'Y':'Y3+', 'Ba':'Ba2+', 'Co':'Co2.25+', 'O':'O2-'}
                          allows to calculate dipole moment

        surface_i (int) - choose particular surface 

        min_slab_size (float) - minimum slab size

        min_vacuum_size (float) - vacuum thicknes in A

    """

    from pymatgen.core.surface import SlabGenerator
    from pymatgen.io.vasp.inputs import Poscar
    from geo import replic

    pm = st.convert2pymatgen(oxidation=oxidation)
    # pm = st.convert2pymatgen()

    slabgen = SlabGenerator(pm,
                            miller_index,
                            min_slab_size,
                            min_vacuum_size,
                            primitive=primitive)
    # print(slabgen.oriented_unit_cell)
    slabs = slabgen.get_slabs(symmetrize=symmetrize)

    printlog(
        len(slabs),
        'surfaces were generated, choose required surface using *surface_i* argument\nWriting POSCARs to xyz',
        imp='y')

    for i, slab in enumerate(slabs):
        pos = Poscar(slab)
        pos.write_file('xyz/POSCAR_suf' + str(i) + str(suf))

    return slabs
def enumerate_surfaces(bulk_atoms, mpid, max_miller=MAX_MILLER):
    '''
    Enumerate all the symmetrically distinct surfaces of a bulk structure. It
    will not enumerate surfaces with Miller indices above the `max_miller`
    argument. Note that we also look at the bottoms of slabs if they are
    distinct from the top. If they are distinct, we flip the slab so the bottom
    is pointing upwards.

    Args:
        bulk_atoms  `ase.Atoms` object of the bulk you want to enumerate
                    surfaces from.
        mpid        String indicating the the Materials Project ID number of
                    the bulk that was selected.
        max_miller  An integer indicating the maximum Miller index of the surfaces
                    you are willing to enumerate. Increasing this argument will
                    increase the number of surfaces, but the surfaces will
                    generally become larger.
    Returns:
        all_slabs_info  A list of 4-tuples containing:  `pymatgen.Structure`
                        objects, 3-tuples for the Miller indices, floats for
                        the shifts, and Booleans for "top".
    '''
    all_slabs = CACHE.get(mpid)
    if all_slabs is None:

        bulk_struct = standardize_bulk(bulk_atoms)

        all_slabs_info = []
        for millers in get_symmetrically_distinct_miller_indices(
                bulk_struct, MAX_MILLER):
            slab_gen = SlabGenerator(initial_structure=bulk_struct,
                                     miller_index=millers,
                                     min_slab_size=7.,
                                     min_vacuum_size=20.,
                                     lll_reduce=False,
                                     center_slab=True,
                                     primitive=True,
                                     max_normal_search=1)
            slabs = slab_gen.get_slabs(tol=0.3,
                                       bonds=None,
                                       max_broken_bonds=0,
                                       symmetrize=False)

            # If the bottoms of the slabs are different than the tops, then we want
            # to consider them, too
            flipped_slabs = [
                flip_struct(slab) for slab in slabs
                if is_structure_invertible(slab) is False
            ]

            # Concatenate all the results together
            slabs_info = [(slab, millers, slab.shift, True) for slab in slabs]
            flipped_slabs_info = [(slab, millers, slab.shift, False)
                                  for slab in flipped_slabs]
            all_slabs_info.extend(slabs_info + flipped_slabs_info)
        CACHE.set(mpid, all_slabs_info)
    return all_slabs_info
Exemplo n.º 15
0
 def test_get_orthogonal_c_slab(self):
     TeI = Structure.from_file(get_path("icsd_TeI.cif"),
                               primitive=False)
     trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10)
     TeI_slabs = trclnc_TeI.get_slabs()
     slab = TeI_slabs[0]
     norm_slab = slab.get_orthogonal_c_slab()
     self.assertAlmostEqual(norm_slab.lattice.angles[0], 90)
     self.assertAlmostEqual(norm_slab.lattice.angles[1], 90)
Exemplo n.º 16
0
def get_dimensionality(structure, max_hkl=2, el_radius_updates=None,
                       min_slab_size=5, min_vacuum_size=5,
                       standardize=True, bonds=None):
    """
    This method returns whether a structure is 3D, 2D (layered), or 1D (linear 
    chains or molecules) according to the algorithm published in Gorai, P., 
    Toberer, E. & Stevanovic, V. Computational Identification of Promising 
    Thermoelectric Materials Among Known Quasi-2D Binary Compounds. J. Mater. 
    Chem. A 2, 4136 (2016).
    
    Note that a 1D structure detection might indicate problems in the bonding
    algorithm, particularly for ionic crystals (e.g., NaCl)
    
    Users can change the behavior of bonds detection by passing either
    el_radius_updates to update atomic radii for auto-detection of max bond 
    distances, or bonds to explicitly specify max bond distances for atom pairs.
    Note that if you pass both, el_radius_updates are ignored.
    
    Args:
        structure: (Structure) structure to analyze dimensionality for 
        max_hkl: (int) max index of planes to look for layers
        el_radius_updates: (dict) symbol->float to update atomic radii
        min_slab_size: (float) internal surface construction parameter
        min_vacuum_size: (float) internal surface construction parameter
        standardize (bool): whether to standardize the structure before 
            analysis. Set to False only if you already have the structure in a 
            convention where layers / chains will be along low <hkl> indexes.
        bonds ({(specie1, specie2): max_bond_dist}: bonds are
                specified as a dict of tuples: float of specie1, specie2
                and the max bonding distance. For example, PO4 groups may be
                defined as {("P", "O"): 3}.

    Returns: (int) the dimensionality of the structure - 1 (molecules/chains), 
        2 (layered), or 3 (3D)

    """
    if standardize:
        structure = SpacegroupAnalyzer(structure). \
            get_conventional_standard_structure()

    if not bonds:
        bonds = get_max_bond_lengths(structure, el_radius_updates)

    num_surfaces = 0
    for h in range(max_hkl):
        for k in range(max_hkl):
            for l in range(max_hkl):
                if max([h, k, l]) > 0 and num_surfaces < 2:
                    sg = SlabGenerator(structure, (h, k, l),
                                       min_slab_size=min_slab_size,
                                       min_vacuum_size=min_vacuum_size)
                    slabs = sg.get_slabs(bonds)
                    for _ in slabs:
                        num_surfaces += 1

    return 3 - min(num_surfaces, 2)
Exemplo n.º 17
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)
def make_surface(file, facet=(1, 1, 1), verbose=True):
    # read in structure
    st = Structure.from_file(file)
    # generate slabs with the given facet
    slabgen = SlabGenerator(st, facet, 6, 16, center_slab=True)
    all_slabs = slabgen.get_slabs(symmetrize=True)
    if verbose:
        print("The slab has %s termination." % (len(all_slabs)))
    # save surfaces
    for i, slab in enumerate(all_slabs):
        slab.to('POSCAR', 'POSCAR_SURF_' + str(i).zfill(2))
Exemplo n.º 19
0
 def test_triclinic_TeI(self):
     # Test case for a triclinic structure of TeI. Only these three
     # Miller indices are used because it is easier to identify which
     # atoms should be in a surface together. The closeness of the sites
     # in other Miller indices can cause some ambiguity when choosing a
     # higher tolerance.
     numb_slabs = {(0, 0, 1): 5, (0, 1, 0): 3, (1, 0, 0): 7}
     TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False)
     for k, v in numb_slabs.items():
         trclnc_TeI = SlabGenerator(TeI, k, 10, 10)
         TeI_slabs = trclnc_TeI.get_slabs()
         self.assertEqual(v, len(TeI_slabs))
Exemplo n.º 20
0
 def test_triclinic_TeI(self):
     # Test case for a triclinic structure of TeI. Only these three
     # Miller indices are used because it is easier to identify which
     # atoms should be in a surface together. The closeness of the sites
     # in other Miller indices can cause some ambiguity when choosing a
     # higher tolerance.
     numb_slabs = {(0, 0, 1): 5, (0, 1, 0): 3, (1, 0, 0): 7}
     TeI = Structure.from_file(get_path("icsd_TeI.cif"),
                               primitive=False)
     for k, v in numb_slabs.items():
         trclnc_TeI = SlabGenerator(TeI, k, 10, 10)
         TeI_slabs = trclnc_TeI.get_slabs()
         self.assertEqual(v, len(TeI_slabs))
Exemplo n.º 21
0
    def enumerate_surfaces(self, max_miller=MAX_MILLER):
        '''
        Enumerate all the symmetrically distinct surfaces of a bulk structure. It
        will not enumerate surfaces with Miller indices above the `max_miller`
        argument. Note that we also look at the bottoms of surfaces if they are
        distinct from the top. If they are distinct, we flip the surface so the bottom
        is pointing upwards.

        Args:
            bulk_atoms  `ase.Atoms` object of the bulk you want to enumerate
                        surfaces from.
            max_miller  An integer indicating the maximum Miller index of the surfaces
                        you are willing to enumerate. Increasing this argument will
                        increase the number of surfaces, but the surfaces will
                        generally become larger.
        Returns:
            all_slabs_info  A list of 4-tuples containing:  `pymatgen.Structure`
                            objects for surfaces we have enumerated, the Miller
                            indices, floats for the shifts, and Booleans for "top".
        '''
        bulk_struct = self.standardize_bulk(self.bulk_atoms)

        all_slabs_info = []
        for millers in get_symmetrically_distinct_miller_indices(bulk_struct, MAX_MILLER):
            slab_gen = SlabGenerator(initial_structure=bulk_struct,
                                     miller_index=millers,
                                     min_slab_size=7.,
                                     min_vacuum_size=20.,
                                     lll_reduce=False,
                                     center_slab=True,
                                     primitive=True,
                                     max_normal_search=1)
            slabs = slab_gen.get_slabs(tol=0.3,
                                       bonds=None,
                                       max_broken_bonds=0,
                                       symmetrize=False)

            # Additional filtering for the 2D materials' slabs
            if self.mpid in COVALENT_MATERIALS_MPIDS:
                slabs = [slab for slab in slabs if is_2D_slab_reasonsable(slab) is True]

            # If the bottoms of the slabs are different than the tops, then we want
            # to consider them, too
            if len(slabs) != 0:
                flipped_slabs_info = [(self.flip_struct(slab), millers, slab.shift, False)
                                      for slab in slabs if self.is_structure_invertible(slab) is False]

                # Concatenate all the results together
                slabs_info = [(slab, millers, slab.shift, True) for slab in slabs]
                all_slabs_info.extend(slabs_info + flipped_slabs_info)
        return all_slabs_info
Exemplo n.º 22
0
    def nonstoichiometric_symmetrized_slab(self):
        # For the (111) halite slab, sometimes a nonstoichiometric
        # system is preferred over the stoichiometric Tasker 2.
        slabgen = SlabGenerator(self.MgO, (1, 1, 1),
                                10,
                                10,
                                max_normal_search=1)
        slabs = slabgen.get_slabs(symmetrize=True)

        # We should end up with two terminations, one with
        # an Mg rich surface and another O rich surface
        self.assertEqual(len(slabs), 2)
        for slab in slabs:
            self.assertTrue(slab.is_symmetric())
Exemplo n.º 23
0
    def test_get_orthogonal_c_slab_site_props(self):
        TeI = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False)
        trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10)
        TeI_slabs = trclnc_TeI.get_slabs()
        slab = TeI_slabs[0]
        # Add site property to slab
        sd_list = [[True, True, True] for site in slab.sites]
        new_sp = slab.site_properties
        new_sp["selective_dynamics"] = sd_list
        slab_with_site_props = slab.copy(site_properties=new_sp)

        # Get orthogonal slab
        norm_slab = slab_with_site_props.get_orthogonal_c_slab()

        # Check if site properties is consistent (or kept)
        self.assertEqual(slab_with_site_props.site_properties, norm_slab.site_properties)
Exemplo n.º 24
0
def generate_surfaces(request):
    f = list(request.FILES.values())[0]
    name, s = get_structure(f)
    miller = [int(i) for i in request.POST["miller"].split(",")]
    if len(miller) != 3:
        raise ValueError("Invalid miller index!")
    vac_size = float(request.POST["vac-size"])
    slab_size = float(request.POST["slab-size"])
    fmt = request.POST["output-format"]
    gen = SlabGenerator(s,
                        miller_index=miller,
                        min_slab_size=slab_size,
                        min_vacuum_size=vac_size)
    slabs = gen.get_slabs(bonds={})
    results = [sl.to(fmt=fmt) for sl in slabs]
    return results
Exemplo n.º 25
0
def slabsets(inputfile,
             outputdir,
             plane2cut,
             vacmin=4,
             vacmax=16,
             numberoflayers=6):
    # Plane to cut should be in pymatgen format miller planes - [A, B ,C] otherthan that it basically calls on pymatgen
    # to do the work
    import os.path

    import pymatgen
    from pymatgen.core.structure import Structure
    from pymatgen.core.surface import SlabGenerator
    from pymatgen.io.cif import CifWriter

    slices_string = ''.join(str(e) for e in plane2cut)

    if vacmax == vacmin:
        vac = []
        vac = [vacmin]
    else:
        vac = [None] * 5
        vacrange = ((vacmax - vacmin) / 3)
        vac[0] = 0
        vac[1] = round(vacmin)
        vac[2] = round(vac[1] + vacrange)
        vac[3] = round(vac[2] + vacrange)
        vac[4] = round(vacmax)

    struc = Structure.from_file(inputfile)
    for vacsize in vac:
        slabgen = SlabGenerator(struc,
                                plane2cut,
                                numberoflayers,
                                vacsize,
                                center_slab=True,
                                in_unit_planes=True)
        all_slabs = slabgen.get_slabs()
        CIF = pymatgen.io.cif.CifWriter(all_slabs[0], symprec=1e-4)
        os.makedirs(outputdir + '/' + slices_string + 'vac' + str(vacsize),
                    exist_ok=True)
        CIF.write_file(outputdir + '/' + slices_string + 'vac' + str(vacsize) +
                       '.cif')
        strucs = Structure.from_file(outputdir + '/' + slices_string + 'vac' +
                                     str(vacsize) + '.cif')
        strucs.to(filename=(outputdir + '/' + slices_string + 'vac' +
                            str(vacsize) + '/POSCAR'))
Exemplo n.º 26
0
 def test_get_tasker2_slabs(self):
     # The uneven distribution of ions on the (111) facets of Halite
     # type slabs are typical examples of Tasker 3 structures. We
     # will test this algo to generate a Tasker 2 structure instead
     slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10, max_normal_search=1)
     # We generate the Tasker 3 structure first
     slab = slabgen.get_slabs()[0]
     self.assertFalse(slab.is_symmetric())
     self.assertTrue(slab.is_polar())
     # Now to generate the Tasker 2 structure, we must
     # ensure there are enough ions on top to move around
     slab.make_supercell([2, 1, 1])
     slabs = slab.get_tasker2_slabs()
     # Check if our Tasker 2 slab is nonpolar and symmetric
     for slab in slabs:
         self.assertTrue(slab.is_symmetric())
         self.assertFalse(slab.is_polar())
Exemplo n.º 27
0
def makeSlab(a, facList, lay, sym, xy, vac):
    pmg_a = AseAtomsAdaptor.get_structure(a)
    unit = SpacegroupAnalyzer(
        pmg_a, symprec=0.1,
        angle_tolerance=5).get_conventional_standard_structure()
    gen = SlabGenerator(unit,
                        facList,
                        findHeight(unit, facList, lay, sym),
                        vac,
                        center_slab=sym,
                        primitive=True,
                        lll_reduce=True)
    slabs = gen.get_slabs()
    if len(slabs) > 1: print "Warning, multiple slabs generated..."
    slab = slabs[0]
    slab.make_supercell([xy[0], xy[1], 1])
    return reorient_z(slab)
Exemplo n.º 28
0
    def test_get_orthogonal_c_slab_site_props(self):
        TeI = Structure.from_file(get_path("icsd_TeI.cif"),
                                  primitive=False)
        trclnc_TeI = SlabGenerator(TeI, (0, 0, 1), 10, 10)
        TeI_slabs = trclnc_TeI.get_slabs()
        slab = TeI_slabs[0]
        # Add site property to slab
        sd_list = [[True, True, True] for site in slab.sites]
        new_sp = slab.site_properties
        new_sp['selective_dynamics'] = sd_list
        slab_with_site_props = slab.copy(site_properties=new_sp)

        # Get orthogonal slab
        norm_slab = slab_with_site_props.get_orthogonal_c_slab()

        # Check if site properties is consistent (or kept)
        self.assertEqual(slab_with_site_props.site_properties, norm_slab.site_properties)
Exemplo n.º 29
0
 def test_get_tasker2_slabs(self):
     # The uneven distribution of ions on the (111) facets of Halite
     # type slabs are typical examples of Tasker 3 structures. We
     # will test this algo to generate a Tasker 2 structure instead
     slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10,
                             max_normal_search=1)
     # We generate the Tasker 3 structure first
     slab = slabgen.get_slabs()[0]
     self.assertFalse(slab.is_symmetric())
     self.assertTrue(slab.is_polar())
     # Now to generate the Tasker 2 structure, we must
     # ensure there are enough ions on top to move around
     slab.make_supercell([2, 1, 1])
     slabs = slab.get_tasker2_slabs()
     # Check if our Tasker 2 slab is nonpolar and symmetric
     for slab in slabs:
         self.assertTrue(slab.is_symmetric())
         self.assertFalse(slab.is_polar())
Exemplo n.º 30
0
 def test_bonds_broken(self):
     # Querying the Materials Project database for Si
     s = self.get_mp_structure("mp-149")
     # Conventional unit cell is supplied to ensure miller indices
     # correspond to usual crystallographic definitions
     conv_bulk = SpacegroupAnalyzer(s).get_conventional_standard_structure()
     slabgen = SlabGenerator(conv_bulk, [1, 1, 1], 10, 10, center_slab=True)
     # Setting a generous estimate for max_broken_bonds
     # so that all terminations are generated. These slabs
     # are ordered by ascending number of bonds broken
     # which is assigned to Slab.energy
     slabs = slabgen.get_slabs(bonds={("Si", "Si"): 2.40}, max_broken_bonds=30)
     # Looking at the two slabs generated in VESTA, we
     # expect 2 and 6 bonds broken so we check for this.
     # Number of broken bonds are floats due to primitive
     # flag check and subsequent transformation of slabs.
     self.assertTrue(slabs[0].energy, 2.0)
     self.assertTrue(slabs[1].energy, 6.0)
Exemplo n.º 31
0
def make_slabs_from_bulk_atoms(atoms, miller_indices, slab_generator_settings,
                               get_slab_settings):
    '''
    Use pymatgen to enumerate the slabs from a bulk.

    Args:
        atoms                   The `ase.Atoms` object of the bulk that you
                                want to make slabs out of
        miller_indices          A 3-tuple of integers containing the three
                                Miller indices of the slab[s] you want to
                                make.
        slab_generator_settings A dictionary containing the settings to be
                                passed to pymatgen's `SpaceGroupAnalyzer`
                                class.
        get_slab_settings       A dictionary containing the settings to be
                                ppassed to the `get_slab` method of
                                pymatgen's `SpaceGroupAnalyzer` class.
    Returns:
        slabs   A list of the slabs in the form of pymatgen.Structure
                objects. Note that there may be multiple slabs because
                of different shifts/terminations.
    '''
    # Get rid of the `miller_index` argument, which is superceded by the
    # `miller_indices` argument.
    try:
        slab_generator_settings = unfreeze_dict(slab_generator_settings)
        slab_generator_settings.pop('miller_index')
        warnings.warn(
            'You passed a `miller_index` object into the '
            '`slab_generator_settings` argument for the '
            '`make_slabs_from_bulk_atoms` function. By design, '
            'this function will instead use the explicit '
            'argument, `miller_indices`.', SyntaxWarning)
    except KeyError:
        pass

    struct = AseAtomsAdaptor.get_structure(atoms)
    sga = SpacegroupAnalyzer(struct, symprec=0.1)
    struct_stdrd = sga.get_conventional_standard_structure()
    slab_gen = SlabGenerator(initial_structure=struct_stdrd,
                             miller_index=miller_indices,
                             **slab_generator_settings)
    slabs = slab_gen.get_slabs(**get_slab_settings)
    return slabs
Exemplo n.º 32
0
    def test_get_slab_regions(self):

        # If a slab layer in the slab cell is not completely inside
        # the cell (noncontiguous), check that get_slab_regions will
        # be able to identify where the slab layers are located

        s = self.get_structure("LiFePO4")
        slabgen = SlabGenerator(s, (0, 0, 1), 15, 15)
        slab = slabgen.get_slabs()[0]
        slab.translate_sites([i for i, site in enumerate(slab)], [0, 0, -0.25])
        bottom_c, top_c = [], []
        for site in slab:
            if site.frac_coords[2] < 0.5:
                bottom_c.append(site.frac_coords[2])
            else:
                top_c.append(site.frac_coords[2])
        ranges = get_slab_regions(slab)
        self.assertEqual(tuple(ranges[0]), (0, max(bottom_c)))
        self.assertEqual(tuple(ranges[1]), (min(top_c), 1))
Exemplo n.º 33
0
    def test_get_slab_regions(self):

        # If a slab layer in the slab cell is not completely inside
        # the cell (noncontiguous), check that get_slab_regions will
        # be able to identify where the slab layers are located

        s = self.get_structure("LiFePO4")
        slabgen = SlabGenerator(s, (0, 0, 1), 15, 15)
        slab = slabgen.get_slabs()[0]
        slab.translate_sites([i for i, site in enumerate(slab)], [0, 0, -0.25])
        bottom_c, top_c = [], []
        for site in slab:
            if site.frac_coords[2] < 0.5:
                bottom_c.append(site.frac_coords[2])
            else:
                top_c.append(site.frac_coords[2])
        ranges = get_slab_regions(slab)
        self.assertEqual(tuple(ranges[0]), (0, max(bottom_c)))
        self.assertEqual(tuple(ranges[1]), (min(top_c), 1))
Exemplo n.º 34
0
    def test_nonstoichiometric_symmetrized_slab(self):
        # For the (111) halite slab, sometimes a nonstoichiometric
        # system is preferred over the stoichiometric Tasker 2.
        slabgen = SlabGenerator(self.MgO, (1, 1, 1), 10, 10,
                                max_normal_search=1)
        slabs = slabgen.get_slabs(symmetrize=True)

        # We should end up with two terminations, one with
        # an Mg rich surface and another O rich surface
        self.assertEqual(len(slabs), 2)
        for slab in slabs:
            self.assertTrue(slab.is_symmetric())

        # For a low symmetry primitive_elemental system such as
        # R-3m, there should be some nonsymmetric slabs
        # without using nonstoichiometric_symmetrized_slab
        slabs = generate_all_slabs(self.Dy, 1, 30, 30,
                                   center_slab=True, symmetrize=True)
        for s in slabs:
            self.assertTrue(s.is_symmetric())
            self.assertGreater(len(s), len(self.Dy))
Exemplo n.º 35
0
 def get_slabs(self, atoms=None, index=None):
     """
     """
     from math import ceil
     if not atoms:
         atoms = self.atoms
     struct = AseAtomsAdaptor.get_structure(self.atoms)
     struct = SpacegroupAnalyzer(
         struct).get_conventional_standard_structure()
     slabgen = SlabGenerator(struct,
                             miller_index=index,
                             min_slab_size=self.min_slab_size,
                             min_vacuum_size=self.min_vacuum_size,
                             center_slab=False)
     slabs = {}
     for n, slab in enumerate(slabgen.get_slabs()):
         slab = AseAtomsAdaptor.get_atoms(slab)
         self.set_ase_cell(slab)
         a, b, c, alpha, beta, gamma = slab.get_cell_lengths_and_angles()
         supercell = [ceil(5 / a), ceil(5 / b), ceil(5 / c)]
         # print(a, b, c, supercell)
         slab = slab * supercell
         slabs[n] = slab.copy()
     return slabs
Exemplo n.º 36
0
def make_super_cell_pymatgen(jdata):
    make_unit_cell(jdata)

    out_dir = jdata['out_dir']
    path_uc = os.path.join(out_dir, global_dirname_02)
    from_path = path_uc
    from_file = os.path.join(from_path, 'POSCAR.unit')
    ss = Structure.from_file(from_file)

    all_millers = jdata['millers']
    path_sc = os.path.join(out_dir, global_dirname_02)
    z_min = jdata['z_min']
    super_cell = jdata['super_cell']

    cwd = os.getcwd()
    path_work = (path_sc)
    path_work = os.path.abspath(path_work)
    os.chdir(path_work)
    for miller in all_millers:
        miller_str = ""
        for ii in miller:
            miller_str += str(ii)
        path_cur_surf = create_path('surf-' + miller_str)
        os.chdir(path_cur_surf)
        slabgen = SlabGenerator(ss, miller, z_min, 1e-3)
        all_slabs = slabgen.get_slabs()
        dlog.info(os.getcwd())
        dlog.info("Miller %s: The slab has %s termination, use the first one" %
                  (str(miller), len(all_slabs)))
        all_slabs[0].to('POSCAR', 'POSCAR')
        if super_cell[0] > 1 or super_cell[1] > 1:
            st = Structure.from_file('POSCAR')
            st.make_supercell([super_cell[0], super_cell[1], 1])
            st.to('POSCAR', 'POSCAR')
        os.chdir(path_work)
    os.chdir(cwd)
Exemplo n.º 37
0
    def list_possible_2D_Slabs(b):
        global view, view_2, struc1, struc2, s1, s2, slabgen1, slabgen2, pressed
        pressed = 0
        slabgen1 = SlabGenerator(
            struc1,
            (int(h1_slide.value), int(k1_slide.value), int(l1_slide.value)),
            10,
            10,
            center_slab=True)
        slabgen2 = SlabGenerator(
            struc2,
            (int(h2_slide.value), int(k2_slide.value), int(l2_slide.value)),
            10,
            10,
            center_slab=True)

        with output_2:
            output_2.clear_output()
            print("For Material 1")
            print(
                "There are actually now %s terminations that can be generated"
                % (len(slabgen1.get_slabs())))
            i = 0
            print("S-ID \t Miller Indices Polar? Symmetric? ")
            for slab in slabgen1.get_slabs():
                print(i, "\t", slab.miller_index, "\t", slab.is_polar(), "\t",
                      slab.is_symmetric())
                i = i + 1
            i = 0
            SID1_slide.max = len(slabgen1.get_slabs()) - 1

            print("For Material 2")
            print(
                "There are actually now %s terminations that can be generated"
                % (len(slabgen2.get_slabs())))
            print("S-ID \t Miller Indices  Polar? Symmetric? ")
            for slab in slabgen2.get_slabs():
                print(i, "\t", slab.miller_index, "\t", slab.is_polar(), "\t",
                      slab.is_symmetric())
                i = i + 1

            SID2_slide.max = len(slabgen2.get_slabs()) - 1
            gen_slabs.disabled = False
Exemplo n.º 38
0
    return ax


ff = Structure(Lattice.from_parameters(2.45, 2.45, 2.45, 60, 60, 60), ['Cu'],
               [[0, 0, 0]])

CO = Molecule(["C", "O"], [[0, 0, 0], [0, 0, 1.5]])
H = Molecule(["H"], [[0, 0, 0]])

placement = [(CO, 'ontop', 0), (CO, 'hollow', 2), (H, 'hollow', 2)]

unit = SpacegroupAnalyzer(ff).get_conventional_standard_structure()

surfU = SlabGenerator(unit, [1, 1, 1], 6, 10)

slab = (surfU.get_slabs()[0])

slab = reorient_z(slab)
slab.make_supercell([2, 2, 1])

for p in placement:
    osites = len(
        AdsorbateSiteFinder(slab).find_adsorption_sites(
            symm_reduce=0)['ontop'])
    hsites = len(
        AdsorbateSiteFinder(slab).find_adsorption_sites(
            symm_reduce=0)['hollow'])
    bsites = len(
        AdsorbateSiteFinder(slab).find_adsorption_sites(
            symm_reduce=0)['bridge'])
    print osites, hsites, bsites
Exemplo n.º 39
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)
Exemplo n.º 40
0
def _mp_generate_slabs(struc,
                       hkl,
                       thickness,
                       vacuum,
                       is_symmetric=True,
                       center_slab=True,
                       **mp_kwargs):
    """
    Helper function for multiprocessing in ``slabs_get_single_hkl``, made so 
    that the information on slab and vacuum thickness is noted and carried 
    forward during multiprocessing. ``**mp_kwargs`` can take any 
    ``SlabGenerator`` argument.

    Args: 
        struc (`pymatgen Structure object`): Structure object decorated with 
            oxidation states 
        max_index (`int`): The maximum Miller index to go up to.
        thickness (`int`): Minimum slab thickness 
        vacuum (`int`): Minimum vacuum thickness
        is_symmetric (`bool`, optional): Whether the slabs cleaved should 
            have inversion symmetry. If bulk is non-centrosymmetric, 
            ``is_symmetric`` needs to be ``False`` - the function will return no
            slabs as it looks for inversion symmetry. Take care checking the 
            slabs for mirror plane symmetry before just using them. Defaults to 
            ``True``.  
        center_slab (`bool`, optional): The position of the slab in the 
            simulation cell. 
            
            * ``True``: the slab is centered with equal amounts of 
              vacuum above and below.

            * ``False``: the slab is at the bottom of the simulation cell with
              all of the vacuum on top of it. 

            Defaults to True. 

    Returns
        List of dicts of slabs and relevant metadata
    
    """

    SlabGenerator_kwargs = {
        'in_unit_planes': False,
        'primitive': True,
        'max_normal_search': None,
        'reorient_lattice': True,
        'lll_reduce': True
    }
    SlabGenerator_kwargs.update(
        (k, mp_kwargs[k])
        for k in SlabGenerator_kwargs.keys() & mp_kwargs.keys())

    get_slabs_kwargs = {
        'ftol': 0.1,
        'tol': 0.1,
        'max_broken_bonds': 0,
        'symmetrize': False,
        'repair': False,
        'bonds': None
    }
    get_slabs_kwargs.update(
        (k, mp_kwargs[k]) for k in get_slabs_kwargs.keys() & mp_kwargs.keys())

    slabs = []
    slabgen = SlabGenerator(struc,
                            hkl,
                            thickness,
                            vacuum,
                            center_slab=center_slab,
                            **SlabGenerator_kwargs)
    all_slabs = slabgen.get_slabs(**get_slabs_kwargs)

    h = slabgen._proj_height
    p = round(h / slabgen.parent.lattice.d_hkl(slabgen.miller_index), 8)
    if slabgen.in_unit_planes:
        nlayers_slab = int(math.ceil(slabgen.min_slab_size / p))
    else:
        nlayers_slab = int(math.ceil(slabgen.min_slab_size / h))

    for i, slab in enumerate(all_slabs):
        if is_symmetric == True:
            if not slab.is_polar() and slab.is_symmetric():
                slabs.append({
                    'hkl': ''.join(map(str, slab.miller_index)),
                    'slab_thickness': thickness,
                    'slab_layers': nlayers_slab,
                    'vac_thickness': vacuum,
                    'slab_index': i,
                    'slab': slab
                })

        else:
            if not slab.is_polar():
                slabs.append({
                    'hkl': ''.join(map(str, slab.miller_index)),
                    'slab_thickness': thickness,
                    'slab_layers': nlayers_slab,
                    'vac_thickness': vacuum,
                    'slab_index': i,
                    'slab': slab
                })

    return slabs
Exemplo n.º 41
0
def generate_slabs(structure,
                   hkl,
                   thicknesses,
                   vacuums,
                   save_slabs=True,
                   save_metadata=True,
                   json_fname=None,
                   make_fols=False,
                   make_input_files=False,
                   max_size=500,
                   center_slab=True,
                   ox_states=None,
                   is_symmetric=True,
                   layers_to_relax=None,
                   fmt='poscar',
                   name='POSCAR',
                   config_dict=None,
                   user_incar_settings=None,
                   user_kpoints_settings=None,
                   user_potcar_settings=None,
                   parallelise=True,
                   **kwargs):
    """
    Generates all unique slabs for a specified Miller indices or up to a maximum 
    Miller index with minimum slab and vacuum thicknesses. It includes all 
    combinations for multiple zero dipole symmetric terminations for 
    the same Miller index. 
    
    The function returns None by default and generates either: 

    (i) POSCAR_hkl_slab_vac_index.vasp (default) 
    (ii) hkl/slab_vac_index folders with structure files
    (iii) hkl/slab_vac_index with all VASP input files 
    
    Or if `save_slabs=False` a list of dicts of all unique slabs is returned. 
    
    Args:
        structure (`str` or pmg Structure obj): Filename of structure file in 
            any format supported by pymatgen or pymatgen structure object. 
        hkl (`tuple`, `list` or `int`): Miller index as tuple, a list of Miller 
            indices or a maximum index up to which the search should be 
            performed. E.g. if searching for slabs up to (2,2,2) ``hkl=2``
        thicknesses (`list`): The minimum size of the slab in Angstroms. 
        vacuums (`list`): The minimum size of the vacuum in Angstroms.
        save_slabs (`bool`, optional): Whether to save the slabs to file. 
            Defaults to ``True``.
        save_metadata (`bool`, optional): Whether to save the slabs' metadata to 
            file. Saves the entire slab object to a json file 
            Defaults to ``True``. 
        json_fname (`str`, optional): Filename of json metadata file. Defaults to
            bulk_formula_metadata.json
        make_fols (`bool`, optional): Makes folders for each termination 
            and slab/vacuum thickness combinations containing structure files. 
            
            * ``True``: A Miller index folder is created, in which folders 
              named slab_vac_index are created to which the relevant structure 
              files are saved. 
                    
                    E.g. for a (0,0,1) slab of index 1 with a slab thickness of 
                    20 Å and vacuum thickness of 30 Å the folder structure would 
                    be: ``001/20_30_1/POSCAR``  

            * ``False``: The indexed structure files are put in a folder named  
              after the bulk formula. 
              
                    E.g. for a (0,0,1) MgO slab of index 1 with a slab thickness 
                    of 20 Å and vacuum thickness of 30 Å the folder structure 
                    would be: ``MgO/POSCAR_001_20_30_1``

            Defaults to ``False``.    
        make_input_files (`bool`, optional): Makes INCAR, POTCAR and 
            KPOINTS files in each folder. If ``make_input_files`` is ``True`` 
            but ``make_files`` or ``save_slabs`` is ``False``, files will be 
            saved to folders regardless. This only works with VASP input files, 
            other formats are not yet supported. Defaults to ``False``. 
        max_size (`int`, optional): The maximum number of atoms in the slab 
            specified to raise warning about slab size. Even if the warning is 
            raised, it still outputs the slabs regardless. Defaults to ``500``. 
        center_slab (`bool`, optional): The position of the slab in the 
            simulation cell. 
            
            * ``True``: the slab is centered with equal amounts of 
              vacuum above and below.

            * ``False``: the slab is at the bottom of the simulation cell with
              all of the vacuum on top of it. 

            Defaults to True. 

        ox_states (``None``, `list` or  `dict`, optional): Add oxidation states 
            to the bulk structure. Different types of oxidation states specified 
            will result in different pymatgen functions used. The options are: 
            
            * if supplied as ``list``: The oxidation states are added by site 
                    
                    e.g. ``[3, 2, 2, 1, -2, -2, -2, -2]``
            
            * if supplied as ``dict``: The oxidation states are added by element
                    
                    e.g. ``{'Fe': 3, 'O':-2}``
            
            * if ``None``: The oxidation states are added by guess. 
              
            Defaults to ``None``. 
        is_symmetric (`bool`, optional): Whether the slabs cleaved should 
            have inversion symmetry. If bulk is non-centrosymmetric, 
            ``is_symmetric`` needs to be ``False`` - the function will return no
            slabs as it looks for inversion symmetry. Take care checking the 
            slabs for mirror plane symmetry before just using them. Defaults to 
            ``True``. 
        layers_to_relax (`int`, optional): Specifies the number of layers at the 
            top and bottom of the slab that should be relaxed, keeps the centre
            constrained using selective dynamics. NB only works for VASP files 
        fmt (`str`, optional): The format of the output structure files. Options 
            include 'cif', 'poscar', 'cssr', 'json', not case sensitive. 
            Defaults to 'poscar'. 
        name (`str`, optional): The name of the surface slab structure file 
            created. Case sensitive. Defaults to 'POSCAR'
        config_dict (`dict` or `str`, optional): Specifies the dictionary used 
            for the generation of the input files. Suppports already loaded 
            dictionaires, yaml and json files. Surfaxe-supplied dictionaries 
            are PBE (``pe``), PBEsol (``ps``) and HSE06 (``hse06``) for single 
            shot calculations and PBE (``pe_relax``) and PBEsol (``ps_relax``) 
            for relaxations. Not case sensitive. Defaults to PBEsol (``ps``). 
        user_incar_settings (`dict`, optional): Overrides the default INCAR 
            parameter settings. Defaults to ``None``.
        user_kpoints_settings (`dict` or Kpoints object, optional): 
            Overrides the default kpoints settings. If it is supplied  
            as `dict`, it should be as ``{'reciprocal_density': 100}``. Defaults 
            to ``None``.
        user_potcar_settings (`dict`, optional): Overrides the default POTCAR 
            settings. Defaults to ``None``.
        parallelise (`bool`, optional): Use multiprocessing to generate
            slabs. Defaults to ``True``. 

    Returns:
        None (default) 
        or unique_slabs (list of dicts) 
    """

    # Set up additional arguments for multiprocessing and saving slabs
    mp_kwargs = {
        'in_unit_planes': False,
        'primitive': True,
        'max_normal_search': None,
        'reorient_lattice': True,
        'lll_reduce': True,
        'ftol': 0.1,
        'tol': 0.1,
        'max_broken_bonds': 0,
        'symmetrize': False,
        'repair': False,
        'bonds': None
    }
    mp_kwargs.update((k, kwargs[k]) for k in mp_kwargs.keys() & kwargs.keys())

    save_slabs_kwargs = {
        'user_incar_settings': None,
        'user_kpoints_settings': None,
        'user_potcar_settings': None,
        'constrain_total_magmom': False,
        'sort_structure': True,
        'potcar_functional': None,
        'user_potcar_functional': None,
        'force_gamma': False,
        'reduce_structure': None,
        'vdw': None,
        'use_structure_charge': False,
        'standardize': False,
        'sym_prec': 0.1,
        'international_monoclinic': True
    }
    save_slabs_kwargs.update(
        (k, kwargs[k]) for k in save_slabs_kwargs.keys() & kwargs.keys())
    save_slabs_kwargs.update({
        'user_incar_settings': user_incar_settings,
        'user_kpoints_settings': user_kpoints_settings,
        'user_potcar_settings': user_potcar_settings
    })

    # Import bulk relaxed structure, add oxidation states for slab dipole
    # calculations
    struc = _instantiate_structure(structure)

    # Check structure is conventional standard and warn if not
    sga = SpacegroupAnalyzer(struc)
    cs = sga.get_conventional_standard_structure()
    if cs.lattice != struc.lattice:
        warnings.formatwarning = _custom_formatwarning
        warnings.warn(
            'Lattice of the structure provided does not match the '
            'conventional standard structure. Miller indices are lattice dependent,'
            ' make sure you are using the correct bulk structure ')

    # Check if oxidation states were added to the bulk already
    struc = oxidation_states(struc, ox_states)

    # Check if hkl provided as tuple or int, find all available hkl if
    # provided as int; make into a list to iterate over
    if type(hkl) == tuple:
        miller = [hkl]
    elif type(hkl) == int:
        miller = get_symmetrically_distinct_miller_indices(struc, hkl)
    elif type(hkl) == list and all(isinstance(x, tuple) for x in hkl):
        miller = hkl
    else:
        raise TypeError(
            'Miller index should be supplied as tuple, int or list '
            'of tuples')

    # create all combinations of hkl, slab and vacuum thicknesses
    combos = itertools.product(miller, thicknesses, vacuums)

    # Check if bulk structure is noncentrosymmetric if is_symmetric=True,
    # change to False if not to make sure slabs are produced, issues warning
    if is_symmetric:
        sg = SpacegroupAnalyzer(struc)
        if not sg.is_laue():
            is_symmetric = False
            warnings.formatwarning = _custom_formatwarning
            warnings.warn(
                ('Inversion symmetry was not found in the bulk '
                 'structure, slabs produced will be non-centrosymmetric'))

    # Check if multiple cores are available, then iterate through the slab and
    # vacuum thicknesses and get all non polar symmetric slabs
    if multiprocessing.cpu_count() > 1 and parallelise == True:
        with multiprocessing.Pool() as pool:
            nested_provisional = pool.starmap(
                functools.partial(_mp_generate_slabs,
                                  struc,
                                  is_symmetric=is_symmetric,
                                  center_slab=center_slab,
                                  **mp_kwargs), combos)

        provisional = list(itertools.chain.from_iterable(nested_provisional))

    else:
        # Set up kwargs again
        SG_kwargs = {
            k: mp_kwargs[k]
            for k in [
                'in_unit_planes', 'primitive'
                'max_normal_search', 'reorient_lattice', 'lll_reduce'
            ]
        }
        gs_kwargs = {
            k: mp_kwargs[k]
            for k in [
                'ftol', 'tol', 'max_broken_bonds', 'symmetrize', 'repair',
                'bonds'
            ]
        }

        provisional = []
        for hkl, thickness, vacuum in combos:
            slabgen = SlabGenerator(struc,
                                    hkl,
                                    thickness,
                                    vacuum,
                                    center_slab=center_slab,
                                    **SG_kwargs)

            # Get the number of layers in the slab
            h = slabgen._proj_height
            p = round(h / slabgen.parent.lattice.d_hkl(slabgen.miller_index),
                      8)
            if slabgen.in_unit_planes:
                nlayers_slab = int(math.ceil(slabgen.min_slab_size / p))
            else:
                nlayers_slab = int(math.ceil(slabgen.min_slab_size / h))

            slabs = slabgen.get_slabs(**gs_kwargs)
            for i, slab in enumerate(slabs):
                # Get all the zero-dipole slabs with inversion symmetry
                if is_symmetric:
                    if slab.is_symmetric() and not slab.is_polar():
                        provisional.append({
                            'hkl':
                            ''.join(map(str, slab.miller_index)),
                            'slab_thickness':
                            thickness,
                            'slab_layers':
                            nlayers_slab,
                            'vac_thickness':
                            vacuum,
                            'slab_index':
                            i,
                            'slab':
                            slab
                        })

                # Get all the zero-dipole slabs wihtout inversion symmetry
                else:
                    if not slab.is_polar():
                        provisional.append({
                            'hkl':
                            ''.join(map(str, slab.miller_index)),
                            'slab_thickness':
                            thickness,
                            'slab_layers':
                            nlayers_slab,
                            'vac_thickness':
                            vacuum,
                            'slab_index':
                            i,
                            'slab':
                            slab
                        })

    # Iterate though provisional slabs to extract the unique slabs
    unique_list_of_dicts, repeat, large = _filter_slabs(provisional, max_size)

    if layers_to_relax is not None and fmt.lower() == 'poscar':
        unique_list_of_dicts, small = _get_selective_dynamics(
            struc, unique_list_of_dicts, layers_to_relax)

        if small:
            warnings.formatwarning = _custom_formatwarning
            warnings.warn(
                'Some slabs were too thin to fix the centre of the slab.'
                ' Slabs with no selective dynamics applied are: ' +
                ', '.join(map(str, small)))

    # Warnings for too large, too small, repeated and no slabs;
    if repeat:
        warnings.formatwarning = _custom_formatwarning
        warnings.warn('Not all combinations of hkl or slab/vac thicknesses '
                      'were generated because of repeat structures. '
                      'The repeat slabs are: ' + ', '.join(map(str, repeat)))

    if large:
        warnings.formatwarning = _custom_formatwarning
        warnings.warn('Some generated slabs exceed the max size specified.'
                      ' Slabs that exceed the max size are: ' +
                      ', '.join(map(str, large)))

    if len(unique_list_of_dicts) == 0:
        raise ValueError(
            'No zero dipole slabs found for specified Miller index')

    # Save the metadata or slabs to file or return the list of dicts
    if save_metadata:
        bulk_name = struc.composition.reduced_formula
        if json_fname is None:
            json_fname = '{}_metadata.json'.format(bulk_name)
        unique_list_of_dicts_copy = deepcopy(unique_list_of_dicts)
        for i in unique_list_of_dicts_copy:
            i['slab'] = i['slab'].as_dict()
        with open(json_fname, 'w') as f:
            json.dump(unique_list_of_dicts_copy, f)

    if save_slabs:
        slabs_to_file(list_of_slabs=unique_list_of_dicts,
                      structure=struc,
                      make_fols=make_fols,
                      make_input_files=make_input_files,
                      config_dict=config_dict,
                      fmt=fmt,
                      name=name,
                      **save_slabs_kwargs)

    else:
        return unique_list_of_dicts