Ejemplo n.º 1
0
def is_equivalent(structure : Structure, atoms_1 : tuple, atoms_2 : tuple , eps=0.05):
    """

    Find Vacancy Strucutres for diffusion into and out of the specified atom_i site.

    :param structure: Structure
        Structure to calculate diffusion pathways
    :param atom_i: int
        Atom to get diffion path from
    :return: [ Structure ]
    """

    # To Find Pathway, look for voronoi edges
    structure = structure.copy() # type: Structure

    coords = get_midpoint(structure, atoms_1[0], atoms_1[1])
    structure.append('H', coords)
    coords = get_midpoint(structure, atoms_2[0], atoms_2[1])
    structure.append('H', coords)

    dist_1 = structure.get_neighbors(structure[-2], 3)
    dist_2 = structure.get_neighbors(structure[-1], 3)
    dist_1.sort(key=lambda x: x[1])
    dist_2.sort(key=lambda x: x[1])
    for (site_a, site_b) in zip(dist_1, dist_2):
        if abs(site_a[1] - site_b[1]) > eps:
            return False
        elif site_a[0].specie != site_b[0].specie:
            return False
    return True
Ejemplo n.º 2
0
def append_interstitial(supercell_info: SupercellInfo,
                        unitcell_structure: Structure,
                        frac_coords: List[float]) -> SupercellInfo:
    """
    inv_trans_mat must be multiplied with coords from the right as the
    trans_mat is multiplied to the unitcell lattice vector from the left.
    see __mul__ of IStructure in pymatgen.
    x_u, x_s means the frac coordinates in unitcell and supercell,
    while a, b, c are the unitcell lattice vector.
    (a_u, b_u, c_u) . (a, b, c) = (a_s, b_s, c_s) . trans_mat . (a, b, c)
    (a_u, b_u, c_u) = (a_s, b_s, c_s) . trans_mat
    so, (a_s, b_s, c_s) = (a_u, b_u, c_u) . inv_trans_mat
    """
    if supercell_info.unitcell_structure and \
            supercell_info.unitcell_structure != unitcell_structure:
        raise NotPrimitiveError

    unitcell_structure.append(species=Element.H, coords=frac_coords)
    symmetrizer = StructureSymmetrizer(unitcell_structure)
    wyckoff_letter = (symmetrizer.spglib_sym_data["wyckoffs"][-1])
    site_symmetry = (symmetrizer.spglib_sym_data["site_symmetry_symbols"][-1])

    inv_matrix = inv(np.array(supercell_info.transformation_matrix))
    new_coords = np.dot(frac_coords, inv_matrix).tolist()
    supercell_info.interstitials.append(
        Interstitial(frac_coords=new_coords,
                     wyckoff_letter=wyckoff_letter,
                     site_symmetry=site_symmetry))
    return supercell_info
Ejemplo n.º 3
0
    def output_clusters(self, fmt, periodic=None):
        """
        Outputs the unique unit cell clusters from the graph

        Args:
        fmt (str): output format for pymatgen structures set up from clusters
        periodic (Boolean): Whether to output only periodic clusters

        Outputs:
        CLUS_*."fmt": A cluster structure file for each cluster in the graph
        """

        if fmt == "poscar":
            tail = "vasp"
        else:
            tail = fmt

        site_sets = []

        for cluster in self.clusters:
            if periodic:
                if cluster.periodic > 0:
                    site_sets.append(
                        frozenset([
                            int(node.labels["UC_index"])
                            for node in cluster.nodes
                        ]))
            else:
                site_sets.append(
                    frozenset([
                        int(node.labels["UC_index"]) for node in cluster.nodes
                    ]))

        site_sets = set(site_sets)

        for index, site_list in enumerate(site_sets):
            cluster_structure = Structure(lattice=self.structure.lattice,
                                          species=[],
                                          coords=[])
            symbols = [species for species in self.structure.symbol_set]
            if "X" in set(symbols):
                symbols.remove("X")
                symbols.append("X0+")
            for symbol in symbols:
                for site in site_list:

                    site = self.structure.sites[site]

                    if site.species_string == symbol:
                        cluster_structure.append(symbol,
                                                 site.coords,
                                                 coords_are_cartesian=True)

            cluster_structure.to(fmt=fmt,
                                 filename="CLUS_" + str(index) + "." + tail)
Ejemplo n.º 4
0
def base_structure(lat, dict_str):
    st = Structure(lat, [], [])
    if dict_str[0] == {}:
        return st
    for tc in dict_str:
        sp = tc["type"]
        coords = read_coords(tc["coords"])
        if len(coords.shape) == 1:
            coords = np.reshape(coords, (1, 3))
        n = coords.shape[0]
        for i in range(n):
            st.append(sp, coords[i, :])
    return st
Ejemplo n.º 5
0
    def return_periodic_structure(self, fmt):
        """
        Gathers all periodic clusters in the graph as a single pymatgen Structure

        Args:
        fmt (str): output format for pymatgen structure set up from cluster

        Returns:
        #CLUS_PER."fmt": A structure file containing periodic sites in the graph.
        cluster_structure (pymatgen Structure): Structure object containing all periodic clusters

        """

        sites = []

        for cluster in self.clusters:
            if cluster.periodic > 0:
                sites.append(
                    frozenset([
                        int(node.labels["UC_index"]) for node in cluster.nodes
                    ]))

        sites = set(sites)
        cluster_structure = Structure(lattice=self.structure.lattice,
                                      species=[],
                                      coords=[])

        for index, site_list in enumerate(sites):
            symbols = [species for species in self.structure.symbol_set]
            if "X" in set(symbols):
                symbols.remove("X")
                symbols.append("X0+")
            for symbol in symbols:
                for site in site_list:

                    site = self.structure.sites[site]

                    if site.species_string == symbol:
                        cluster_structure.append(symbol,
                                                 site.coords,
                                                 coords_are_cartesian=True)

        return cluster_structure
