Exemplo n.º 1
0
    def setUp(self):
        zno1 = Structure.from_file(get_path("ZnO-wz.cif"), primitive=False)
        zno55 = SlabGenerator(zno1, [1, 0, 0],
                              5,
                              5,
                              lll_reduce=False,
                              center_slab=False).get_slab()

        Ti = Structure(
            Lattice.hexagonal(4.6, 2.82),
            ["Ti", "Ti", "Ti"],
            [
                [0.000000, 0.000000, 0.000000],
                [0.333333, 0.666667, 0.500000],
                [0.666667, 0.333333, 0.500000],
            ],
        )

        Ag_fcc = Structure(
            Lattice.cubic(4.06),
            ["Ag", "Ag", "Ag", "Ag"],
            [
                [0.000000, 0.000000, 0.000000],
                [0.000000, 0.500000, 0.500000],
                [0.500000, 0.000000, 0.500000],
                [0.500000, 0.500000, 0.000000],
            ],
        )

        m = [[3.913449, 0, 0], [0, 3.913449, 0], [0, 0, 5.842644]]
        latt = Lattice(m)
        fcoords = [[0.5, 0, 0.222518], [0, 0.5, 0.777482], [0, 0, 0],
                   [0, 0, 0.5], [0.5, 0.5, 0]]
        non_laue = Structure(latt, ["Nb", "Nb", "N", "N", "N"], fcoords)

        self.ti = Ti
        self.agfcc = Ag_fcc
        self.zno1 = zno1
        self.zno55 = zno55
        self.nonlaue = non_laue
        self.h = Structure(Lattice.cubic(3), ["H"], [[0, 0, 0]])
        self.libcc = Structure(Lattice.cubic(3.51004), ["Li", "Li"],
                               [[0, 0, 0], [0.5, 0.5, 0.5]])
Exemplo n.º 2
0
 def test_bonds_broken(self):
     # Querying the Materials Project database for Si
     s = self.get_structure("Si")
     # 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.º 3
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))
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.º 5
0
def findHeight(unit, facet, layers, sym):
    """
	Finds the slab thickness input necessary for SlabGenerator to produce slab with correct number of layers
	"""
    x0, dx, n = 1, 1, None  # Angstrom
    getN = lambda x: len(
        SlabGenerator(unit, facet, x, 4, primitive=True).get_slabs(symmetrize=
                                                                   sym)[0])
    while n != layers:
        x0 += dx
        n = getN(x0)
        if n > 20:
            raise ValueError, (
                'over ten atoms in primitive surface column' +
                ' ... primitive cell may contain multiple atoms (' +
                str(getN(0.1)) +
                ') not compatible with requested # of layers (' + str(layers) +
                ')')
    return x0
Exemplo n.º 6
0
    def findHeight(self):
        """
		Finds the slab thickness input necessary for SlabGenerator to produce slab with correct number of layers
		"""
        from pymatgen.core.surface import SlabGenerator
        x0, dx, n = 1, 1, None  # Angstrom
        getN = lambda x: len(
            SlabGenerator(self.bulkUnit(), self.facet, x, 4, primitive=True).
            get_slabs(symmetrize=self.symmetric)[0])
        while n != self.scale[2]:
            #print x0,n
            x0 += dx
            n = getN(x0)
            if n > 20:
                raise ValueError, 'over ten atoms in primitive surface column ... primitive cell may contain multiple atoms (' + str(
                    getN(0.1)
                ) + ') not compatible with requested # of layers (' + str(
                    self.scale[2]) + ')'
        return x0
Exemplo n.º 7
0
    def from_zsl(
        cls,
        match: ZSLMatch,
        film: Structure,
        film_miller,
        substrate_miller,
        elasticity_tensor=None,
        ground_state_energy=0,
    ):
        """Generate a substrate match from a ZSL match plus metadata"""

        # Get the appropriate surface structure
        struc = SlabGenerator(film, film_miller, 20, 15,
                              primitive=False).get_slab().oriented_unit_cell

        dfm = Deformation(match.match_transformation)

        strain = dfm.green_lagrange_strain.convert_to_ieee(struc,
                                                           initial_fit=False)
        von_mises_strain = strain.von_mises_strain

        if elasticity_tensor is not None:
            energy_density = elasticity_tensor.energy_density(strain)

            elastic_energy = film.volume * energy_density / len(film.sites)
        else:
            elastic_energy = 0

        match_dict = match.as_dict()

        cls(
            film_miller=film_miller,
            substrate_miller=substrate_miller,
            strain=strain,
            von_mises_strain=von_mises_strain,
            elastic_energy=elastic_energy,
            ground_state_energy=ground_state_energy,
            **{
                k: match_dict[k]
                for k in match_dict.keys() if not k.startswith("@")
            },
        )
