Example #1
0
    def generate(self,
                 film,
                 substrate,
                 film_millers=None,
                 substrate_millers=None,
                 lowest=False):
        """
        Generates the film/substrate combinations for either set miller
        indicies or all possible miller indices up to a max miller index

        Args:
            film(Structure):  Conventional standard pymatgen structure for
                the film
            substrate(Struture): Conventional standard pymatgen Structure
                for the substrate
            film_millers(array): array of film miller indicies to consider
                in the matching algorithm
            substrate_millers(array): array of substrate miller indicies to
                consider in the matching algorithm
        """

        # Sets film and substrate for search
        self.substrate = substrate
        self.film = film

        # Generate miller indicies if none specified for film
        if film_millers is None:
            film_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.film, self.film_max_miller))

        # Generate miller indicies if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.substrate, self.substrate_max_miller))

        # Check each miller index combination
        for [
                film_area, substrate_area, film_vectors, substrate_vectors,
                film_miller, substrate_miller
        ] in self.generate_slabs(film_millers, substrate_millers):
            # Generate all super lattice comnbinations for a given set of miller
            # indicies
            transformations = self.generate_sl_transformations(
                film_area, substrate_area)
            # Check each super-lattice pair to see if they match
            for match in self.check_transformations(transformations,
                                                    film_vectors,
                                                    substrate_vectors):
                # Yield the match area, the miller indicies,
                yield self.match_as_dict(film_miller, substrate_miller,
                                         match[0], match[1],
                                         film_vectors, substrate_vectors,
                                         vec_area(*match[0]))

                # Just want lowest match per direction
                if (lowest):
                    break
Example #2
0
    def calculate(self,
                  film,
                  substrate,
                  elasticity_tensor=None,
                  film_millers=None,
                  substrate_millers=None,
                  ground_state_energy=0,
                  lowest=False):
        """
        Finds all topological matches for the substrate and calculates elastic
        strain energy and total energy for the film if elasticity tensor and
        ground state energy are provided:

        Args:
            film(Structure): conventional standard structure for the film
            substrate(Structure): conventional standard structure for the
                substrate
            elasticity_tensor(ElasticTensor): elasticity tensor for the film
                in the IEEE orientation
            film_millers(array): film facets to consider in search as defined by
                miller indicies
            substrate_millers(array): substrate facets to consider in search as
                defined by miller indicies
            ground_state_energy(float): ground state energy for the film
            lowest(bool): only consider lowest matching area for each surface
        """
        self.film = film
        self.substrate = substrate

        # Generate miller indicies if none specified for film
        if film_millers is None:
            film_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.film, self.film_max_miller))

        # Generate miller indicies if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.substrate, self.substrate_max_miller))

        # Check each miller index combination
        surface_vector_sets = self.generate_surface_vectors(
            film_millers, substrate_millers)
        for [film_vectors, substrate_vectors, film_miller,
             substrate_miller] in surface_vector_sets:
            for match in self.zsl(film_vectors, substrate_vectors, lowest):
                match['film_miller'] = film_miller
                match['sub_miller'] = substrate_miller
                if (elasticity_tensor is not None):
                    energy, strain = self.calculate_3D_elastic_energy(
                        film, match, elasticity_tensor, include_strain=True)
                    match["elastic_energy"] = energy
                    match["strain"] = strain
                if (ground_state_energy is not 0):
                    match['total_energy'] = match.get('elastic_energy',
                                                      0) + ground_state_energy

                yield match