Ejemplo n.º 6
0
def sort_structure(structure, order):
    """ 
    Given a pymatgen structure object sort the species so that their indices
    sit side by side in the structure, in given order - allows for POSCAR file to 
    be written in a readable way after doping

    Args:
       - structure (Structure): pymatgen structure object
       - order ([str,str..]): list of species str in order to sort

    Returns:
       - structure (Structure): ordered pymatgen Structure object
    """

    symbols = [species for species in structure.symbol_set]

    if "X" in set(symbols):
        symbols.remove("X")
        symbols.append("X0+")

    if set(symbols) == set(order):
        structure_sorted = Structure(lattice=structure.lattice,
                                     species=[],
                                     coords=[])
        for symbol in symbols:
            for i, site in enumerate(structure.sites):
                if site.species_string == symbol:
                    structure_sorted.append(
                        symbol,
                        site.coords,
                        coords_are_cartesian=True,
                        properties=site.properties,
                    )
    else:
        error_msg = "Error: sort structure elements in list passed in order does not match that found in POSCAR\n"
        error_msg += "Passed: {}\n".format(order)
        error_msg += "POSCAR: {}\n".format(symbols)
        raise ValueError(error_msg)
    return structure_sorted
Ejemplo n.º 7
0
def add_hydrogen(
    structure: Structure,
    cutoff: float,
    dist: float,
):
    all_bonds = calculate_bond_list(structure=structure, cutoff=cutoff)

    lattice = np.copy(structure.lattice.matrix)
    coords = np.copy(structure.cart_coords)

    for bonds in all_bonds:
        # only add hydrogen to atoms with two neighbors
        if len(bonds) != 2:
            continue

        bond_pos = bonds_to_positions(bonds, lattice, coords)
        a, b = bond_pos[0]
        _, c = bond_pos[1]
        structure.append(
            species=Element.H,
            coords=calc_hydrogen_loc(a, b - a, c - a, dist),
            coords_are_cartesian=True,
        )
Ejemplo n.º 8
0
    def adsorb_both_surfaces(self, molecule, repeat=None, min_lw=5.0,
                             reorient=True, find_args={}, ltol=0.1,
                             stol=0.1, angle_tol=0.01):

        """
        Function that generates all adsorption structures for a given
        molecular adsorbate on both surfaces of a slab.

        Args:
            molecule (Molecule): molecule corresponding to adsorbate
            repeat (3-tuple or list): repeat argument for supercell generation
            min_lw (float): minimum length and width of the slab, only used
                if repeat is None
            reorient (bool): flag on whether or not to reorient adsorbate
                along the miller index
            find_args (dict): dictionary of arguments to be passed to the
                call to self.find_adsorption_sites, e.g. {"distance":2.0}
            ltol (float): Fractional length tolerance. Default is 0.2.
            stol (float): Site tolerance. Defined as the fraction of the
                average free length per atom := ( V / Nsites ) ** (1/3)
                Default is 0.3.
            angle_tol (float): Angle tolerance in degrees. Default is 5 degrees.
        """

        # First get all possible adsorption configurations for this surface
        adslabs = self.generate_adsorption_structures(molecule, repeat=repeat, min_lw=min_lw,
                                                      reorient=reorient, find_args=find_args)

        # Now we need to sort the sites by their position along
        # c as well as whether or not they are adsorbate
        single_ads = []
        for i, slab in enumerate(adslabs):
            sorted_sites = sorted(slab, key=lambda site: site.frac_coords[2])
            ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                           if site.surface_properties == "adsorbate"]
            non_ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                               if site.surface_properties != "adsorbate"]

            species, fcoords, props = [], [], {"surface_properties": []}
            for site in sorted_sites:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)

            slab_other_side = Structure(slab.lattice, species,
                                        fcoords, site_properties=props)

            # For each adsorbate, get its distance from the surface and move it
            # to the other side with the same distance from the other surface
            for ads_index in ads_indices:
                props["surface_properties"].append("adsorbate")
                adsite = sorted_sites[ads_index]
                diff = abs(adsite.frac_coords[2] - \
                           sorted_sites[non_ads_indices[-1]].frac_coords[2])
                slab_other_side.append(adsite.specie, [adsite.frac_coords[0],
                                                       adsite.frac_coords[1],
                                                       sorted_sites[0].frac_coords[2] - diff],
                                       properties={"surface_properties": "adsorbate"})
                # slab_other_side[-1].add

            # Remove the adsorbates on the original side of the slab
            # to create a slab with one adsorbate on the other side
            slab_other_side.remove_sites(ads_indices)

            # Put both slabs with adsorption on one side
            # and the other in a list of slabs for grouping
            single_ads.extend([slab, slab_other_side])

        # Now group the slabs.
        matcher = StructureMatcher(ltol=ltol, stol=stol,
                                   angle_tol=angle_tol)
        groups = matcher.group_structures(single_ads)

        # Each group should be a pair with adsorbate on one side and the other.
        # If a slab has no equivalent adsorbed slab on the other side, skip.
        adsorb_both_sides = []
        for i, group in enumerate(groups):
            if len(group) != 2:
                continue
            ads1 = [site for site in group[0] if \
                    site.surface_properties == "adsorbate"][0]
            group[1].append(ads1.specie, ads1.frac_coords,
                            properties={"surface_properties": "adsorbate"})
            # Build the slab object
            species, fcoords, props = [], [], {"surface_properties": []}
            for site in group[1]:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)
            s = Structure(group[1].lattice, species, fcoords, site_properties=props)
            adsorb_both_sides.append(s)

        return adsorb_both_sides