Exemplo n.º 8
0
    def test_init(self):
        zno_slab = Slab(
            self.zno55.lattice,
            self.zno55.species,
            self.zno55.frac_coords,
            self.zno55.miller_index,
            self.zno55.oriented_unit_cell,
            0,
            self.zno55.scale_factor,
        )
        m = self.zno55.lattice.matrix
        area = np.linalg.norm(np.cross(m[0], m[1]))
        self.assertAlmostEqual(zno_slab.surface_area, area)
        self.assertEqual(zno_slab.lattice.parameters, self.zno55.lattice.parameters)
        self.assertEqual(zno_slab.oriented_unit_cell.composition, self.zno1.composition)
        self.assertEqual(len(zno_slab), 8)

        # check reorient_lattice. get a slab not oriented and check that orientation
        # works even with cartesian coordinates.
        zno_not_or = SlabGenerator(
            self.zno1,
            [1, 0, 0],
            5,
            5,
            lll_reduce=False,
            center_slab=False,
            reorient_lattice=False,
        ).get_slab()
        zno_slab_cart = Slab(
            zno_not_or.lattice,
            zno_not_or.species,
            zno_not_or.cart_coords,
            zno_not_or.miller_index,
            zno_not_or.oriented_unit_cell,
            0,
            zno_not_or.scale_factor,
            coords_are_cartesian=True,
            reorient_lattice=True,
        )
        self.assertArrayAlmostEqual(zno_slab.frac_coords, zno_slab_cart.frac_coords)
        c = zno_slab_cart.lattice.matrix[2]
        self.assertArrayAlmostEqual([0, 0, np.linalg.norm(c)], c)
Exemplo n.º 9
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.º 10
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.º 11
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))
Exemplo n.º 12
0
def calculate_unit_slab_height(atoms,
                               miller_indices,
                               slab_generator_settings=None):
    '''
    Calculates the height of the smallest unit slab from a given bulk and
    Miller cut

    Args:
        atoms                   An `ase.Atoms` object of the bulk you want to
                                make a surface out of
        miller_indices          A 3-tuple of integers representing the Miller
                                indices of the surface you want to make
        slab_generator_settings A dictionary that can be passed as kwargs to
                                instantiate the
                                `pymatgen.core.surface.SlabGenerator` class.
                                Defaults to the settings in
                                `gaspy.defaults.slab_settings`.
    Returns:
        height  A float corresponding the height (in Angstroms) of the smallest
                unit slab
    '''
    if slab_generator_settings is None:
        slab_generator_settings = slab_settings()['slab_generator_settings']
        # We don't care about these things
        del slab_generator_settings['min_vacuum_size']
        del slab_generator_settings['min_slab_size']

    # Instantiate a pymatgen `SlabGenerator`
    structure = AseAtomsAdaptor.get_structure(atoms)
    sga = SpacegroupAnalyzer(structure, symprec=0.1)
    structure = sga.get_conventional_standard_structure()
    gen = SlabGenerator(initial_structure=structure,
                        miller_index=miller_indices,
                        min_vacuum_size=0.,
                        min_slab_size=0.,
                        **slab_generator_settings)

    # Get and return the height
    height = gen._proj_height
    return height
Exemplo n.º 13
0
def makeSlab(bulkASE, facList, lay, sym, xy, vac):
    a = asedb.get_atoms(id=bulkASE)
    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([str(xy)[0], str(xy)[1], 1])
    return reorient_z(slab)