Example #3
0
def smart_surf(strt=None, tol=0.1):
    """
    Umbrell function for surface energies with convergence

    Args:
       strt: Structure object
       tol: surface energy convergence tolerance in eV
    Returns:
          surf_list: list of surface energies
          surf_header_list: list of surface names
           
    """
    sg_mat = SpacegroupAnalyzer(strt)
    mat_cvn = sg_mat.get_conventional_standard_structure()
    mat_cvn.sort()
    layers = 2
    indices = get_symmetrically_distinct_miller_indices(mat_cvn, 1)
    ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn)
    for i in indices:
        ase_slab = surface(ase_atoms, i, layers)
        ase_slab.center(vacuum=15, axis=2)
        if len(ase_slab) < 50:
            layers = 3
    surf_arr = []
    surf_done = 0
    surf = surfer(mat=strt, layers=layers)
    surf_list = [100000 for y in range(len(surf) - 1)]
    print("in smart_surf :surf,surf_list=", surf, surf_list)
    while surf_done != 1:
        layers = layers + 1
        indices = get_symmetrically_distinct_miller_indices(mat_cvn, 1)
        ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn)
        for i in indices:
            ase_slab = surface(ase_atoms, i, layers)
            ase_slab.center(vacuum=15, axis=2)
            if len(ase_slab) > 100:
                surf_done = 1
            if (ase_slab.get_cell()[2][2]) > 40:
                surf_done = 1
        surf = surfer(mat=strt, layers=layers)
        if surf not in surf_arr:
            surf_arr.append(surf)
            surf_list2, surf_header_list = surf_energy(surf=surf)
            print("in smart_surf :surf2,surf_list2=", surf_list2,
                  surf_header_list)
            diff = matrix(surf_list) - matrix(surf_list2)
            print("in smart_surf :surf3,surf_list3=", matrix(surf_list),
                  matrix(surf_list2))
            diff_arr = np.array(diff).flatten()
            if any(diff_arr) > tol:
                #for el in diff_arr:
                #    if abs(el)>tol :
                #        print ("in smart_surf :abs el=",abs(el))
                surf_done = 0
                surf_list = surf_list2
            else:
                surf_done = 1
    return surf_list, surf_header_list
Example #4
0
    def generate(self, film, substrate, film_millers=None, substrate_millers=None,
                lowest=False):
        """
        Generates the film/substrate combinations for either set miller
        indicies or all possible miller indices up to a max miller index

        Args:
            film(Structure):  Conventional standard pymatgen structure for
                the film
            substrate(Struture): Conventional standard pymatgen Structure
                for the substrate
            film_millers(array): array of film miller indicies to consider
                in the matching algorithm
            substrate_millers(array): array of substrate miller indicies to
                consider in the matching algorithm
        """

        # Sets film and substrate for search
        self.substrate = substrate
        self.film = film

        # Generate miller indicies if none specified for film
        if film_millers is None:
            film_millers = sorted(get_symmetrically_distinct_miller_indices(
                self.film, self.film_max_miller))

        # Generate miller indicies if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(self.substrate,
                                                          self.substrate_max_miller))

        # Check each miller index combination
        for [film_area, substrate_area, film_vectors, substrate_vectors,
             film_miller, substrate_miller] in self.generate_slabs(film_millers,
                                                                   substrate_millers):
            # Generate all super lattice comnbinations for a given set of miller
            # indicies
            transformations = self.generate_sl_transformations(
                film_area, substrate_area)
            # Check each super-lattice pair to see if they match
            for match in self.check_transformations(transformations,
                                                    film_vectors,
                                                    substrate_vectors):
                # Yield the match area, the miller indicies,
                yield self.match_as_dict(film_miller, substrate_miller, match[0],
                                         match[1], film_vectors, substrate_vectors, vec_area(*match[0]))

                # Just want lowest match per direction
                if (lowest):
                    break
Example #5
0
    def calculate(self, film, substrate, elasticity_tensor=None,
                  film_millers=None, substrate_millers=None,
                  ground_state_energy=0, lowest=False):
        """
        Finds all topological matches for the substrate and calculates elastic
        strain energy and total energy for the film if elasticity tensor and
        ground state energy are provided:

        Args:
            film(Structure): conventional standard structure for the film
            substrate(Structure): conventional standard structure for the
                substrate
            elasticity_tensor(ElasticTensor): elasticity tensor for the film
                in the IEEE orientation
            film_millers(array): film facets to consider in search as defined by
                miller indicies
            substrate_millers(array): substrate facets to consider in search as
                defined by miller indicies
            ground_state_energy(float): ground state energy for the film
            lowest(bool): only consider lowest matching area for each surface
        """
        self.film = film
        self.substrate = substrate

        # Generate miller indicies if none specified for film
        if film_millers is None:
            film_millers = sorted(get_symmetrically_distinct_miller_indices(
                self.film, self.film_max_miller))

        # Generate miller indicies if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(self.substrate,
                                                          self.substrate_max_miller))

        # Check each miller index combination
        surface_vector_sets = self.generate_surface_vectors(film_millers, substrate_millers)
        for [film_vectors, substrate_vectors, film_miller, substrate_miller] in surface_vector_sets:
            for match in self.zsl(film_vectors, substrate_vectors, lowest):
                match['film_miller'] = film_miller
                match['sub_miller'] = substrate_miller
                if (elasticity_tensor is not None):
                    energy, strain = self.calculate_3D_elastic_energy(
                        film, match, elasticity_tensor, include_strain=True)
                    match["elastic_energy"] = energy
                    match["strain"] = strain
                if (ground_state_energy is not 0):
                    match['total_energy'] = match.get('elastic_energy', 0) + ground_state_energy

                yield match