Ejemplo n.º 9
0
class MofStructure(Structure):
    """Extend the pymatgen Structure class to add MOF specific features"""
    def __init__(self,
                 lattice,
                 species,
                 coords,
                 charge=None,
                 validate_proximity=False,
                 to_unit_cell=False,
                 coords_are_cartesian=False,
                 site_properties=None,
                 name="N/A"):
        """Create a MOf structure. The arguments are the same as in the
        pymatgen Structure class with the addition of the name argument.
        The super constructor is called and additional MOF specific properties
        are initialized.

        :param name: MOF name, used to identify the structure.
        """
        super().__init__(lattice,
                         species,
                         coords,
                         charge=charge,
                         validate_proximity=validate_proximity,
                         to_unit_cell=to_unit_cell,
                         coords_are_cartesian=coords_are_cartesian,
                         site_properties=site_properties)

        self._all_coord_spheres_indices = None
        self._all_distances = None
        self._metal_coord_spheres = []
        self._name = name
        self.metal = None
        self.metal_indices = []
        self.organic = None
        self.species_str = [str(s) for s in self.species]

        metal_set = set([s for s in self.species_str if Atom(s).is_metal])
        non_metal_set = set(
            [s for s in self.species_str if not Atom(s).is_metal])
        todays_date = datetime.datetime.now().isoformat()
        self.summary = {
            'cif_okay': 'N/A',
            'problematic': 'N/A',
            'has_oms': 'N/A',
            'metal_sites': [],
            'oms_density': 'N/A',
            'checksum': 'N/A',
            'metal_species': list(metal_set),
            'non_metal_species': list(non_metal_set),
            'name': name,
            'uc_volume': self.volume,
            'density': self.density,
            'date_created': str(todays_date)
        }

        self._tolerance = None
        self._split_structure_to_organic_and_metal()

    @classmethod
    def from_file(cls, filename, primitive=False, sort=False, merge_tol=0.0):
        """Create a MofStructure from a CIF file.

        This makes use of the from_file function of the Structure class and
        catches the exception in case a CIF file cannot be read.
        If the CIF is read successfully then the MofStructure is marked as okay,
        and the file checksum is added to the summary. If the CIF file cannot be
        read then it is marked as not okay and all the other properties are
        set to None and because there cannot be an empty Structure a carbon atom
        is added as placeholder at 0,0,0.

        :param filename: (str) The filename to read from.
        :param primitive: (bool) Whether to convert to a primitive cell
        Only available for cifs. Defaults to False.
        :param sort: (bool) Whether to sort sites. Default to False.
        :param merge_tol: (float) If this is some positive number, sites that
        are within merge_tol from each other will be merged. Usually 0.01
        should be enough to deal with common numerical issues.
        :return: Return the created MofStructure
        """
        mof_name = os.path.splitext(os.path.basename(filename))[0]
        try:
            s = Structure.from_file(filename,
                                    primitive=primitive,
                                    sort=sort,
                                    merge_tol=merge_tol)
            s_mof = cls(s.lattice, s.species, s.frac_coords, name=mof_name)
            s_mof.summary['cif_okay'] = True
            s_mof.summary['checksum'] = Helper.get_checksum(filename)
        except Exception as e:
            print('\nAn Exception occurred: {}'.format(e))
            print('Cannot load {}\n'.format(filename))
            # Make a placeholder MOF object, set all its summary entries
            # to None and set cif_okay to False
            s_mof = cls([[10, 0, 0], [0, 10, 0], [0, 0, 10]], ["C"],
                        [[0, 0, 0]],
                        name=mof_name)
            s_mof._mark_failed_to_read()
            s_mof.summary['cif_okay'] = False

        return s_mof

    def analyze_metals(self, output_folder, verbose='normal'):
        """Run analysis to detect all open metal sites in a MofStructure. In
        addition the metal sites are marked as unique.

        :param output_folder: Folder where OMS analysis results will be stored.
        :param verbose: Verbosity level for the output of the analysis.
        """

        Helper.make_folder(output_folder)
        running_indicator = output_folder + "/analysis_running"
        open(running_indicator, 'w').close()

        self.summary['problematic'] = False

        ms_cs_list = {True: [], False: []}
        for m, omc in enumerate(self.metal_coord_spheres):
            m_index = self.metal_indices[m]
            omc.check_if_open()
            if not self.summary['problematic']:
                self.summary['problematic'] = omc.is_problematic

            cs = self._find_coordination_sequence(m_index)
            cs = [self.species_str[m_index]] + cs
            omc.is_unique = self._check_if_new_site(ms_cs_list[omc.is_open],
                                                    cs)
            if omc.is_unique:
                ms_cs_list[omc.is_open].append(cs)

            self.summary['metal_sites'].append(omc.metal_summary)

        unique_sites = [s['unique'] for s in self.summary['metal_sites']]
        open_sites = [s['is_open'] for s in self.summary['metal_sites']]

        self.summary['oms_density'] = sum(unique_sites) / self.volume
        self.summary['has_oms'] = any(open_sites)

        self.write_results(output_folder, verbose)
        os.remove(running_indicator)

    def write_results(self, output_folder, verbose='normal'):
        """Store summary dictionary holding all MOF and OMS information to a
        JSON file, store CIF files for the metal and non-metal parts of the MOF
        as well as all the identified coordination spheres.

        :param output_folder: Location to be used to store
        :param verbose: Verbosity level (default: 'normal')
        """
        Helper.make_folder(output_folder)
        for index, mcs in enumerate(self.metal_coord_spheres):
            mcs.write_cif_file(output_folder, index)
        if self.metal:
            output_fname = "{}/{}_metal.cif".format(output_folder,
                                                    self.summary['name'])
            self.metal.to(filename=output_fname)
        output_fname = "{}/{}_organic.cif".format(output_folder,
                                                  self.summary['name'])
        self.organic.to(filename=output_fname)

        json_file_out = "{}/{}.json".format(output_folder,
                                            self.summary['name'])
        summary = copy.deepcopy(self.summary)
        if verbose == 'normal':
            for ms in summary["metal_sites"]:
                ms.pop('all_dihedrals', None)
                ms.pop('min_dihedral', None)
        with open(json_file_out, 'w') as outfile:
            json.dump(summary, outfile, indent=3)

    @property
    def tolerance(self):
        """Tolerance values for dihedral checks. If not set, defaults are given.
        """
        if self._tolerance is None:
            self._tolerance = {'on_plane': 15}
        return self._tolerance

    @property
    def name(self):
        """Name of the MofStructure."""
        return self._name

    @name.setter
    def name(self, name):
        """Setter for the name of the MofStructure."""
        self._name = name
        self.summary['name'] = name

    @property
    def all_distances(self):
        """Distances between all atoms in the MofStructure"""
        if self._all_distances is None:
            self._all_distances = self.lattice.get_all_distances(
                self.frac_coords, self.frac_coords)
        return self._all_distances

    @property
    def all_coord_spheres_indices(self):
        """Compute the indices of the atoms in the first coordination shell
        for all atoms in the MofStructure
        """
        if self._all_coord_spheres_indices:
            return self._all_coord_spheres_indices

        self._all_coord_spheres_indices = [
            self._find_cs_indices(i) for i in range(len(self))
        ]
        return self._all_coord_spheres_indices

    @property
    def metal_coord_spheres(self):
        """For all metal atoms in a MofStructure compute the first coordination
        sphere as a MetalSite object.
        """
        if not self._metal_coord_spheres:
            self._metal_coord_spheres = [
                self._find_metal_coord_sphere(c) for c in self.metal_indices
            ]
        return self._metal_coord_spheres

    def _mark_failed_to_read(self):
        """If a CIF cannot be read set certain properties to None"""
        self.summary['metal_species'] = None
        self.summary['non_metal_species'] = None
        self.summary['uc_volume'] = None
        self.summary['density'] = None

    def _split_structure_to_organic_and_metal(self):
        """Split a MOF to two pymatgen Structures, one containing only metal
         atoms and one containing only non-metal atoms."""
        self.metal = Structure(self.lattice, [], [])
        self.organic = Structure(self.lattice, [], [])
        i = 0
        for s, fc in zip(self.species, self.frac_coords):
            if Atom(str(s)).is_metal:
                self.metal.append(s, fc)
                self.metal_indices.append(i)
            else:
                self.organic.append(s, fc)
            i += 1

    def _find_cs_indices(self, center):
        """Find the indices of the atoms in the coordination sphere.

        :param center: Central atom of coordination sphere.
        :return: c_sphere_indices: Return in the coordination sphere of center.
        """
        dist = list(self.all_distances[center])
        if dist[center] > 0.0000001:
            sys.exit('The self distance appears to be non-zero')

        a = Atom(self.species_str[center])
        c_sphere_indices = [
            i for i, dis in enumerate(dist)
            if i != center and a.check_bond(self.species_str[i], dis)
        ]
        c_sphere_indices.insert(0, center)
        return c_sphere_indices

    def _find_metal_coord_sphere(self, center):
        """Identify the atoms in the first coordination sphere of a metal atom.

        Obtain all atoms connecting to the metal using the
        all_coord_spheres_indices values and keeping only valid bonds as well as
        center the atoms around the metal center for visualization purposes.

        :param center:
        :return:
        """
        dist = self.all_distances[center]
        if dist[center] > 0.0000001:
            sys.exit('The self distance appears to be non-zero')

        c_sphere = MetalSite(self.lattice, [self.species[center]],
                             [self.frac_coords[center]],
                             tolerance=self.tolerance)

        cs_i = self.all_coord_spheres_indices[center]
        for i in cs_i[1:]:
            c_sphere.append(self.species_str[i], self.frac_coords[i])
        c_sphere.keep_valid_bonds()
        c_sphere.center_around_metal()
        return c_sphere

    @staticmethod
    def _check_if_new_site(cs_list, cs):
        """Check if a given site is unique based on its coordination sequence"""
        for cs_i in cs_list:
            if Helper.compare_lists(cs_i, cs):
                return False
        return True  # len(cs_list),

    def _find_coordination_sequence(self, center):
        """Compute the coordination sequence up to the 6th coordination shell.

        :param center: Atom to compute coordination sequence for
        :return cs: Coordination sequence for center
        """

        shell_list = {(center, (0, 0, 0))}
        shell_list_prev = set([])
        all_shells = set(shell_list)
        n_shells = 6
        cs = []
        count_total = 0
        for n in range(0, n_shells):
            c_set = set([])
            for a_uc in shell_list:
                a = a_uc[0]
                lattice = a_uc[1]
                coord_sphere = self.all_coord_spheres_indices[a]
                count_total += 1
                coord_sphere_with_uc = []
                for c in coord_sphere:
                    diff = self.frac_coords[a] - self.frac_coords[c]
                    new_lat_i = [round(d, 0) for d in diff]
                    uc = tuple(l - nl for l, nl in zip(lattice, new_lat_i))
                    coord_sphere_with_uc.append((c, uc))
                coord_sphere_with_uc = tuple(coord_sphere_with_uc)
                c_set = c_set.union(set(coord_sphere_with_uc))
            for a in shell_list_prev:
                c_set.discard(a)
            for a in shell_list:
                c_set.discard(a)

            cs.append(len(c_set))
            all_shells = all_shells.union(c_set)
            shell_list_prev = shell_list
            shell_list = c_set

        return cs