Exemplo n.º 14
0
    def create_slab(self, vacuum=12, thickness=10):
        """
        set the vacuum spacing, slab thickness and call sd_flags
        for top 2 layers

        returns the poscar corresponding to the modified structure
        """
        strt_structure = self.input_structure.copy()
        if self.from_ase:
            slab_struct = get_ase_slab(strt_structure, hkl=self.system['hkl'],
                                       min_thick=thickness, min_vac=vacuum)
        else:
            slab_struct = SlabGenerator(initial_structure=strt_structure,
                                        miller_index=self.system['hkl'],
                                        min_slab_size=thickness,
                                        min_vacuum_size=vacuum,
                                        lll_reduce=False, center_slab=True,
                                        primitive=False).get_slab()
        slab_struct.sort()
        sd = self.set_sd_flags(slab_struct)
        comment = 'VAC' + str(vacuum) + 'THICK' + str(thickness)
        return Poscar(slab_struct, comment=comment,
                      selective_dynamics=sd)
Exemplo n.º 15
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.º 16
0
    def setUp(self):
        zno1 = Structure.from_file(get_path("ZnO-wz.cif"), primitive=False)
        zno55 = SlabGenerator(zno1, [1, 0, 0], 5, 5, lll_reduce=False,
                              center_slab=False).get_slab()

        Ti = Structure(Lattice.hexagonal(4.6, 2.82), ["Ti", "Ti", "Ti"],
                       [[0.000000, 0.000000, 0.000000],
                        [0.333333, 0.666667, 0.500000],
                        [0.666667, 0.333333, 0.500000]])

        Ag_fcc = Structure(Lattice.cubic(4.06), ["Ag", "Ag", "Ag", "Ag"],
                           [[0.000000, 0.000000, 0.000000],
                            [0.000000, 0.500000, 0.500000],
                            [0.500000, 0.000000, 0.500000],
                            [0.500000, 0.500000, 0.000000]])

        self.ti = Ti
        self.agfcc = Ag_fcc
        self.zno1 = zno1
        self.zno55 = zno55
        self.h = Structure(Lattice.cubic(3), ["H"],
                           [[0, 0, 0]])
        self.libcc = Structure(Lattice.cubic(3.51004), ["Li", "Li"],
                               [[0, 0, 0], [0.5, 0.5, 0.5]])
Exemplo n.º 17
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.º 18
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.º 19
0
 def __init__(self,
              strt,
              hkl=[1, 1, 1],
              min_thick=10,
              min_vac=10,
              supercell=[1, 1, 1],
              name=None,
              adsorb_on_species=None,
              adatom_on_lig=None,
              ligand=None,
              displacement=1.0,
              surface_coverage=None,
              scell_nmax=10,
              coverage_tol=0.25,
              solvent=None,
              start_from_slab=False,
              validate_proximity=False,
              to_unit_cell=False,
              coords_are_cartesian=False,
              primitive=True,
              from_ase=False,
              lll_reduce=False,
              center_slab=True,
              max_normal_search=None,
              force_normalize=False,
              x_shift=0,
              y_shift=0,
              rot=[0, 0, 0]):
     self.from_ase = from_ase
     vac_extension = 0
     if ligand is not None:
         vac_extension = ligand.max_dist
     if isinstance(strt, Structure) and not isinstance(strt, Slab):
         self.min_vac = min_vac + vac_extension
         if self.from_ase:
             strt = get_ase_slab(strt,
                                 hkl=hkl,
                                 min_thick=min_thick,
                                 min_vac=min_vac + vac_extension)
         else:
             slab = SlabGenerator(strt,
                                  hkl,
                                  min_thick,
                                  min_vac + vac_extension,
                                  center_slab=center_slab,
                                  lll_reduce=lll_reduce,
                                  max_normal_search=max_normal_search,
                                  primitive=primitive).get_slab()
             if force_normalize:
                 strt = slab.get_orthogonal_c_slab()
             else:
                 strt = slab
         strt.make_supercell(supercell)
     else:
         self.min_vac = min_vac
     Slab.__init__(self,
                   strt.lattice,
                   strt.species_and_occu,
                   strt.frac_coords,
                   miller_index=strt.miller_index,
                   oriented_unit_cell=strt.oriented_unit_cell,
                   shift=strt.shift,
                   scale_factor=strt.scale_factor,
                   validate_proximity=validate_proximity,
                   to_unit_cell=to_unit_cell,
                   coords_are_cartesian=coords_are_cartesian,
                   site_properties=strt.site_properties,
                   energy=strt.energy)
     self.strt = strt
     self.name = name
     self.hkl = hkl
     self.min_thick = min_thick
     self.supercell = supercell
     self.ligand = ligand
     self.slab = strt
     self.displacement = displacement
     self.solvent = solvent
     self.surface_coverage = surface_coverage
     self.adsorb_on_species = adsorb_on_species
     self.adatom_on_lig = adatom_on_lig
     self.scell_nmax = scell_nmax
     self.coverage_tol = coverage_tol
     self.x_shift = x_shift
     self.y_shift = y_shift
     self.rot = rot