Example #6
0
def test_calculate_unit_slab_height():
    '''
    Test all the Miller indices for one bulk
    '''
    # Find all the Miller indices we can use
    atoms = ase.io.read(TEST_CASE_LOCATION + 'bulks/Cu_FCC.traj')
    structure = AseAtomsAdaptor.get_structure(atoms)
    distinct_millers = get_symmetrically_distinct_miller_indices(structure, 3)

    # These are the hard-coded answers
    expected_heights = [
        6.252703415323648, 6.1572366883500305, 4.969144795636368,
        5.105310960166873, 4.969144795636368, 6.15723668835003,
        6.252703415323648, 6.128875244668134, 4.824065416519261,
        4.824065416519261, 6.128875244668133, 6.157236688350029,
        3.26536786177718, 4.824065416519261, 4.969144795636368,
        3.4247467059623546, 5.006169270932693, 5.105310960166873,
        3.26536786177718, 4.824065416519261, 6.128875244668134
    ]

    # Test our function
    for miller_indices, expected_height in zip(distinct_millers,
                                               expected_heights):
        height = calculate_unit_slab_height(atoms, miller_indices)
        assert height == expected_height
Example #7
0
def surfer(mpid='',
           vacuum=15,
           layers=2,
           mat=None,
           max_index=1,
           write_file=True):
    """
    ASE surface bulder

    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('layers') + str(layers)
    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:
        ase_slab = surface(ase_atoms, i, layers)
        ase_slab.center(vacuum=vacuum, axis=2)
        slab_pymatgen = AseAtomsAdaptor().get_structure(ase_slab)
        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('layers') + str(layers)
        except:
            pass
        if write_file == True:
            pos.write_file(filename=str('POSCAR-') + str("Surf-") +
                           str(surf_name) + str('.vasp'))
        structures.append(pos)

    return structures
Example #8
0
    def test_generate_all_slabs(self):

        slabs = generate_all_slabs(self.cscl, 1, 10, 10)

        # Only three possible slabs, one each in (100), (110) and (111).
        self.assertEqual(len(slabs), 3)

        slabs = generate_all_slabs(self.cscl, 1, 10, 10,
                                   bonds={("Cs", "Cl"): 4})
        # No slabs if we don't allow broken Cs-Cl
        self.assertEqual(len(slabs), 0)

        slabs = generate_all_slabs(self.cscl, 1, 10, 10,
                                   bonds={("Cs", "Cl"): 4},
                                   max_broken_bonds=100)
        self.assertEqual(len(slabs), 3)

        slabs2 = generate_all_slabs(self.lifepo4, 1, 10, 10,
                                    bonds={("P", "O"): 3, ("Fe", "O"): 3})
        self.assertEqual(len(slabs2), 0)

        # There should be only one possible stable surfaces, all of which are
        # in the (001) oriented unit cell
        slabs3 = generate_all_slabs(self.LiCoO2, 1, 10, 10,
                                    bonds={("Co", "O"): 3})
        self.assertEqual(len(slabs3), 1)
        mill = (0, 0, 1)
        for s in slabs3:
            self.assertEqual(s.miller_index, mill)

        slabs1 = generate_all_slabs(self.lifepo4, 1, 10, 10, tol=0.1,
                                    bonds={("P", "O"): 3})
        self.assertEqual(len(slabs1), 4)

        # Now we test this out for repair_broken_bonds()
        slabs1_repair = generate_all_slabs(self.lifepo4, 1, 10, 10, tol=0.1,
                                    bonds={("P", "O"): 3}, repair=True)
        self.assertGreater(len(slabs1_repair), len(slabs1))

        # Lets see if there are no broken PO4 polyhedrons
        miller_list = get_symmetrically_distinct_miller_indices(self.lifepo4, 1)
        all_miller_list = []
        for slab in slabs1_repair:
            hkl = tuple(slab.miller_index)
            if hkl not in all_miller_list:
                all_miller_list.append(hkl)
            broken = []
            for site in slab:
                if site.species_string == "P":
                    neighbors = slab.get_neighbors(site, 3)
                    cn = 0
                    for nn in neighbors:
                        cn += 1 if nn[0].species_string == "O" else 0
                    broken.append(cn != 4)
            self.assertFalse(any(broken))

        # check if we were able to produce at least one
        # termination for each distinct Miller _index
        self.assertEqual(len(miller_list), len(all_miller_list))
Example #9
0
def MillerIndexLibrary(structure):
    MI_lib = {}
    UniqueIndices = get_symmetrically_distinct_miller_indices(structure,
                                                              max_index=4)
    for Index in UniqueIndices:
        MI_lib[str(Index)] = get_sym_fam(
            structure).get_equivalent_miller_indices(Index)
    return MI_lib
Example #10
0
    def test_get_symmetrically_distinct_miller_indices(self):

        # Tests to see if the function obtains the known number of unique slabs

        indices = get_symmetrically_distinct_miller_indices(self.cscl, 1)
        self.assertEqual(len(indices), 3)
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 2)
        self.assertEqual(len(indices), 6)

        self.assertEqual(
            len(get_symmetrically_distinct_miller_indices(self.lifepo4, 1)), 7)

        # The TeI P-1 structure should have 13 unique millers (only inversion
        # symmetry eliminates pairs)
        indices = get_symmetrically_distinct_miller_indices(self.tei, 1)
        self.assertEqual(len(indices), 13)

        # P1 and P-1 should have the same # of miller indices since surfaces
        # always have inversion symmetry.
        indices = get_symmetrically_distinct_miller_indices(self.p1, 1)
        self.assertEqual(len(indices), 13)

        indices = get_symmetrically_distinct_miller_indices(self.graphite, 2)
        self.assertEqual(len(indices), 12)

        # Now try a trigonal system.
        indices = get_symmetrically_distinct_miller_indices(self.trigBi, 2)
        self.assertEqual(len(indices), 17)
Example #11
0
    def test_get_symmetrically_distinct_miller_indices(self):

        # Tests to see if the function obtains the known number of unique slabs

        indices = get_symmetrically_distinct_miller_indices(self.cscl, 1)
        self.assertEqual(len(indices), 3)
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 2)
        self.assertEqual(len(indices), 6)

        self.assertEqual(len(get_symmetrically_distinct_miller_indices(self.lifepo4, 1)), 7)

        # The TeI P-1 structure should have 13 unique millers (only inversion
        # symmetry eliminates pairs)
        indices = get_symmetrically_distinct_miller_indices(self.tei, 1)
        self.assertEqual(len(indices), 13)

        # P1 and P-1 should have the same # of miller indices since surfaces
        # always have inversion symmetry.
        indices = get_symmetrically_distinct_miller_indices(self.p1, 1)
        self.assertEqual(len(indices), 13)

        indices = get_symmetrically_distinct_miller_indices(self.graphite, 2)
        self.assertEqual(len(indices), 12)

        # Now try a trigonal system.
        indices = get_symmetrically_distinct_miller_indices(self.trigBi, 2, return_hkil=True)
        self.assertEqual(len(indices), 17)
        self.assertTrue(all([len(hkl) == 4 for hkl in indices]))
Example #12
0
    def test_get_symmetrically_distinct_miller_indices(self):
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 1)
        self.assertEqual(len(indices), 3)
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 2)
        self.assertEqual(len(indices), 6)

        self.assertEqual(len(get_symmetrically_distinct_miller_indices(
                         self.lifepo4, 1)), 7)

        # The TeI P-1 structure should have 13 unique millers (only inversion
        # symmetry eliminates pairs)
        indices = get_symmetrically_distinct_miller_indices(self.tei, 1)
        self.assertEqual(len(indices), 13)

        # P1 and P-1 should have the same # of miller indices since surfaces
        # always have inversion symmetry.
        indices = get_symmetrically_distinct_miller_indices(self.p1, 1)
        self.assertEqual(len(indices), 13)
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
Example #14
0
    def test_get_symmetrically_distinct_miller_indices(self):
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 1)
        self.assertEqual(len(indices), 3)
        indices = get_symmetrically_distinct_miller_indices(self.cscl, 2)
        self.assertEqual(len(indices), 6)

        self.assertEqual(len(get_symmetrically_distinct_miller_indices(
                         self.lifepo4, 1)), 7)

        # The TeI P-1 structure should have 13 unique millers (only inversion
        # symmetry eliminates pairs)
        indices = get_symmetrically_distinct_miller_indices(self.tei, 1)
        self.assertEqual(len(indices), 13)

        # P1 and P-1 should have the same # of miller indices since surfaces
        # always have inversion symmetry.
        indices = get_symmetrically_distinct_miller_indices(self.p1, 1)
        self.assertEqual(len(indices), 13)
Example #15
0
def pmg_surfer(mpid='', vacuum=15, mat=None, max_index=1, min_slab_size=15):
    if mat == None:
        with MPRester() as mp:
            mat = mp.get_structure_by_material_id(mpid)
        if mpid == '':
            print('Provide structure')

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

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

    return structures
Example #16
0
def surfer(vacuum=15, layers=2, mat=None, max_index=1, write_file=True):
    """
    ASE surface bulder

    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:
        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("layers") + str(layers))
    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:
        ase_slab = surface(ase_atoms, i, layers)
        ase_slab.center(vacuum=vacuum, axis=2)
        slab_pymatgen = AseAtomsAdaptor().get_structure(ase_slab)
        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("layers") + str(layers))
        except:
            pass
        if write_file == True:
            pos.write_file(filename=str("POSCAR-") + str("Surf-") +
                           str(surf_name) + str(".vasp"))
        structures.append(pos)

    return structures