Ejemplo n.º 10
0
Archivo: fss.py Proyecto: uw-cmg/MAST
def finite_size_scale(standard, ssize, primordial, fsize, psize=[1,1,1]):
    """Function to perform finite size scaling for defect structure relaxation
    Inputs:
        standard = POSCAR file of structure containing defect
        ssize = Supercell size of structure with defect (list of size 3)
        primordial = POSCAR file of structure for basic unit of perfect cell
        psize = Supercell size of structure for padding. Default = [1,1,1] (list of size 3)
        fsize = Desired supercell size of final structure (list of size 3)
    Outputs:
        POSCAR file of structure containing defect and padding"""
    
    # Check if the input sizes work out with the desired final size
    padding = [0,0,0]
    for i in range(3):
        diff = fsize[i] - ssize[i]
        if diff < 0:
            raise RuntimeError('Desired final size of the structure must be larger than \
existing defect structure size. Defect Size = '+repr(ssize)+' Final Size = '+repr(fsize))
        elif diff >= 0:
             if math.fmod(diff,psize[i]):
                raise RuntimeError('Primordial structure and defect structure sizes cannot \
be used to form desired final size.  Reduce size of primordial structure. Defect Size = '+
    repr(ssize)+' Final Size = '+repr(fsize)+' Primordial size = '+repr(psize))
             else:
                padding[i] = diff/psize[i]
    
    # Load the defect structure and primordial structure
    try:
        defst = read_structure(standard)
    except:
        raise RuntimeError('Error: Unable to read standard structure.  Please check file. Filename: '+\
            standard)
    try:
        pst = read_structure(primordial)
    except:
        raise RuntimeError('Error: Unable to read primordial structure.  Please check file. Filename: '+\
            primordial)
    
    # Pad the structure
    positions = [site.coords for site in pst]
    syms = [str(site.specie.symbol) for site in pst]
    lv = [one/ssize for one in defst.lattice.matrix]
    vect = []
    for m0 in range(padding[0]):
        for m1 in numpy.arange(0,fsize[1],psize[1]):
            for m2 in numpy.arange(0,fsize[2],psize[2]):
                vect.append([ssize[0]+m0*psize[0],m1,m2])
    
    for m1 in range(padding[1]):
        for m0 in numpy.arange(0,ssize[0],psize[0]):
            for m2 in numpy.arange(0,fsize[2],psize[2]):
                vect.append([m0,ssize[1]+m1*psize[1],m2])
    
    for m2 in range(padding[2]):
        for m0 in numpy.arange(0,ssize[0],psize[0]):
            for m1 in numpy.arange(0,ssize[1],psize[1]):
                vect.append([m0,m1,ssize[2]+m2*psize[2]])
    
    #Construct a new structure with desired size
    new_lat = Lattice(numpy.array([fsize[c] * lv[c] for c in range(3)]))
    final = Structure(new_lat, defst.species_and_occu,defst.cart_coords,
            coords_are_cartesian=True)
    for m0,m1,m2 in vect:
        npos = positions + numpy.dot((m0, m1, m2), lv)
        for i in range(len(npos)):
            final.append(syms[i],npos[i],coords_are_cartesian=True)
    
    #Check for periodic issues in final structure
    final = check_periodic(final,defst)
    
    # Write output as POSCAR
    write_structure(final, 'POSCAR_Final')
    
    return final
