def __init__( self, initial_structure: Structure, task: Task, kpt_mode: Optional[KpointsMode] = None, # None for default setting kpt_density: Optional[float] = None, # in Å gamma_centered: Optional[bool] = None, # Vasp definition only_even_num_kpts: bool = False, # If ceil kpt numbers to be even. num_kpt_factor: Optional[int] = None, # Set NKRED to this as well. band_ref_dist: float = defaults.band_mesh_distance, symprec: float = defaults.symmetry_length_tolerance, angle_tolerance: float = defaults.symmetry_angle_tolerance, is_magnetization: bool = False): # Whether the system is magnetic. self._initial_structure = initial_structure.copy() self._task = task self._kpt_density = kpt_density or defaults.kpoint_density self._is_magnetization = is_magnetization self._num_kpt_factor = num_kpt_factor or self._task.default_kpt_factor self._symmetrizer = \ StructureSymmetrizer(structure=self._initial_structure, symprec=symprec, angle_tolerance=angle_tolerance, band_mesh_distance=band_ref_dist, time_reversal=(not self._is_magnetization)) # overwrite options fixed by other options self._gamma_centered = task.requisite_gamma_centered or gamma_centered self._adjust_only_even_num_kpts(only_even_num_kpts) self._adjust_kpt_mode(kpt_mode)
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
def merge_clashing(s: Structure, tolerance_factor: float = 0.9): """ Naive method for 'merging' clashing sites. In case it the two sites are not the same elements, priority will be given to the heavier one. Args: s: Returns: """ crystal = s.copy() duplicates = get_duplicates_dynamic_threshold(s, tolerance_factor) logger.debug('found %s clashing sites', duplicates) deleted = [] for duplicate in duplicates: if (not duplicate[0] in deleted) and (not duplicate[1] in deleted): element0 = crystal[duplicate[0]].specie.number element1 = crystal[duplicate[1]].specie.number if element0 == element1: deleted.append(duplicate[1]) elif element0 > element1: deleted.append(duplicate[1]) elif element0 < element1: deleted.append(duplicate[0]) crystal.remove_sites(deleted) return crystal
def poscar_from_sitelist( configs, labels, sitelists, structure, subset = None ): """ Uses pymatgen Structure.to() method to generates POSCAR files for a set of configurations within a parent structure. Args: configs (list): site configurations labels (list): atom labels to output sitelist (list): list of sites in fractional coords structure (pymatgen Structure): Parent structure subset (Optional [list]): list of atom indices to output """ if subset: species_clean = [ spec for i,spec in enumerate( structure.species ) if i not in subset ] species_config = [ spec for i,spec in enumerate( structure.species ) if i in subset ] frac_coords_clean = [ coord for i, coord in enumerate( structure.frac_coords ) if i not in subset ] clean_structure = Structure( structure.lattice, species_clean, frac_coords_clean ) else: clean_structure = Structure( structure.lattice, [], [] ) species_config = structure.species for idx, config in enumerate( configs, start=1 ): structure_config = clean_structure.copy() for label in labels: for pos in config.position( label ): for sitelist in sitelists: structure_config.append( species_config[ pos ], sitelist[ pos ] ) structure_config.to( filename="POSCAR_{}.vasp".format( idx ) )
def agnr_edge_types(cell: Structure, atom_list=None, combinations=None): #To check whether the graphene structure is broken if there is an atom with only 1 bond def broken_structure(cell: Structure): all_bonds = calculate_bond_list(structure=cell) for atoms in all_bonds: if len(atoms) == 1: return True return False #Get all the bonds all_bonds = calculate_bond_list(structure=cell) #Get the atoms with only 2 bonds atoms_2bonds = [] atoms_2bonds_index = [] for index, atoms in enumerate(all_bonds): if len(atoms) == 2: atoms_2bonds.append(atoms) atoms_2bonds_index.append(index) #Store the combinations of atoms indices list if combinations == None: combinations = set() if atom_list is None: atom_list = [i for i in range(len(all_bonds))] #Store all possible combinations of the cell with the removed atoms in a list structures = [] for atoms in atoms_2bonds: for bonds in atoms: #Go through the atoms with only 2 bonds and check if its neighbor has 2 bonds. if bonds[4] in atoms_2bonds_index: atom_list_temp = atom_list.copy() #Remove the atoms from indices list if bonds[3] > bonds[4]: del atom_list_temp[bonds[3]] del atom_list_temp[bonds[4]] else: del atom_list_temp[bonds[4]] del atom_list_temp[bonds[3]] #Remove the atoms from structure cell_temp = cell.copy() cell_temp.remove_sites([bonds[3], bonds[4]]) #Check whether there are duplicates and if the structure is broken if tuple(atom_list_temp ) not in combinations and broken_structure( cell_temp) == False: combinations.add(tuple(atom_list_temp)) structures.append(cell_temp) structures.extend( edge_types(cell_temp, atom_list_temp, combinations)) return structures
def remove_random_carbon(structure: Structure) -> Structure: structure = structure.copy() carbon_indices = [ idx for idx, s in enumerate(structure.species) if s == Element.C ] to_remove = np.random.randint(len(carbon_indices)) structure.remove_sites([to_remove]) return structure
def __init__(self, structure: Structure, symprec: float = defaults.symmetry_length_tolerance, angle_tolerance: float = defaults.symmetry_angle_tolerance, time_reversal: bool = True, band_mesh_distance: float = defaults.band_mesh_distance): """Get full information of seekpath band path. Note: site properties such as magmom are removed. The structures of aP (SG:1, 2), mC (5, 8, 9, 12, 15) and oA (38, 39, 40, 41) can be different between spglib and seekpath. see Y. Hinuma et al. Comput. Mater. Sci. 128 (2017) 140–184 -- spglib mC 6.048759 -3.479491 0.000000 6.048759 3.479491 0.000000 -4.030758 0.000000 6.044512 -- seekpath mC 6.048759 3.479491 0.000000 -6.048759 3.479491 0.000000 -4.030758 0.000000 6.044512 """ self.structure = structure.copy() if structure.site_properties: logger.warning( f"Site property {structure.site_properties.keys()} " f"removed in primitive and conventional structures.") self.symprec = symprec self.angle_tolerance = angle_tolerance self.time_reversal = time_reversal self.ref_distance = band_mesh_distance lattice_matrix = structure.lattice.matrix positions = structure.frac_coords.tolist() atomic_numbers = [i.specie.number for i in structure.sites] self.cell = (lattice_matrix, positions, atomic_numbers) # evaluated lazily self._spglib_sym_data = None self._conventional = None self._primitive = None self._second_primitive = None self._seekpath_data = None self._band_primitive = None self._irreducible_kpoints = None
def check_unbound(s: Structure, whitelist: list = ['H'], threshold: float = 2.5, mode='naive') -> bool: """ This uses the fact that unbound solvent is often in pores and more distant from all other atoms. So far this test focusses on water. Args: s (pymatgen structure object): structure to be checked whitelist (list): elements that are not considered in the check (they are basically removed from the structure) mode (str): checking mode. If 'naive' then a simple distance based check is used and a atom is detected as unbound it there is no other atom within the threshold distance. Returns: """ crystal = s.copy() if whitelist: crystal.remove_species(whitelist) if mode == 'naive': for atom in crystal: neighbors = crystal.get_neighbors(atom, threshold) if len(neighbors) == 0: return True return False if mode == 'graph': nn_strategy = JmolNN() sgraph = StructureGraph.with_local_env_strategy( crystal, nn_strategy) molecules = get_subgraphs_as_molecules_all(sgraph) if len(molecules) > 0: return True else: return False
def remove_unbound_solvent(structure: Structure) -> Structure: """ Constructs a structure graph and removes unbound solvent molecules if they are in a hardcoded composition list. Args: structure (pymatgen structure object=: Returns: """ crystal = structure.copy() molecules_solvent = ['H2 O1', 'H3 O1', 'C2 H6 O S', 'O1'] nn_strategy = JmolNN() sgraph = StructureGraph.with_local_env_strategy(crystal, nn_strategy) molecules = get_subgraphs_as_molecules_all(sgraph) cart_coordinates = crystal.cart_coords indices = [] for molecule in molecules: print(str(molecule.composition)) if molecule.formula in molecules_solvent: for coord in [ site.as_dict()['xyz'] for site in molecule.sites ]: if (coord[0] < crystal.lattice.a) and ( coord[1] < crystal.lattice.b) and ( coord[2] < crystal.lattice.c): print(coord) indices.append( np.where( np.prod( np.isclose(cart_coordinates - coord, 0), axis=1) == 1)[0][0]) print(indices) crystal.remove_sites(indices) return crystal
class Test2D(unittest.TestCase): def setUp(self): ## simple toy structure for preliminary testing self.a0 = 3.0 self.c = 20.0 self.structure = Structure( Lattice.from_parameters(a=self.a0, b=self.a0, c=self.c, alpha=90, beta=90, gamma=90), ["O", "O"], [[0.0, 0.0, 0.1], [0.5, 0.5, 0.3]]) self.nvecs = [3, 3, 1] self.vacuum = 20 self.q = 0 self.structure.make_supercell(self.nvecs) self.structure_bulk = self.structure.copy() self.initdef_list = [] self.initdef_list.append( {"def1": { "type": "vac", "species": "O", "index": 0 }}) self.initdef_list.append({ "def1": { "type": "sub", "index": -1, "species": "O", "species_new": "N" } }) self.initdef_list.append({ "def1": { "type": "sub", "index": -1, "species": "O", "species_new": "N" }, "def2": { "type": "vac", "index": -1, "species": "O", "index_offset_n1n2": -1 } }) self.initdef_list.append({ "def1": { "type": "ad", "index": [-1], "species": ["O"], "species_new": "N", "shift_z": "3.0" } }) self.initdef_list.append({ "def1": { "type": "int", "index": [0, 0, 0, 0], "index_offset_n1": [0, 1, 0, 0], "index_offset_n2": [0, 0, 0, 2], "index_offset_n1n2": [0, 0, 1, 1], "species": 4 * ["O"], "species_new": "N" } }) self.create_defects() self.siteinds_list = [[0], [17], [17, 8], [[17]], [[0, 3, 9, 11]]] self.defcoords_list = [[[0.0, 0.0, 0.1]], [[0.833333, 0.833333, 0.3]], [[0.833333, 0.833333, 0.3], [0.666667, 0.666667, 0.1]], [[0.833333, 0.833333, 0.45]], [[0.166667, 0.0, 0.2]]] self.natoms_list = [17, 18, 17, 19, 19] def create_defects(self): ## create defects self.defects_list = [] for initdef in self.initdef_list: ## initialize defect object defect = gen_defect_supercells.Defect(self.structure_bulk, self.structure.copy(), self.nvecs, self.vacuum, self.q) ## set the defect info (type, site, species) for each defect for d in initdef: initdef[d]["index_offset_n1"] = initdef[d].get( "index_offset_n1", 0) initdef[d]["index_offset_n2"] = initdef[d].get( "index_offset_n2", 0) initdef[d]["index_offset_n1n2"] = initdef[d].get( "index_offset_n1n2", 0) defect_site, siteinds = defect.get_defect_site(initdef[d]) defect.add_defect_info(initdef[d], defect_site) ## create defect(s) defect.remove_atom() defect.replace_atom() defect.add_atom() self.defects_list.append(defect) def test_get_site_index(self): ## test get_site_index returns the correct absolute site indices ## initialize dummy "defect" object defect = gen_defect_supercells.Defect(self.structure_bulk, self.structure.copy(), self.nvecs, self.vacuum, self.q) for initdef, siteinds in zip(self.initdef_list, self.siteinds_list): for d, siteind_ref in zip(initdef, siteinds): if initdef[d]["type"][0] == "v" or initdef[d]["type"][0] == "s": siteind = defect.get_site_ind( initdef[d]["index"], initdef[d]["species"], initdef[d]["index_offset_n1"], initdef[d]["index_offset_n2"], initdef[d]["index_offset_n1n2"]) self.assertEqual(siteind, siteind_ref) if initdef[d]["type"][0] == "a" or initdef[d]["type"][0] == "i": for siteindi_ref,ind,sp,offset_n1,offset_n2,offset_n1n2 \ in zip(siteind_ref, initdef[d]["index"], initdef[d]["species"], initdef[d]["index_offset_n1"], initdef[d]["index_offset_n2"], initdef[d]["index_offset_n1n2"]): siteind = defect.get_site_ind(ind, sp, offset_n1, offset_n2, offset_n1n2) self.assertEqual(siteind, siteindi_ref) def test_defcoords(self): ## test that the defect(s) are created at the correct position ## essentially tests the get_defect_site function for defects, defcoords_ref in zip(self.defects_list, self.defcoords_list): for defect_site, defcoord_ref in zip(defects.defect_site, defcoords_ref): defcoord = defect_site.lattice.get_fractional_coords( defect_site.coords) # defcoord = defect_site.frac_coords for j in range(3): self.assertAlmostEqual(defcoord[j] % 1 % 1, defcoord_ref[j] % 1 % 1, places=6) def test_natoms(self): ## test that the defect creation results in the correct number of atoms for defect, natoms in zip(self.defects_list, self.natoms_list): self.assertEqual(defect.structure.num_sites, natoms)
return nfinal # Potential issues: # --Poor accounting of internal periodicity # -definition of relative term can be ambiguous depending on structure # --Assumes defect structure and primordial structures are aligned if __name__ == "__main__": #Sanity checks #Test 1 - Final size cannot be constructed directly from primordial structure ssize=[5,5,5] fsize=[9,5,5] psize=[2,5,5] felat = Lattice.cubic(2.87) primf = Structure(felat,["Fe","Fe"],[[0,0,0],[0.5,0.5,0.5]]) primordialstructure = primf.copy() primordialstructure.make_supercell(psize) f = primf.copy() f.make_supercell(fsize) write_structure(primordialstructure,'POSCAR_prim1') final = finite_size_scale('POSCAR_Fdefect', ssize, 'POSCAR_prim1', fsize, psize) write_structure(f,'POSCAR_F1expected') write_structure(final,'POSCAR_F1') #Test 2 - Test in all 3 dimensions ssize=[5,5,5] fsize=[8,8,8] psize=[1,1,1] primordialstructure = primf.copy() primordialstructure.make_supercell(psize) f = primf.copy()
def test_supercell_subsets(self): sm = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size='volume') sm_no_s = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=False, supercell_size='volume') l = Lattice.orthorhombic(1, 2, 3) s1 = Structure(l, ['Ag', 'Si', 'Si'], [[.7, .4, .5], [0, 0, 0.1], [0, 0, 0.2]]) s1.make_supercell([2, 1, 1]) s2 = Structure(l, ['Si', 'Si', 'Ag'], [[0, 0.1, -0.95], [0, 0.1, 0], [-.7, .5, .375]]) shuffle = [0, 2, 1, 3, 4, 5] s1 = Structure.from_sites([s1[i] for i in shuffle]) # test when s1 is exact supercell of s2 result = sm.get_s2_like_s1(s1, s2) for a, b in zip(s1, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species, b.species) self.assertTrue(sm.fit(s1, s2)) self.assertTrue(sm.fit(s2, s1)) self.assertTrue(sm_no_s.fit(s1, s2)) self.assertTrue(sm_no_s.fit(s2, s1)) rms = (0.048604032430991401, 0.059527539448807391) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, s1), rms)) # test when the supercell is a subset of s2 subset_supercell = s1.copy() del subset_supercell[0] result = sm.get_s2_like_s1(subset_supercell, s2) self.assertEqual(len(result), 6) for a, b in zip(subset_supercell, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species, b.species) self.assertTrue(sm.fit(subset_supercell, s2)) self.assertTrue(sm.fit(s2, subset_supercell)) self.assertFalse(sm_no_s.fit(subset_supercell, s2)) self.assertFalse(sm_no_s.fit(s2, subset_supercell)) rms = (0.053243049896333279, 0.059527539448807336) self.assertTrue(np.allclose(sm.get_rms_dist(subset_supercell, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, subset_supercell), rms)) # test when s2 (once made a supercell) is a subset of s1 s2_missing_site = s2.copy() del s2_missing_site[1] result = sm.get_s2_like_s1(s1, s2_missing_site) for a, b in zip((s1[i] for i in (0, 2, 4, 5)), result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species, b.species) self.assertTrue(sm.fit(s1, s2_missing_site)) self.assertTrue(sm.fit(s2_missing_site, s1)) self.assertFalse(sm_no_s.fit(s1, s2_missing_site)) self.assertFalse(sm_no_s.fit(s2_missing_site, s1)) rms = (0.029763769724403633, 0.029763769724403987) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2_missing_site), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2_missing_site, s1), rms))
def zgnr_edge_types(cell: Structure, atom_list=None, combinations=None): def recurse_bonds(current_bonds: np.array, all_bonds: np.array, previous_atom=-1, traverse_atoms=None): ''' Get a set of atoms that can be traversed by starting from a atom Return a dictionary, traverse_atoms, in which the keys are the atoms traversed and the values are a set of periodic direction to move to that atom ''' if traverse_atoms == None: traverse_atoms = dict() for bonds in current_bonds: # Do not move back to the previous atom # Do not move in the negative periodic direction if bonds[4] != previous_atom and bonds[0] != -1: current_atom = bonds[3] neighbor_atom = bonds[4] # Create the key if the atom has not been traversed if neighbor_atom not in traverse_atoms: traverse_atoms[neighbor_atom] = {bonds[0]} recurse_bonds(all_bonds[neighbor_atom], all_bonds, current_atom, traverse_atoms) # Add the new periodic direction. The values can only be 0 or 1 else: traverse_atoms[neighbor_atom].add(bonds[0]) return traverse_atoms def broken_structure(cell: Structure): ''' Check whether the graphene structure is broken if there is an atom with only 1 bond or if the structure is no longer periodic ''' try: all_bonds = calculate_bond_list(structure=cell) except IndexError: return True for atoms in all_bonds: if atoms.shape[0] == 1: return True # Get the list of atoms traversed by starting from atom 0 atom0 = all_bonds[0][0][3] traverse_atoms = recurse_bonds(all_bonds[0], all_bonds) traverse_path = set() for i in traverse_atoms.values(): traverse_path.update(i) # If there is no path moving back to atom 0 or if no positive periodic direction is # taken, then the structure is broken if atom0 not in traverse_atoms.keys() or 1 not in traverse_path \ or len(traverse_atoms.keys()) != len(all_bonds): return True return False all_bonds = calculate_bond_list(structure=cell) # Get the atoms with only 2 bonds atoms_2bonds_index = set() for index, atoms in enumerate(all_bonds): if atoms.shape[0] == 2: atoms_2bonds_index.add(index) # Store the set of atoms of the structures for search for duplicates if combinations == None: combinations = set() if atom_list is None: atom_list = [i for i in range(len(all_bonds))] # Store all possible structures with the removed atoms in a list structures = [] for atoms in all_bonds: bond1 = atoms[0] bond2 = atoms[1] bond1_in_2bonds = bond1[4] in atoms_2bonds_index bond2_in_2bonds = bond2[4] in atoms_2bonds_index cell_temp = cell.copy() atom_list_temp = atom_list.copy() # Remove the atoms from both the atom_list and the structure bond_list = None if bond1_in_2bonds and bond2_in_2bonds: bond_list = [bond1[3], bond1[4], bond2[4]] cell_temp.remove_sites([bond1[3], bond1[4], bond2[4]]) elif atoms.shape[0] == 3: bond3 = atoms[2] bond3_in_2bonds = bond3[4] in atoms_2bonds_index if bond1_in_2bonds and bond3_in_2bonds: bond_list = [bond1[3], bond1[4], bond3[4]] cell_temp.remove_sites([bond1[3], bond1[4], bond3[4]]) elif bond2_in_2bonds and bond3_in_2bonds: bond_list = [bond2[3], bond2[4], bond3[4]] cell_temp.remove_sites([bond2[3], bond2[4], bond3[4]]) elif bond1[3] in atoms_2bonds_index and bond1_in_2bonds: del atom_list_temp[max([bond1[3], bond1[4]])] del atom_list_temp[min([bond1[3], bond1[4]])] cell_temp.remove_sites([bond1[3], bond1[4]]) elif bond1[3] in atoms_2bonds_index and bond2_in_2bonds: del atom_list_temp[max([bond1[3], bond2[4]])] del atom_list_temp[min([bond1[3], bond2[4]])] cell_temp.remove_sites([bond1[3], bond2[4]]) if bond_list != None: del atom_list_temp[max(bond_list)] bond_list.remove(max(bond_list)) del atom_list_temp[max(bond_list)] del atom_list_temp[min(bond_list)] # Check whether there are duplicates and if the structure is broken if cell_temp != cell and atom_list_temp != atom_list: if broken_structure(cell_temp) == False and tuple( atom_list_temp) not in combinations: combinations.add(tuple(atom_list_temp)) structures.append(cell_temp) structures.extend( edge_types(cell_temp, atom_list_temp, combinations)) return structures
def get_interstitial_diffusion_pathways_from_cell(structure : Structure, interstitial_atom : str, vis=False, get_midpoints=False, dummy='He', min_dist=0.5, weight_cutoff=0.0001, is_interstitial_structure=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 ] """ vnn = VoronoiNN(targets=[interstitial_atom]) # To Find Pathway, look for voronoi edges if not is_interstitial_structure: orig_structure = structure.copy() structure = structure.copy() # type: Structure interstitial_structure = structure.copy() interstitial_structure.DISTANCE_TOLERANCE = 0.01 if vis: Poscar(structure).write_file(vis) open_in_VESTA(vis) inter_gen = list(VoronoiInterstitialGenerator(orig_structure, interstitial_atom)) if vis: print(len(inter_gen)) for interstitial in inter_gen: sat_structure = None for dist_tol in [0.2, 0.15, 0.1, 0.05, 0.01, 0.001]: try: sat_structure = create_saturated_interstitial_structure(interstitial, dist_tol=dist_tol) # type: Structure break except ValueError: continue except TypeError: continue if not sat_structure: continue sat_structure.remove_site_property('velocities') if vis: Poscar(sat_structure).write_file(vis) open_in_VESTA(vis) time.sleep(0.5) for site in sat_structure: # type: PeriodicSite if site.specie == interstitial_atom: try: interstitial_structure.append(site.specie, site.coords, coords_are_cartesian=True, validate_proximity=True) except StructureError: pass # combined_structure.merge_sites(mode='delete') interstitial_structure.remove_site_property('velocities') if vis: Poscar(interstitial_structure).write_file(vis) open_in_VESTA(vis) else: interstitial_structure = structure.copy() # edges = vnn.get_nn_info(structure, atom_i) # base_coords = structure[atom_i].coords pathway_structure = interstitial_structure.copy() # type: Structure pathway_structure.DISTANCE_TOLERANCE = 0.01 # Add H for all other diffusion atoms, so symmetry is preserved for i in get_atom_i(interstitial_structure, interstitial_atom): sym_edges = vnn.get_nn_info(interstitial_structure, i) base = pathway_structure[i] # type: PeriodicSite for edge in sym_edges: dest = edge['site'] if base.distance(dest, jimage=edge['image']) > min_dist and edge['weight'] > weight_cutoff: coords = (base.coords + dest.coords) / 2 try: neighbors = [i, edge['site_index']] # neighbors.sort() pathway_structure.append(dummy, coords, True, validate_proximity=True, properties={'neighbors': neighbors, 'image' : edge['image']}) except StructureError: pass except ValueError: pass if vis: Poscar(pathway_structure).write_file(vis) open_in_VESTA(vis) # Remove symmetrically equivalent pathways: # sga = SpacegroupAnalyzer(pathway_structure, 0.1, angle_tolerance=10) # ss = sga.get_symmetrized_structure() return interstitial_structure, pathway_structure
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
def remove_disorder(structure: Structure, distance: float = 0.3) -> Structure: """ Merges sites within distance that are likely due to structural disorder. Inspired by the pymatgen merge function - we assume that the site properties of the clustered species are all the same - we assume that we can replace the disorder with an averaged position Args: structure (pymatgen Structure object): distance (float): distance threshold for the merging operation Returns: structure object with merged sites """ crystal = structure.copy() d = crystal.distance_matrix indices_to_dump = [] to_append = defaultdict(list) symbol_indices_dict = get_symbol_indices(crystal) symbol_set = symbol_indices_dict.keys() for symbol in symbol_set: sub_matrix = d[ symbol_indices_dict[symbol], :][:, symbol_indices_dict[symbol]] # perform hierarchical clustering, get flat array of indices clusters = fcluster( linkage(squareform(sub_matrix)), distance, 'distance') symbol_indices = symbol_indices_dict[symbol] species_coord = [crystal[i].frac_coords for i in symbol_indices] for c in np.unique(clusters): inds = np.where(clusters == c)[0] indices_to_dump.append([symbol_indices[i] for i in inds]) coords = [species_coord[i] for i in inds] if len(coords) == 1: average_coord = np.concatenate(coords).ravel().tolist() else: average_coord = np.mean(coords, axis=0).tolist() to_append[symbol].append(average_coord) indices_to_dump = list( set(np.concatenate(indices_to_dump).ravel().tolist())) crystal.remove_sites(indices_to_dump) to_append = dict(to_append) for symbol in to_append.keys(): for coord in to_append[symbol]: crystal.append( symbol, coord, validate_proximity=False, ) return crystal
class Test2D_WSe2(Test2D): def setUp(self): ## hexagonal WSe2 unitcell self.a0 = 3.287596 self.c = 23.360843 self.structure = Structure( Lattice.from_parameters(a=self.a0, b=self.a0, c=self.c, alpha=90, beta=90, gamma=120), ["W", "Se", "Se"], [[0.0, 0.0, 0.5], [0.333333, 0.666667, 0.571091], [0.333333, 0.666667, 0.428909]]) self.nvecs = [4, 4, 1] self.vacuum = 20 self.q = 0 self.structure.make_supercell(self.nvecs) self.structure_bulk = self.structure.copy() self.initdef_list = [] self.initdef_list.append( {"def1": { "type": "vac", "species": "Se", "index": 0 }}) self.initdef_list.append({ "def1": { "type": "sub", "index": -1, "species": "W", "species_new": "Re" } }) self.initdef_list.append({ "def1": { "type": "sub", "index": -1, "species": "W", "species_new": "Re" }, "def2": { "type": "vac", "index": -1, "species": "Se", "index_offset_n1n2": -1 } }) self.initdef_list.append({ "def1": { "type": "ad-W", "index": [-1], "species": ["W"], "species_new": "Re", "shift_z": "3.36" } }) self.initdef_list.append({ "def1": { "type": "int-hex", "index": [0, 1, 0], "index_offset_n1": [1, 1, 0], "species": 3 * ["W"], "species_new": "Re" } }) self.create_defects() self.siteinds_list = [[16], [15], [15, 31], [[15]], [[4, 5, 0]]] self.defcoords_list = [[[0.083333, 0.166667, 0.571091]], [[0.75, 0.75, 0.5]], [[0.75, 0.75, 0.5], [0.833333, 0.916667, 0.571091]], [[0.75, 0.75, 0.643830]], [[0.166667, 0.083333, 0.5]]] self.natoms_list = [47, 48, 47, 49, 49]