Example #17
0
    def generate(self, film_millers=None, substrate_millers=None):
        """
        Generates the film/substrate combinations for either set miller
        indicies or all possible miller indices up to a max miller index

        Args:
            film_millers(array): array of film miller indicies to consider
                in the matching algorithm
            substrate_millers(array): array of substrate miller indicies to
                consider in the matching algorithm
        """

        # Generate miller indicies if none specified for film
        if film_millers is None:
            film_millers = sorted(get_symmetrically_distinct_miller_indices(
                self.film, self.film_max_miller))

        # Generate miller indicies if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(self.substrate,
                                                          self.substrate_max_miller))

        # Check each miller index combination
        for [film_area, substrate_area, film_vectors, substrate_vectors,
             film_miller, substrate_miller] in self.generate_slabs(film_millers,
                                                                   substrate_millers):
            # Generate all super lattice comnbinations for a given set of miller
            # indicies
            transformations = self.generate_sl_transformations(
                film_area, substrate_area)
            # Check each super-lattice pair to see if they match
            for match in self.check_transformations(transformations,
                                                    film_vectors,
                                                    substrate_vectors):
                # Yield the match area, the miller indicies,
                yield ZSLMatch(film_miller,substrate_miller,match[0],
                    match[1],film_vectors,substrate_vectors,
                    vec_area(*match[0]))