Ejemplo n.º 11
0
def finite_size_scale(standard, ssize, primordial, fsize, psize=[1, 1, 1]):
    """Function to perform finite size scaling for defect structure relaxation
    Inputs:
        standard = POSCAR file of structure containing defect
        ssize = Supercell size of structure with defect (list of size 3)
        primordial = POSCAR file of structure for basic unit of perfect cell
        psize = Supercell size of structure for padding. Default = [1,1,1] (list of size 3)
        fsize = Desired supercell size of final structure (list of size 3)
    Outputs:
        POSCAR file of structure containing defect and padding"""

    # Check if the input sizes work out with the desired final size
    padding = [0, 0, 0]
    for i in range(3):
        diff = fsize[i] - ssize[i]
        if diff < 0:
            raise RuntimeError(
                'Desired final size of the structure must be larger than \
existing defect structure size. Defect Size = ' + repr(ssize) +
                ' Final Size = ' + repr(fsize))
        elif diff >= 0:
            if math.fmod(diff, psize[i]):
                raise RuntimeError(
                    'Primordial structure and defect structure sizes cannot \
be used to form desired final size.  Reduce size of primordial structure. Defect Size = '
                    + repr(ssize) + ' Final Size = ' + repr(fsize) +
                    ' Primordial size = ' + repr(psize))
            else:
                padding[i] = diff / psize[i]

    # Load the defect structure and primordial structure
    try:
        defst = read_structure(standard)
    except:
        raise RuntimeError('Error: Unable to read standard structure.  Please check file. Filename: '+\
            standard)
    try:
        pst = read_structure(primordial)
    except:
        raise RuntimeError('Error: Unable to read primordial structure.  Please check file. Filename: '+\
            primordial)

    # Pad the structure
    positions = [site.coords for site in pst]
    syms = [str(site.specie.symbol) for site in pst]
    lv = [one / ssize for one in defst.lattice.matrix]
    vect = []
    for m0 in range(padding[0]):
        for m1 in numpy.arange(0, fsize[1], psize[1]):
            for m2 in numpy.arange(0, fsize[2], psize[2]):
                vect.append([ssize[0] + m0 * psize[0], m1, m2])

    for m1 in range(padding[1]):
        for m0 in numpy.arange(0, ssize[0], psize[0]):
            for m2 in numpy.arange(0, fsize[2], psize[2]):
                vect.append([m0, ssize[1] + m1 * psize[1], m2])

    for m2 in range(padding[2]):
        for m0 in numpy.arange(0, ssize[0], psize[0]):
            for m1 in numpy.arange(0, ssize[1], psize[1]):
                vect.append([m0, m1, ssize[2] + m2 * psize[2]])

    #Construct a new structure with desired size
    new_lat = Lattice(numpy.array([fsize[c] * lv[c] for c in range(3)]))
    final = Structure(new_lat,
                      defst.species_and_occu,
                      defst.cart_coords,
                      coords_are_cartesian=True)
    for m0, m1, m2 in vect:
        npos = positions + numpy.dot((m0, m1, m2), lv)
        for i in range(len(npos)):
            final.append(syms[i], npos[i], coords_are_cartesian=True)

    #Check for periodic issues in final structure
    final = check_periodic(final, defst)

    # Write output as POSCAR
    write_structure(final, 'POSCAR_Final')

    return final
