def cluster(S, tol=1.1, seed_index=0, write_poscar_from_cluster=False): # S = Structure object # tol = tolerance bonding S = SpacegroupAnalyzer(S, 0.1).get_conventional_standard_structure() if len(S) < 20: S.make_supercell(2) Distance_matrix = S.distance_matrix #np.fill_diagonal(Distance_matrix,100) radii = [Elem_radius[site.species_string] for site in S.sites] radiiT = np.array(radii)[np.newaxis].T #transpose of radii list radii_matrix = radii + radiiT * tol temp = Distance_matrix - radii_matrix binary_matrix = (temp < 0).astype(int) original = list(range(len(S))) final = [] difference = [] counter = 0 while (len(original) != len(final)): seed = set((np.where(binary_matrix[seed_index] == 1))[0]) cluster = seed NEW = seed check = True while check: Ss = set() for n in NEW: Ss.update(set(np.where(binary_matrix[n] == 1)[0])) #print n,set(np.where( binary_matrix[n]==1 )[0]) if Ss.issubset(cluster): check = False else: NEW = Ss - cluster cluster.update(Ss) L = [] cl = list(cluster) sites = [S[i] for i in cl] S.from_sites(sites).to('POSCAR', 'POSCAR{}'.format(str(counter))) counter += 1 final = set(final) final.update(cluster) final = list(final) difference = list(set(original) - set(final)) if (len(difference) > 0): seed_index = difference[0]
def get_structure_type(structure, tol=0.1, seed_index=0, write_poscar_from_cluster=False): """ This is a topology-scaling algorithm used to describe the periodicity of bonded clusters in a bulk structure. Args: structure (structure): Pymatgen structure object to classify. tol (float): Additional percent of atomic radii to allow for overlap, thereby defining bonds (0.1 = +10%, -0.1 = -10%) seed_index (int): Atom number to start the cluster. write_poscar_from_cluster (bool): Set to True to write a POSCAR file from the sites in the cluster. Returns: string. "molecular" (0D), "chain" (1D), "layered" (2D), or "conventional" (3D). Also includes " heterogeneous" if the cluster's composition is not equal to that of the overal structure. """ # Get conventional structure to orthogonalize the lattice as # much as possible. A tolerance of 0.1 Angst. was suggested by # pymatgen developers. s = SpacegroupAnalyzer(structure, 0.1).get_conventional_standard_structure() heterogeneous = False noble_gases = ["He", "Ne", "Ar", "Kr", "Xe", "Rn"] if len([e for e in structure.composition if e.symbol in noble_gases]) != 0: type = "noble gas" else: # make 2x2x2 supercell to ensure sufficient number of atoms # for cluster building. s.make_supercell(2) # Distance matrix (rowA, columnB) shows distance between # atoms A and B, taking PBCs into account. distance_matrix = s.distance_matrix # Fill diagonal with a large number, so the code knows that # each atom is not bonded to itself. np.fill_diagonal(distance_matrix, 100) # Rows (`radii`) and columns (`radiiT`) of radii. radii = [ELEMENT_RADII[site.species_string] for site in s.sites] radiiT = np.array(radii)[np.newaxis].T radii_matrix = radii + radiiT * (1 + tol) # elements of temp that have value less than 0 are bonded. temp = distance_matrix - radii_matrix # True (1) is placed where temp < 0, and False (0) where # it is not. binary_matrix = (temp < 0).astype(int) # list of atoms bonded to the seed atom of a cluster seed = set((np.where(binary_matrix[seed_index] == 1))[0]) cluster = seed NEW = seed while True: temp_set = set() for n in NEW: # temp_set will have all atoms, without duplicates, # that are connected to all atoms in NEW. temp_set.update(set(np.where(binary_matrix[n] == 1)[0])) if temp_set.issubset(cluster): # if temp_set has no new atoms, the search is done. break else: NEW = temp_set - cluster # List of newly discovered atoms cluster.update(temp_set) # cluster is updated with new atoms if len(cluster) == 0: # i.e. the cluster is a single atom. cluster = [seed_index] # Make sure it's not empty to write POSCAR. type = "molecular" elif len(cluster) == len(s.sites): # i.e. all atoms are bonded. type = "conventional" else: cmp = Composition.from_dict( Counter([s[l].specie.name for l in list(cluster)])) if cmp.reduced_formula != s.composition.reduced_formula: # i.e. the cluster does not have the same composition # as the overall crystal; therefore there are other # clusters of varying composition. heterogeneous = True old_cluster_size = len(cluster) # Increase structure to determine whether it is # layered or molecular, then perform the same kind # of cluster search as before. s.make_supercell(2) distance_matrix = s.distance_matrix np.fill_diagonal(distance_matrix, 100) radii = [ELEMENT_RADII[site.species_string] for site in s.sites] radiiT = np.array(radii)[np.newaxis].T radii_matrix = radii + radiiT * (1 + tol) temp = distance_matrix - radii_matrix binary_matrix = (temp < 0).astype(int) seed = set((np.where(binary_matrix[seed_index] == 1))[0]) cluster = seed NEW = seed check = True while check: temp_set = set() for n in NEW: temp_set.update(set(np.where(binary_matrix[n] == 1)[0])) if temp_set.issubset(cluster): check = False else: NEW = temp_set - cluster cluster.update(temp_set) if len(cluster) != 4 * old_cluster_size: type = "molecular" else: type = "layered" if heterogeneous: type += " heterogeneous" cluster_sites = [s.sites[n] for n in cluster] if write_poscar_from_cluster: s.from_sites(cluster_sites).get_primitive_structure().to( "POSCAR", "POSCAR") return type
def get_structure_type(structure, tol=0.1, seed_index=0, write_poscar_from_cluster=False): """ This is a topology-scaling algorithm used to describe the periodicity of bonded clusters in a bulk structure. Args: structure (structure): Pymatgen structure object to classify. tol (float): Additional percent of atomic radii to allow for overlap, thereby defining bonds (0.1 = +10%, -0.1 = -10%) seed_index (int): Atom number to start the cluster. write_poscar_from_cluster (bool): Set to True to write a POSCAR file from the sites in the cluster. Returns: string. "molecular" (0D), "chain" (1D), "layered" (2D), or "conventional" (3D). Also includes " heterogeneous" if the cluster's composition is not equal to that of the overal structure. """ # Get conventional structure to orthogonalize the lattice as # much as possible. A tolerance of 0.1 Angst. was suggested by # pymatgen developers. s = SpacegroupAnalyzer(structure, 0.1).get_conventional_standard_structure() heterogeneous = False noble_gases = ["He", "Ne", "Ar", "Kr", "Xe", "Rn"] if len([e for e in structure.composition if e.symbol in noble_gases]) != 0: type = "noble gas" else: # make 2x2x2 supercell to ensure sufficient number of atoms # for cluster building. s.make_supercell(2) # Distance matrix (rowA, columnB) shows distance between # atoms A and B, taking PBCs into account. distance_matrix = s.distance_matrix # Fill diagonal with a large number, so the code knows that # each atom is not bonded to itself. np.fill_diagonal(distance_matrix, 100) # Rows (`radii`) and columns (`radiiT`) of radii. radii = [ELEMENT_RADII[site.species_string] for site in s.sites] radiiT = np.array(radii)[np.newaxis].T radii_matrix = radii + radiiT*(1+tol) # elements of temp that have value less than 0 are bonded. temp = distance_matrix - radii_matrix # True (1) is placed where temp < 0, and False (0) where # it is not. binary_matrix = (temp < 0).astype(int) # list of atoms bonded to the seed atom of a cluster seed = set((np.where(binary_matrix[seed_index]==1))[0]) cluster = seed NEW = seed while True: temp_set = set() for n in NEW: # temp_set will have all atoms, without duplicates, # that are connected to all atoms in NEW. temp_set.update(set(np.where(binary_matrix[n]==1)[0])) if temp_set.issubset(cluster): # if temp_set has no new atoms, the search is done. break else: NEW = temp_set - cluster # List of newly discovered atoms cluster.update(temp_set) # cluster is updated with new atoms if len(cluster) == 0: # i.e. the cluster is a single atom. cluster = [seed_index] # Make sure it's not empty to write POSCAR. type = "molecular" elif len(cluster) == len(s.sites): # i.e. all atoms are bonded. type = "conventional" else: cmp = Composition.from_dict(Counter([s[l].specie.name for l in list(cluster)])) if cmp.reduced_formula != s.composition.reduced_formula: # i.e. the cluster does not have the same composition # as the overall crystal; therefore there are other # clusters of varying composition. heterogeneous = True old_cluster_size = len(cluster) # Increase structure to determine whether it is # layered or molecular, then perform the same kind # of cluster search as before. s.make_supercell(2) distance_matrix = s.distance_matrix np.fill_diagonal(distance_matrix,100) radii = [ELEMENT_RADII[site.species_string] for site in s.sites] radiiT = np.array(radii)[np.newaxis].T radii_matrix = radii + radiiT*(1+tol) temp = distance_matrix-radii_matrix binary_matrix = (temp < 0).astype(int) seed = set((np.where(binary_matrix[seed_index]==1))[0]) cluster = seed NEW = seed check = True while check: temp_set = set() for n in NEW: temp_set.update(set(np.where(binary_matrix[n]==1)[0])) if temp_set.issubset(cluster): check = False else: NEW = temp_set - cluster cluster.update(temp_set) if len(cluster) != 4 * old_cluster_size: type = "molecular" else: type = "layered" if heterogeneous: type += " heterogeneous" cluster_sites = [s.sites[n] for n in cluster] if write_poscar_from_cluster: s.from_sites(cluster_sites).get_primitive_structure().to("POSCAR", "POSCAR") return type