Example #18
0
    def from_max_index(self,
                       max_index,
                       max_normal_search=True,
                       terminations=False,
                       get_bulk_e=True,
                       max_only=False):
        """
            Class method to create a surface workflow with a list of unit cells
            based on the max miller index. Used in combination with list_of_elements

                Args:
                    max_index (int): The maximum miller index to create slabs from
                    max_normal_search (bool): Whether or not to orthogonalize slabs
                        and oriented unit cells along the c direction.
                    terminations (bool): Whether or not to consider the different
                        possible terminations in a slab. If set to false, only one
                        slab is calculated per miller index with the shift value
                        set to 0.
        """

        miller_dict = {}
        for el in self.elements:
            max_miller = []
            # generate_all_slabs() is very slow, especially for Mn
            list_of_indices = \
                get_symmetrically_distinct_miller_indices(self.unit_cells_dict[el][0],
                                                          max_index)

            print 'surface ', el

            print '# ', el

            if max_only:
                for hkl in list_of_indices:
                    if abs(min(hkl)) == max_index or abs(
                            max(hkl)) == max_index:
                        max_miller.append(hkl)
                miller_dict[el] = max_only
            else:
                miller_dict[el] = list_of_indices

        return CreateSurfaceWorkflow(miller_dict,
                                     self.unit_cells_dict,
                                     self.vaspdbinsert_params,
                                     ssize=self.ssize,
                                     vsize=self.vsize,
                                     max_normal_search=max_normal_search,
                                     terminations=terminations,
                                     fail_safe=self.fail_safe,
                                     reset=self.reset,
                                     get_bulk_e=get_bulk_e)
Example #19
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
Example #20
0
    def run(self):
        with open(self.input().path, 'rb') as file_handle:
            bulk_doc = pickle.load(file_handle)

        # Convert the bulk into a `pytmatgen.Structure` and then standardize it
        # for consistency
        bulk_atoms = make_atoms_from_doc(bulk_doc)
        bulk_structure = AseAtomsAdaptor.get_structure(bulk_atoms)
        sga = SpacegroupAnalyzer(bulk_structure, symprec=0.1)
        bulk_struct_standard = sga.get_conventional_standard_structure()

        # Enumerate and save the distinct Miller indices
        distinct_millers = get_symmetrically_distinct_miller_indices(bulk_struct_standard,
                                                                     self.max_miller)
        save_task_output(self, distinct_millers)