Ejemplo n.º 12
0
class Crossover:
    '''
    crossover

    # ---------- args
    atype (list): atom type, e.g. ['Si', 'O'] for Si4O8

    nat (list): number of atom, e.g. [4, 8] for Si4O8

    mindist (2d list): constraint on minimum interatomic distance,
                       mindist must be a symmetric matrix
        e.g. [[1.8, 1.2], [1.2, 1.5]
            Si - Si: 1.8 angstrom
            Si - O: 1.2
            O - O: 1.5

    crs_lat ('equal' or 'random') how to mix lattice vectors

    crs_func ('OP' or 'TP'): one point or two point crossover

    nat_diff_tole (int): tolerance for difference in number of atoms
                         in crossover

    maxcnt_ea (int): maximum number of trial in crossover

    # ---------- instance methods
    self.gen_child(struc_A, struc_B)
        if success, return self.child
        if fail, return None
    '''
    def __init__(self,
                 atype,
                 nat,
                 mindist,
                 crs_lat='equal',
                 crs_func='OP',
                 nat_diff_tole=4,
                 maxcnt_ea=100):
        # ---------- check args
        # ------ atype, nat, mindist
        for x in [atype, nat, mindist]:
            if type(x) is not list:
                raise ValueError('atype, nat, and mindist must be list')
        if not len(atype) == len(nat) == len(mindist):
            raise ValueError('not len(atype) == len(nat) == len(mindist)')
        # -- check symmetric
        for i in range(len(mindist)):
            for j in range(i):
                if not mindist[i][j] == mindist[j][i]:
                    raise ValueError(
                        'mindist is not symmetric. ' +
                        '({}, {}): {}, ({}, {}): {}'.format(
                            i, j, mindist[i][j], j, i, mindist[j][i]))
        self.atype = atype
        self.nat = nat
        self.mindist = mindist
        # ------ crs_lat
        if crs_lat == 'equal':
            self.w_lat = np.array([1.0, 1.0])
        elif crs_lat == 'random':
            self.w_lat = np.random.choice([0.0, 1.0], size=2, replace=False)
        else:
            raise ValueError('crs_lat must be equal or random')
        # ------ crs_func
        if crs_func not in ['OP', 'TP']:
            raise ValueError('crs_func must be OP or TP')
        else:
            self.crs_func = crs_func
        # ------ nat_diff_tole, maxcnt_ea
        for x in [nat_diff_tole, maxcnt_ea]:
            if type(x) is int and x > 0:
                pass
            else:
                raise ValueError('nat_diff_tole and maxcnt_ea'
                                 ' must be positive int')
        self.nat_diff_tole = nat_diff_tole
        self.maxcnt_ea = maxcnt_ea

    def gen_child(self, struc_A, struc_B):
        '''
        generate child struture

        # ---------- return
        (if success) self.child:
        (if fail) None:
        '''
        # ---------- initialize
        self.parent_A = origin_shift(struc_A)
        self.parent_B = origin_shift(struc_B)
        count = 0
        # ---------- lattice crossover
        self._lattice_crossover()
        # ---------- generate children
        while True:
            count += 1
            # ------ coordinate crossover
            if self.crs_func == 'OP':
                self._one_point_crossover()
            elif self.crs_func == 'TP':
                self._two_point_crossover()
            self.child = Structure(lattice=self.lattice,
                                   species=self.species,
                                   coords=self.coords)
            # ------ check nat_diff
            self._check_nat()  # get self._nat_diff
            if any([abs(n) > self.nat_diff_tole for n in self._nat_diff]):
                if count > self.maxcnt_ea:  # fail
                    self.child = None
                    return self.child
                continue  # slice again
            # ------ check mindist
            dist_list = check_distance(self.child,
                                       self.atype,
                                       self.mindist,
                                       check_all=True)
            # ------ something smaller than mindist
            if dist_list:
                # -- remove atoms within mindist
                if any([n > 0 for n in self._nat_diff]):
                    self._remove_within_mindist()
                    if self.child is None:  # fail --> slice again
                        if count > self.maxcnt_ea:
                            return None
                        continue
                else:  # nothing to remove, nat_diff = [0, 0]
                    if count > self.maxcnt_ea:
                        return None
                    continue  # fail --> slice again
            # ------ recheck nat_diff
            self._check_nat()
            # ------ nothing smaller than mindist
            # -- remove atoms near the border line
            if any([n > 0 for n in self._nat_diff]):
                self._remove_border_line()
            # -- add atoms near border line
            if any([n < 0 for n in self._nat_diff]):
                self._add_border_line()
            # -- success --> break while loop
            if self.child is not None:
                break
            # -- fail --> slice again
            else:
                if count > self.maxcnt_ea:
                    return None
                continue
        # ---------- final check for nat
        self._check_nat()
        if not all([n == 0 for n in self._nat_diff]):
            raise ValueError('There is a bug: final check for nat')
        # ---------- sort by atype
        self.child = sort_by_atype(self.child, self.atype)
        # ---------- return
        return self.child

    def _lattice_crossover(self):
        # ---------- component --> self.w_lat
        matrix = ((self.w_lat[0] * self.parent_A.lattice.matrix +
                   self.w_lat[1] * self.parent_B.lattice.matrix) /
                  self.w_lat.sum())
        mat_len = np.sqrt((matrix**2).sum(axis=1))
        # ---------- absolute value of vector
        lat_len = ((np.array(self.parent_A.lattice.abc) * self.w_lat[0] +
                    np.array(self.parent_B.lattice.abc) * self.w_lat[1]) /
                   self.w_lat.sum())
        # ---------- correction of vector length
        lat_array = np.empty([3, 3])
        for i in range(3):
            lat_array[i] = matrix[i] * lat_len[i] / mat_len[i]
        # ---------- Lattice for pymatgen
        self.lattice = Lattice(lat_array)

    def _one_point_crossover(self):
        # ---------- slice point
        while True:
            self._slice_point = np.random.normal(loc=0.5, scale=0.1)
            if 0.3 <= self._slice_point <= 0.7:
                break
        self._axis = np.random.choice([0, 1, 2])
        # ---------- crossover
        species_A = []
        species_B = []
        coords_A = []
        coords_B = []
        for i in range(self.parent_A.num_sites):
            if self.parent_A.frac_coords[i, self._axis] <= self._slice_point:
                species_A.append(self.parent_A[i].species_string)
                coords_A.append(self.parent_A[i].frac_coords)
            else:
                species_B.append(self.parent_A[i].species_string)
                coords_B.append(self.parent_A[i].frac_coords)
            if self.parent_B.frac_coords[i, self._axis] >= self._slice_point:
                species_A.append(self.parent_B[i].species_string)
                coords_A.append(self.parent_B[i].frac_coords)
            else:
                species_B.append(self.parent_B[i].species_string)
                coords_B.append(self.parent_B[i].frac_coords)
        # ---------- adopt a structure with more atoms
        if len(species_A) > len(species_B):
            species = species_A
            coords = coords_A
        elif len(species_A) < len(species_B):
            species = species_B
            coords = coords_B
        else:
            if np.random.choice([0, 1]):
                species = species_A
                coords = coords_A
            else:
                species = species_B
                coords = coords_B
        # ---------- set instance variables
        self.species, self.coords = species, coords

    def _two_point_crossover(self):
        # ---------- slice point
        while True:
            self._slice_point = np.random.normal(loc=0.25, scale=0.1)
            if 0.1 <= self._slice_point <= 0.4:
                break
        sp0 = self._slice_point
        sp1 = self._slice_point + 0.5
        self._axis = np.random.choice([0, 1, 2])
        # ---------- crossover
        species_A = []
        species_B = []
        coords_A = []
        coords_B = []
        for i in range(self.parent_A.num_sites):
            if ((self.parent_A.frac_coords[i, self._axis] <= sp0)
                    or (sp1 <= self.parent_A.frac_coords[i, self._axis])):
                species_A.append(self.parent_A[i].species_string)
                coords_A.append(self.parent_A[i].frac_coords)
            else:
                species_B.append(self.parent_A[i].species_string)
                coords_B.append(self.parent_A[i].frac_coords)
            if sp0 <= self.parent_B.frac_coords[i, self._axis] <= sp1:
                species_A.append(self.parent_B[i].species_string)
                coords_A.append(self.parent_B[i].frac_coords)
            else:
                species_B.append(self.parent_B[i].species_string)
                coords_B.append(self.parent_B[i].frac_coords)
        # ---------- adopt a structure with more atoms
        if len(species_A) > len(species_B):
            species = species_A
            coords = coords_A
        elif len(species_A) < len(species_B):
            species = species_B
            coords = coords_B
        else:
            if np.random.choice([0, 1]):
                species = species_A
                coords = coords_A
            else:
                species = species_B
                coords = coords_B
        # ---------- set instance variables
        self.species, self.coords = species, coords

    def _check_nat(self):
        self._nat_diff = []
        species_list = [a.species_string for a in self.child]
        for i in range(len(self.atype)):
            self._nat_diff.append(
                species_list.count(self.atype[i]) - self.nat[i])

    def _remove_within_mindist(self):
        '''
        if success: self.child <-- child structure data
        if fail: self.child <-- None
        '''
        for itype in range(len(self.atype)):
            while self._nat_diff[itype] > 0:
                # ---------- check dist
                dist_list = check_distance(self.child,
                                           self.atype,
                                           self.mindist,
                                           check_all=True)
                if not dist_list:  # nothing within mindist
                    return
                # ---------- appearance frequency
                ij_within_dist = [isite[0] for isite in dist_list
                                  ] + [jsite[1] for jsite in dist_list]
                site_counter = Counter(ij_within_dist)
                # ---------- get index for removing
                rm_index = None
                # ---- site[0]: index, site[1]: count
                for site in site_counter.most_common():
                    if self.child[site[0]].species_string == self.atype[itype]:
                        rm_index = site[0]
                        break  # break for loop
                # ---------- remove atom
                if rm_index is None:
                    self.child = None
                    return
                else:
                    self.child.remove_sites([rm_index])
                    self._nat_diff[itype] -= 1
        # ---------- final check
        dist_list = check_distance(self.child,
                                   self.atype,
                                   self.mindist,
                                   check_all=True)
        if dist_list:  # still something within mindist
            self.child = None

    def _remove_border_line(self):
        # ---------- rank atoms from border line
        coords_axis = self.child.frac_coords[:, self._axis]
        if self.crs_func == 'OP':
            # ------ one point crossover: boundary --> 0.0, slice_point, 1.0
            near_sp = (self._slice_point/2.0 < coords_axis) & \
                      (coords_axis < (self._slice_point + 1.0)/2.0)
            near_one = (self._slice_point + 1.0) / 2.0 <= coords_axis
            # -- distance from nearest boundary
            coords_diff = np.where(near_sp,
                                   abs(coords_axis - self._slice_point),
                                   coords_axis)
            coords_diff = np.where(near_one, 1.0 - coords_diff, coords_diff)
        elif self.crs_func == 'TP':
            # ------ two point crossover:
            #            boundary --> slice_point, slice_point + 0.5
            # -- distance from nearst boundary
            coords_diff = abs(self.child.frac_coords[:, self._axis] -
                              self._slice_point)
            coords_diff = np.where(0.5 < coords_diff, coords_diff - 0.5,
                                   coords_diff)
            coords_diff = np.where(0.25 < coords_diff, 0.5 - coords_diff,
                                   coords_diff)
        else:
            raise ValueError('crs_func should be OP or TP')
        atom_border_indx = np.argsort(coords_diff)
        # ---------- remove list
        rm_list = []
        for itype, nrm in enumerate(self._nat_diff):
            rm_list.append([])
            if nrm > 0:
                for ab_indx in atom_border_indx:
                    if self.child[ab_indx].species_string == self.atype[itype]:
                        rm_list[itype].append(ab_indx)
                    if len(rm_list[itype]) == nrm:
                        break
        # ---------- remove
        for each_type in rm_list:
            if each_type:
                self.child.remove_sites(each_type)

    def _add_border_line(self):
        for i in range(len(self.atype)):
            # ---------- counter
            cnt = 0
            # ---------- add atoms
            while self._nat_diff[i] < 0:
                cnt += 1
                coords = np.random.rand(3)
                self._mean_choice()
                coords[self._axis] = np.random.normal(loc=self._mean,
                                                      scale=0.08)
                self.child.append(species=self.atype[i], coords=coords)
                if check_distance(self.child, self.atype, self.mindist):
                    cnt = 0  # reset
                    self._nat_diff[i] += 1
                else:
                    self.child.pop()  # cancel
                # ------ fail
                if cnt == self.maxcnt_ea:
                    self.child = None
                    return

    def _mean_choice(self):
        '''which boundary possesses more atoms'''
        if self.crs_func == 'OP':
            n_zero = np.sum(
                np.abs(self.child.frac_coords[:, self._axis] - 0.0) < 0.1)
            n_slice = np.sum(
                np.abs(self.child.frac_coords[:, self._axis] -
                       self._slice_point) < 0.1)
            if n_zero < n_slice:
                self._mean = 0.0
            elif n_zero > n_slice:
                self._mean = self._slice_point
            else:
                self._mean = np.random.choice([0.0, self._slice_point])
        elif self.crs_func == 'TP':
            n_sp0 = np.sum(
                np.abs(self.child.frac_coords[:, self._axis] -
                       self._slice_point) < 0.1)
            n_sp1 = np.sum(
                np.abs(self.child.frac_coords[:, self._axis] -
                       self._slice_point - 0.5) < 0.1)
            if n_sp0 < n_sp1:
                self._mean = self._slice_point
            elif n_sp0 > n_sp1:
                self._mean = self._slice_point + 0.5
            else:
                self._mean = np.random.choice(
                    [self._slice_point, self._slice_point + 0.5])
        else:
            raise ValueError('crs_func must be OP or TP')