Exemplo n.º 20
0
    ax.set_xlim(x_lim)
    ax.set_ylim(y_lim)
    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(
Exemplo n.º 21
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.º 22
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.º 23
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
Exemplo n.º 24
0
# Import the neccesary tools to generate surfaces
from pymatgen.core.surface import SlabGenerator, generate_all_slabs, Structure, Lattice
# Import the neccesary tools for making a Wulff shape
from pymatgen.analysis.wulff import WulffShape

import os

lattice = Lattice.cubic(3.508)

Ni = Structure(lattice, ["Ni", "Ni", "Ni", "Ni"],
               [[0, 0, 0], [0, 0.5, 0], [0.5, 0, 0], [0, 0, 0.5]])
slabgen = SlabGenerator(Ni, (1, 1, 1), 10, 10)

lattice = Lattice.cubic(5.46873)
Si = Structure(lattice, ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"],
               [[0.00000, 0.00000, 0.50000], [0.75000, 0.75000, 0.75000],
                [0.00000, 0.50000, 0.00000], [0.75000, 0.25000, 0.25000],
                [0.50000, 0.00000, 0.00000], [0.25000, 0.75000, 0.25000],
                [0.50000, 0.50000, 0.50000], [0.25000, 0.25000, 0.75000]])

slabgen = SlabGenerator(Si, (1, 1, 1), 10, 10)
print("Notice now there are actually now %s terminations that can be \
generated in the (111) direction for diamond Si" % (len(slabgen.get_slabs())))
Exemplo n.º 25
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, max_normal_search=1)
        slab = gen.get_slab()
        self.assertEqual(len(slab), 6)
        gen = SlabGenerator(fcc, [1, 1, 1], 10, 10, primitive=False, max_normal_search=1)
        slab_non_prim = gen.get_slab()
        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")
                    or sg.int_number
                    in [
                        143,
                        144,
                        145,
                        147,
                        149,
                        150,
                        151,
                        152,
                        153,
                        154,
                        156,
                        157,
                        158,
                        159,
                        162,
                        163,
                        164,
                        165,
                    ]
                )
            ):
                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 create_min_thickness_slab(structure,
                              index,
                              num_termination_min,
                              layerthickness,
                              layer_subtractions,
                              tol,
                              min_thickness=10):
    #Find final_slabthickness such that the cleaned slab is a least min_thickness thick after layer_subtractions times the average layerthickness
    #Cleaning consists of removing one layer on each side and then ensuring that the number of atomic layers is multiples of the number of unique terminations

    slabthickness = min_thickness
    final_slabthickness = 0
    while final_slabthickness < min_thickness + layerthickness * layer_subtractions:
        final_slab = SlabGenerator(structure,
                                   index,
                                   min_slab_size=slabthickness,
                                   min_vacuum_size=5,
                                   primitive=True,
                                   center_slab=True,
                                   in_unit_planes=False,
                                   max_normal_search=7).get_slab()
        final_slabthickness = np.max(final_slab.cart_coords[:, 2]) - np.min(
            final_slab.cart_coords[:, 2])
        slabthickness += 2  #increase slabthickness for potential next iteration

        if final_slabthickness > 5:  #Ensure that slab is thick enough for subtractions
            #"Clean" the two outside surfaces, pymatgen makes weird terminations sometimes
            min_value = np.min(final_slab.cart_coords[:, 2])
            first_layer_size = len(
                np.where(
                    np.abs(min_value - final_slab.cart_coords[:, 2]) < tol)[0])
            for i in range(first_layer_size):
                index_to_remove = np.argmin(final_slab.frac_coords[:, 2])
                final_slab.remove_sites([index_to_remove])

            max_value = np.max(final_slab.cart_coords[:, 2])
            first_layer_size = len(
                np.where(
                    np.abs(max_value - final_slab.cart_coords[:, 2]) < tol)[0])
            for i in range(first_layer_size):
                index_to_remove = np.argmax(final_slab.frac_coords[:, 2])
                final_slab.remove_sites([index_to_remove])

            nlayers = number_of_atomic_layers(final_slab, tol)

            #Delete all layers that are not multiple of num_termination_min (e.g. make ABCABCAB -> ABCABC; important for slabs with mirror symmetry)
            n_extra_layers = nlayers % num_termination_min
            if n_extra_layers != 0 and n_extra_layers < nlayers:
                for layer in range(n_extra_layers):
                    max_value = np.max(final_slab.cart_coords[:, 2])
                    first_layer_size = len(
                        np.where(
                            np.abs(max_value -
                                   final_slab.cart_coords[:, 2]) < tol)[0])
                    for i in range(first_layer_size):
                        index_to_remove = np.argmax(final_slab.frac_coords[:,
                                                                           2])
                        final_slab.remove_sites([index_to_remove])

            final_slabthickness = np.max(
                final_slab.cart_coords[:, 2]) - np.min(
                    final_slab.cart_coords[:, 2])

    while final_slabthickness - 1.3 * num_termination_min * layerthickness > min_thickness + layerthickness * layer_subtractions:
        for nterm in range(num_termination_min):
            max_value = np.max(final_slab.cart_coords[:, 2])
            first_layer_size = len(
                np.where(
                    np.abs(max_value - final_slab.cart_coords[:, 2]) < tol)[0])
            for i in range(first_layer_size):
                index_to_remove = np.argmax(final_slab.frac_coords[:, 2])
                final_slab.remove_sites([index_to_remove])

        final_slabthickness = np.max(final_slab.cart_coords[:, 2]) - np.min(
            final_slab.cart_coords[:, 2])
    return final_slab