Example #21
0
    def from_max_index(self, max_index, max_normal_search=True, terminations=False, get_bulk_e=True, max_only=False):

        """
            Class method to create a surface workflow with a list of unit cells
            based on the max miller index. Used in combination with list_of_elements

                Args:
                    max_index (int): The maximum miller index to create slabs from
                    max_normal_search (bool): Whether or not to orthogonalize slabs
                        and oriented unit cells along the c direction.
                    terminations (bool): Whether or not to consider the different
                        possible terminations in a slab. If set to false, only one
                        slab is calculated per miller index with the shift value
                        set to 0.
        """

        miller_dict = {}
        for el in self.elements:
            max_miller = []
            # generate_all_slabs() is very slow, especially for Mn
            list_of_indices = get_symmetrically_distinct_miller_indices(self.unit_cells_dict[el][0], max_index)

            print "surface ", el

            print "# ", el

            if max_only:
                for hkl in list_of_indices:
                    if abs(min(hkl)) == max_index or abs(max(hkl)) == max_index:
                        max_miller.append(hkl)
                miller_dict[el] = max_only
            else:
                miller_dict[el] = list_of_indices

        return CreateSurfaceWorkflow(
            miller_dict,
            self.unit_cells_dict,
            self.vaspdbinsert_params,
            ssize=self.ssize,
            vsize=self.vsize,
            max_normal_search=max_normal_search,
            terminations=terminations,
            fail_safe=self.fail_safe,
            reset=self.reset,
            get_bulk_e=get_bulk_e,
        )
Example #22
0
def test_calculate_unit_slab_height():
    '''
    Test all the Miller indices for one bulk
    '''
    # Find all the Miller indices we can use
    atoms = ase.io.read(TEST_CASE_LOCATION + 'bulks/Cu_FCC.traj')
    structure = AseAtomsAdaptor.get_structure(atoms)
    distinct_millers = get_symmetrically_distinct_miller_indices(structure, 3)

    # These are the hard-coded answers
    expected_heights = [6.272210880031006, 6.176446311678471, 4.984647756561888,
                        5.121238738402999, 4.984647756561888, 6.176446311678471,
                        6.272210880031005, 6.147996384691849, 4.839115752287323,
                        4.839115752287323, 6.147996384691849, 6.176446311678471,
                        3.275555302966866, 4.839115752287323, 4.984647756561887,
                        3.4354313844223103, 5.021787742477727, 5.121238738402999,
                        3.275555302966866, 4.839115752287322, 6.14799638469185]

    # Test our function
    for miller_indices, expected_height in zip(distinct_millers, expected_heights):
        height = calculate_unit_slab_height(atoms, miller_indices)
        assert height == expected_height
Example #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
Example #24
0
def pmg_surfer(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:
        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)

    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
        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