Ejemplo n.º 13
0
def get_vacancy_diffusion_pathways_from_cell(structure : Structure, atom_i : int, vis=False, get_midpoints=False):
    """

    Find Vacancy Strucutres for diffusion into and out of the specified atom_i site.

    :param structure: Structure
        Structure to calculate diffusion pathways
    :param atom_i: int
        Atom to get diffion path from
    :return: [ Structure ]
    """

    # To Find Pathway, look for voronoi edges
    orig_structure = structure.copy()
    structure = structure.copy() # type: Structure
    target_atom = structure[atom_i].specie
    vnn = VoronoiNN(targets=[target_atom])
    edges = vnn.get_nn_info(structure, atom_i)
    base_coords = structure[atom_i].coords

    # Add H in middle of the discovered pathways.  Use symmetry analysis to elminate equivlent H and therfore
    # equivalent pathways
    site_dir = {}
    for edge in edges:
        coords = np.round((base_coords + edge['site'].coords)/2,3)
        structure.append('H', coords, True)
       # site_dir[tuple(np.round(coords))] = structure.index(edge['site']) # Use Tuple for indexing dict, need to round
        site_dir[tuple(np.round(coords))] =  [list(x) for x in np.round(structure.frac_coords % 1,2) ].index(list(np.round(edge['site'].frac_coords % 1, 2))) # Use Tuple for indexing dict, need to round
    # Add H for all other diffusion atoms, so symmetry is preserved
    for i in get_atom_i(orig_structure, target_atom):
        sym_edges = vnn.get_nn_info(orig_structure, i)
        base_coords = structure[i].coords
        for edge in sym_edges:
            coords = (base_coords + edge['site'].coords) / 2
            try:
                structure.append('H', coords, True, True)
            except:
                pass

    # Remove symmetrically equivalent pathways:
    sga = SpacegroupAnalyzer(structure, 0.5, angle_tolerance=20)
    ss = sga.get_symmetrized_structure()

    final_structure = structure.copy()
    indices = []
    for i in range(len(orig_structure), len(orig_structure)+len(edges)): # get all 'original' edge sites
        sites = ss.find_equivalent_sites(ss[i])
        new_indices = [ss.index(site) for site in sites if ss.index(site) < len(orig_structure) + len(edges)] # Check if symmetrically equivalent to other original edge sites
        new_indices.remove(i)
        if i not in indices: # Don't duplicate effort
            indices = indices + new_indices
            indices.sort()
    indices = indices + list(range(len(orig_structure)+len(edges), len(final_structure)))
    final_structure.remove_sites(indices)
    diffusion_elements = [ site_dir[tuple(np.round(h.coords))] for h in final_structure[len(orig_structure):] ]
    if vis:
        view(final_structure, 'VESTA')
        print(diffusion_elements)

    if get_midpoints:
        centers = [h.frac_coords for h in final_structure[len(orig_structure):]]
        return (diffusion_elements, centers)


    return diffusion_elements