Exemplo n.º 27
0
    def get_slabs(self,
                  max_index: int = 1,
                  min_slab_size: float = 5.0,
                  min_vacuum_size: float = 15.0,
                  is_fix_vacuum_size: bool = False,
                  bonds: Dict[Tuple[str, str], float] = None,
                  tolerance: float = 0.001,
                  max_broken_bonds: int = 0,
                  is_lll_reduce: bool = False,
                  is_center_slab: bool = False,
                  is_primitive: bool = True,
                  max_normal_search: int = None,
                  is_symmetrize: bool = False,
                  is_repair: bool = False,
                  is_in_unit_planes: bool = False) -> List[Structure]:
        '''
    Search and return the slabs found in the given structure.

    @in
      - max_index, int, max of miller index.
        e.g. when max_index = 1 for cubic structure, only (100), (110), (111)
        miller surfaces are searched.
      - min_slab_size, float, minimum size in angstroms of layers containing atoms
      - min_vacuum_size, float, minimum size in angstroms of vacuum layer
      - is_fix_vacuum_size, bool, Not implemented yet
      - bonds, {(str, str): float}, specify the maximum length of bond length for
        given atom pairs to avoid bond broken.
      - tolerance, float, accuracy
      - max_broken_bonds, int
      - is_lll_reduce, bool, whether or not the slabs will be orthogonalized
      - is_center_slab, bool, whether or not the slabs will be centered between
        the vacuum layer
      - is_primitive, bool, whether to reduce any generated slabs to a primitive
        cell
      - max_normal_search, If set to a positive integer, the code will conduct a
        search for a normal lattice vector that is as perpendicular to the surface
        as possible by considering multiples linear combinations of lattice vectors
        up to max_normal_search.
      - is_symmetrize, bool, Whether or not to ensure the surfaces of the slabs
        are equivalent
      - is_repair, bool, whether to repair terminations with broken bonds or just
        omit them
      - is_in_unit_planes, bool, whether to set min_slab_size and min_vac_size
        in units of hkl planes (True) or Angstrom (False/default)
    @out
    '''

        st = self.old_structure.copy()
        all_slabs = []
        for miller in get_symmetrically_distinct_miller_indices(st, max_index):
            if is_fix_vacuum_size:
                pass

            else:
                vacuum_size = random.random() * min_vacuum_size

            gen = SlabGenerator(st,
                                miller,
                                min_slab_size,
                                vacuum_size,
                                lll_reduce=is_lll_reduce,
                                center_slab=is_center_slab,
                                primitive=is_primitive,
                                max_normal_search=max_normal_search,
                                in_unit_planes=is_in_unit_planes)
            slabs = gen.get_slabs(bonds=bonds,
                                  tol=tolerance,
                                  symmetrize=is_symmetrize,
                                  max_broken_bonds=max_broken_bonds,
                                  repair=is_repair)

            if len(slabs) > 0:
                all_slabs.extend(slabs)

        self.operations.append({'slabs': len(slabs)})
        return all_slabs