Example #25
0
    def test_generate_all_slabs(self):

        slabs = generate_all_slabs(self.cscl, 1, 10, 10)
        # Only three possible slabs, one each in (100), (110) and (111).
        self.assertEqual(len(slabs), 3)

        # make sure it generates reconstructions
        slabs = generate_all_slabs(self.Fe, 1, 10, 10,
                                   include_reconstructions=True)

        # Four possible slabs, (100), (110), (111) and the zigzag (100).
        self.assertEqual(len(slabs), 4)

        slabs = generate_all_slabs(self.cscl, 1, 10, 10,
                                   bonds={("Cs", "Cl"): 4})
        # No slabs if we don't allow broken Cs-Cl
        self.assertEqual(len(slabs), 0)

        slabs = generate_all_slabs(self.cscl, 1, 10, 10,
                                   bonds={("Cs", "Cl"): 4},
                                   max_broken_bonds=100)
        self.assertEqual(len(slabs), 3)

        slabs2 = generate_all_slabs(self.lifepo4, 1, 10, 10,
                                    bonds={("P", "O"): 3, ("Fe", "O"): 3})
        self.assertEqual(len(slabs2), 0)

        # There should be only one possible stable surfaces, all of which are
        # in the (001) oriented unit cell
        slabs3 = generate_all_slabs(self.LiCoO2, 1, 10, 10,
                                    bonds={("Co", "O"): 3})
        self.assertEqual(len(slabs3), 1)
        mill = (0, 0, 1)
        for s in slabs3:
            self.assertEqual(s.miller_index, mill)

        slabs1 = generate_all_slabs(self.lifepo4, 1, 10, 10, tol=0.1,
                                    bonds={("P", "O"): 3})
        self.assertEqual(len(slabs1), 4)

        # Now we test this out for repair_broken_bonds()
        slabs1_repair = generate_all_slabs(self.lifepo4, 1, 10, 10, tol=0.1,
                                           bonds={("P", "O"): 3}, repair=True)
        self.assertGreater(len(slabs1_repair), len(slabs1))

        # Lets see if there are no broken PO4 polyhedrons
        miller_list = get_symmetrically_distinct_miller_indices(self.lifepo4, 1)
        all_miller_list = []
        for slab in slabs1_repair:
            hkl = tuple(slab.miller_index)
            if hkl not in all_miller_list:
                all_miller_list.append(hkl)
            broken = []
            for site in slab:
                if site.species_string == "P":
                    neighbors = slab.get_neighbors(site, 3)
                    cn = 0
                    for nn in neighbors:
                        cn += 1 if nn[0].species_string == "O" else 0
                    broken.append(cn != 4)
            self.assertFalse(any(broken))

        # check if we were able to produce at least one
        # termination for each distinct Miller _index
        self.assertEqual(len(miller_list), len(all_miller_list))
Example #26
0
def get_all_slabs(unit_cell,
                  max_miller_ind,
                  slab_thickness,
                  surf_supercell,
                  run_dir,
                  in_unit_planes=False):
    slab_memory = []
    if not in_unit_planes:
        slab_range = range(slab_thickness - 3, slab_thickness + 3)
        layer_units = 'angstroms'
    else:
        slab_range = range(slab_thickness - 1, slab_thickness + 1)
        layer_units = 'layers'
    miller_lst = get_symmetrically_distinct_miller_indices(
        unit_cell, max_miller_ind)
    for miller in miller_lst:
        for n_angstroms in slab_range:
            slabgen = SlabGenerator(unit_cell,
                                    miller,
                                    n_angstroms,
                                    15,
                                    in_unit_planes=in_unit_planes,
                                    lll_reduce=True)

            slabs = slabgen.get_slabs(symmetrize='equivalent_surface')

            slab_lst = []

            for slab in slabs:
                make_term = slab.make_single_species_termination()
                slab_lst.append(slab)
                if type(make_term) == list:
                    slab_lst.extend(make_term)

            slab_idx = 0

            for slab in slab_lst:
                new_slab = slab.copy()
                new_slab = freeze_center(new_slab)
                new_slab.make_supercell(
                    [surf_supercell[0], surf_supercell[1], 1])
                new_slab = new_slab.get_sorted_structure()
                if new_slab in slab_memory:
                    pass
                else:
                    slab_idx += 1
                    if not os.path.exists(
                            '%s\surface_stability\%s%s%s\%s\%s%s' %
                        (run_dir, miller[0], miller[1], miller[2], slab_idx,
                         n_angstroms, layer_units)):
                        os.makedirs('%s\surface_stability\%s%s%s\%s\%s%s' %
                                    (run_dir, miller[0], miller[1], miller[2],
                                     slab_idx, n_angstroms, layer_units))
                    new_slab.to(
                        'poscar',
                        '%s\surface_stability\%s%s%s\%s\%s%s\\POSCAR' %
                        (run_dir, miller[0], miller[1], miller[2], slab_idx,
                         n_angstroms, layer_units))
                    slab_memory.append(new_slab)
    if not os.path.exists('%s/surface_stability/bulk_reference' % run_dir):
        os.makedirs('%s/surface_stability/bulk_reference' % run_dir)
    unit_cell.to("poscar",
                 '%s/surface_stability/bulk_reference/POSCAR' % run_dir)
    return slab_memory