Ejemplo n.º 14
0
    def adsorb_both_surfaces(self,
                             molecule,
                             repeat=None,
                             min_lw=5.0,
                             reorient=True,
                             find_args={},
                             ltol=0.1,
                             stol=0.1,
                             angle_tol=0.01):
        """
        Function that generates all adsorption structures for a given
        molecular adsorbate on both surfaces of a slab.

        Args:
            molecule (Molecule): molecule corresponding to adsorbate
            repeat (3-tuple or list): repeat argument for supercell generation
            min_lw (float): minimum length and width of the slab, only used
                if repeat is None
            reorient (bool): flag on whether or not to reorient adsorbate
                along the miller index
            find_args (dict): dictionary of arguments to be passed to the
                call to self.find_adsorption_sites, e.g. {"distance":2.0}
            ltol (float): Fractional length tolerance. Default is 0.2.
            stol (float): Site tolerance. Defined as the fraction of the
                average free length per atom := ( V / Nsites ) ** (1/3)
                Default is 0.3.
            angle_tol (float): Angle tolerance in degrees. Default is 5 degrees.
        """

        # First get all possible adsorption configurations for this surface
        adslabs = self.generate_adsorption_structures(molecule,
                                                      repeat=repeat,
                                                      min_lw=min_lw,
                                                      reorient=reorient,
                                                      find_args=find_args)

        # Now we need to sort the sites by their position along
        # c as well as whether or not they are adsorbate
        single_ads = []
        for i, slab in enumerate(adslabs):
            sorted_sites = sorted(slab, key=lambda site: site.frac_coords[2])
            ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                           if site.surface_properties == "adsorbate"]
            non_ads_indices = [site_index for site_index, site in enumerate(sorted_sites) \
                               if site.surface_properties != "adsorbate"]

            species, fcoords, props = [], [], {"surface_properties": []}
            for site in sorted_sites:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)

            slab_other_side = Structure(slab.lattice,
                                        species,
                                        fcoords,
                                        site_properties=props)

            # For each adsorbate, get its distance from the surface and move it
            # to the other side with the same distance from the other surface
            for ads_index in ads_indices:
                props["surface_properties"].append("adsorbate")
                adsite = sorted_sites[ads_index]
                diff = abs(adsite.frac_coords[2] - \
                           sorted_sites[non_ads_indices[-1]].frac_coords[2])
                slab_other_side.append(
                    adsite.specie, [
                        adsite.frac_coords[0], adsite.frac_coords[1],
                        sorted_sites[0].frac_coords[2] - diff
                    ],
                    properties={"surface_properties": "adsorbate"})
                # slab_other_side[-1].add

            # Remove the adsorbates on the original side of the slab
            # to create a slab with one adsorbate on the other side
            slab_other_side.remove_sites(ads_indices)

            # Put both slabs with adsorption on one side
            # and the other in a list of slabs for grouping
            single_ads.extend([slab, slab_other_side])

        # Now group the slabs.
        matcher = StructureMatcher(ltol=ltol, stol=stol, angle_tol=angle_tol)
        groups = matcher.group_structures(single_ads)

        # Each group should be a pair with adsorbate on one side and the other.
        # If a slab has no equivalent adsorbed slab on the other side, skip.
        adsorb_both_sides = []
        for i, group in enumerate(groups):
            if len(group) != 2:
                continue
            ads1 = [site for site in group[0] if \
                    site.surface_properties == "adsorbate"][0]
            group[1].append(ads1.specie,
                            ads1.frac_coords,
                            properties={"surface_properties": "adsorbate"})
            # Build the slab object
            species, fcoords, props = [], [], {"surface_properties": []}
            for site in group[1]:
                species.append(site.specie)
                fcoords.append(site.frac_coords)
                props["surface_properties"].append(site.surface_properties)
            s = Structure(group[1].lattice,
                          species,
                          fcoords,
                          site_properties=props)
            adsorb_both_sides.append(s)

        return adsorb_both_sides