Exemplo n.º 28
0
def pmg_surfer(mpid='',
               vacuum=15,
               mat=None,
               max_index=1,
               min_slab_size=15,
               write_file=True):
    """
    Pymatgen surface builder for a Poscar

    Args:
        vacuum: vacuum region
        mat: Structure object
        max_index: maximum miller index
        min_slab_size: minimum slab size

    Returns:
           structures: list of surface Structure objects
    """

    if mat == None:
        with MPRester() as mp:
            mat = mp.get_structure_by_material_id(mpid)
        if mpid == '':
            print('Provide structure')

    sg_mat = SpacegroupAnalyzer(mat)
    mat_cvn = sg_mat.get_conventional_standard_structure()
    mat_cvn.sort()
    indices = get_symmetrically_distinct_miller_indices(mat_cvn, max_index)
    #ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn)

    structures = []
    pos = Poscar(mat_cvn)
    try:
        pos.comment = str('sbulk') + str('@') + str('vac') + str(vacuum) + str(
            '@') + str('size') + str(min_slab_size)
    except:
        pass
    structures.append(pos)
    if write_file == True:
        mat_cvn.to(fmt='poscar',
                   filename=str('POSCAR-') + str('cvn') + str('.vasp'))
    for i in indices:
        slab = SlabGenerator(initial_structure=mat_cvn,
                             miller_index=i,
                             min_slab_size=min_slab_size,
                             min_vacuum_size=vacuum,
                             lll_reduce=False,
                             center_slab=True,
                             primitive=False).get_slab()
        normal_slab = slab.get_orthogonal_c_slab()
        slab_pymatgen = Poscar(normal_slab).structure
        #ase_slab.center(vacuum=vacuum, axis=2)
        #slab_pymatgen = AseAtomsAdaptor().get_structure(ase_slab)
        xy_size = min_slab_size
        dim1 = int((float(xy_size) /
                    float(max(abs(slab_pymatgen.lattice.matrix[0]))))) + 1
        dim2 = int(
            float(xy_size) /
            float(max(abs(slab_pymatgen.lattice.matrix[1])))) + 1
        slab_pymatgen.make_supercell([dim1, dim2, 1])
        slab_pymatgen.sort()
        surf_name = '_'.join(map(str, i))
        pos = Poscar(slab_pymatgen)
        try:
            pos.comment = str("Surf-") + str(surf_name) + str('@') + str(
                'vac') + str(vacuum) + str('@') + str('size') + str(
                    min_slab_size)
        except:
            pass
        if write_file == True:
            pos.write_file(filename=str('POSCAR-') + str("Surf-") +
                           str(surf_name) + str('.vasp'))
        structures.append(pos)

    return structures
Exemplo n.º 29
0
def get_dimensionality_gorai(
    structure,
    max_hkl=2,
    el_radius_updates=None,
    min_slab_size=5,
    min_vacuum_size=5,
    standardize=True,
    bonds=None,
):
    """
    This method returns whether a structure is 3D, 2D (layered), or 1D (linear
    chains or molecules) according to the algorithm published in Gorai, P.,
    Toberer, E. & Stevanovic, V. Computational Identification of Promising
    Thermoelectric Materials Among Known Quasi-2D Binary Compounds. J. Mater.
    Chem. A 2, 4136 (2016).

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

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

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

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

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

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

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

    return 3 - min(num_surfaces, 2)
Exemplo n.º 30
0
    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