Example #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
Example #28
0
def smart_surf(strt=None, parameters=None, layers=3, tol=0.5):
    """
    Function to get all surface energies

    Args:
        strt: Structure object
        parameters: parameters with LAMMPS inputs
        layers: starting number of layers
        tol: surface energy tolerance for convergence
    Returns:
          surf_list: list of surface energies
          surf_header_list: list of surface names

    """
    parameters["control_file"] = input_nobox  #'/users/knc6/inelast_nobox.mod'
    sg_mat = SpacegroupAnalyzer(strt)
    mat_cvn = sg_mat.get_conventional_standard_structure()
    mat_cvn.sort()
    layers = 3
    indices = get_symmetrically_distinct_miller_indices(mat_cvn, 1)
    ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn)
    for i in indices:
        ase_slab = surface(ase_atoms, i, layers)
        ase_slab.center(vacuum=15, axis=2)
        if len(ase_slab) < 50:
            layers = 3
    surf_arr = []
    surf_done = True
    try:
        surf = surfer(mat=strt, layers=layers)
        surf_list = [100000 for y in range(len(surf) - 1)]
        print("in smart_surf :surf,surf_list=", surf, surf_list)
    except:
        print("Failed at s1", os.getcwd())
        pass
    while surf_done:
        layers = layers + 1
        indices = get_symmetrically_distinct_miller_indices(mat_cvn, 1)
        ase_atoms = AseAtomsAdaptor().get_atoms(mat_cvn)
        for i in indices:
            ase_slab = surface(ase_atoms, i, layers)
            ase_slab.center(vacuum=15, axis=2)
            # if len(ase_slab) > 100:
            #   surf_done=True
            # if (ase_slab.get_cell()[2][2]) > 40:
            #   surf_done=True

        try:
            surf = surfer(mat=strt, layers=layers)
        except:

            print("Failed at s2", os.getcwd())
            pass
        if surf not in surf_arr:
            surf_arr.append(surf)
            try:
                surf_list2, surf_header_list = surf_energy(
                    surf=surf, parameters=parameters
                )
                print("in smart_surf :surf2,surf_list2=", surf_list2, surf_header_list)
                diff = matrix(surf_list) - matrix(surf_list2)
                print(
                    "in smart_surf :surf3,surf_list3=",
                    matrix(surf_list),
                    matrix(surf_list2),
                )
                diff_arr = np.array(diff).flatten()
            except:
                print("Failed for s_3", os.getcwd())
                pass
            if len(ase_slab) > 50:
                surf_done = True
                break
                # print ("layersssssssssssssssssssssssss",layers,surf_done)
                break
            if any(diff_arr) > tol:
                # for el in diff_arr:
                #    if abs(el)>tol :
                #        print ("in smart_surf :abs el=",abs(el))
                surf_done = True
                surf_list = surf_list2
            else:
                surf_done = False
    return surf_list, surf_header_list
Example #29
0
    def calculate(
        self,
        film,
        substrate,
        elasticity_tensor=None,
        film_millers=None,
        substrate_millers=None,
        ground_state_energy=0,
        lowest=False,
    ):
        """
        Finds all topological matches for the substrate and calculates elastic
        strain energy and total energy for the film if elasticity tensor and
        ground state energy are provided:

        Args:
            film(Structure): conventional standard structure for the film
            substrate(Structure): conventional standard structure for the
                substrate
            elasticity_tensor(ElasticTensor): elasticity tensor for the film
                in the IEEE orientation
            film_millers(array): film facets to consider in search as defined by
                miller indices
            substrate_millers(array): substrate facets to consider in search as
                defined by miller indices
            ground_state_energy(float): ground state energy for the film
            lowest(bool): only consider lowest matching area for each surface
        """
        self.film = film
        self.substrate = substrate

        # Generate miller indices if none specified for film
        if film_millers is None:
            film_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.film, self.film_max_miller))

        # Generate miller indices if none specified for substrate
        if substrate_millers is None:
            substrate_millers = sorted(
                get_symmetrically_distinct_miller_indices(
                    self.substrate, self.substrate_max_miller))

        # Check each miller index combination
        surface_vector_sets = self.generate_surface_vectors(
            film_millers, substrate_millers)
        for [
                film_vectors,
                substrate_vectors,
                film_miller,
                substrate_miller,
        ] in surface_vector_sets:
            for match in self(film_vectors, substrate_vectors, lowest):

                sub_match = SubstrateMatch.from_zsl(
                    match=match,
                    film=film,
                    film_miller=film_miller,
                    substrate_miller=substrate_miller,
                    elasticity_tensor=elasticity_tensor,
                    ground_state_energy=ground_state_energy,
                )

                yield sub_match
Example #30
0
def get_all_miller_indices(structure, highestindex):
    """
    wraps the pymatgen function get_symmetrically_distinct_miller_indices for an AiiDa structure
    """
    return get_symmetrically_distinct_miller_indices(
        structure.get_pymatgen_structure(), highestindex)