def _preprocess(self, struct1, struct2, niggli=True): """ Rescales, finds the reduced structures (primitive and niggli), and finds fu, the supercell size to make struct1 comparable to s2 """ struct1 = Structure.from_sites(struct1) struct2 = Structure.from_sites(struct2) if niggli: struct1 = struct1.get_reduced_structure(reduction_algo="niggli") struct2 = struct2.get_reduced_structure(reduction_algo="niggli") #primitive cell transformation if self._primitive_cell: struct1 = struct1.get_primitive_structure() struct2 = struct2.get_primitive_structure() if self._supercell: fu, s1_supercell = self._get_supercell_size(struct1, struct2) else: fu, s1_supercell = 1, True mult = fu if s1_supercell else 1/fu #rescale lattice to same volume if self._scale: ratio = (struct2.volume / (struct1.volume * mult)) ** (1 / 6) nl1 = Lattice(struct1.lattice.matrix * ratio) struct1.modify_lattice(nl1) nl2 = Lattice(struct2.lattice.matrix / ratio) struct2.modify_lattice(nl2) return struct1, struct2, fu, s1_supercell
def get_aligned_lattices(slab_sub, slab_2d, max_area=200, max_mismatch=0.05, max_angle_diff=1, r1r2_tol=0.2): """ given the 2 slab structures and the alignment paramters, return slab structures with lattices that are aligned with respect to each other """ # get the matching substrate and 2D material lattices uv_substrate, uv_mat2d = get_matching_lattices(slab_sub, slab_2d, max_area=max_area, max_mismatch=max_mismatch, max_angle_diff=max_angle_diff, r1r2_tol=r1r2_tol) if not uv_substrate and not uv_mat2d: print("no matching u and v, trying adjusting the parameters") sys.exit() substrate = Structure.from_sites(slab_sub) mat2d = Structure.from_sites(slab_2d) # map the intial slabs to the newly found matching lattices substrate_latt = Lattice(np.array( [ uv_substrate[0][:], uv_substrate[1][:], substrate.lattice.matrix[2, :] ])) # to avoid numerical issues with find_mapping mat2d_fake_c = mat2d.lattice.matrix[2, :] / np.linalg.norm(mat2d.lattice.matrix[2, :]) * 5.0 mat2d_latt = Lattice(np.array( [ uv_mat2d[0][:], uv_mat2d[1][:], mat2d_fake_c ])) mat2d_latt_fake = Lattice(np.array( [ mat2d.lattice.matrix[0, :], mat2d.lattice.matrix[1, :], mat2d_fake_c ])) _, __, scell = substrate.lattice.find_mapping(substrate_latt, ltol=0.05, atol=1) scell[2] = np.array([0, 0, 1]) substrate.make_supercell(scell) _, __, scell = mat2d_latt_fake.find_mapping(mat2d_latt, ltol=0.05, atol=1) scell[2] = np.array([0, 0, 1]) mat2d.make_supercell(scell) # modify the substrate lattice so that the 2d material can be # grafted on top of it lmap = Lattice(np.array( [ substrate.lattice.matrix[0, :], substrate.lattice.matrix[1, :], mat2d.lattice.matrix[2, :] ])) mat2d.modify_lattice(lmap) return substrate, mat2d
def _get_structures(self, num_structs): structs = [] rs = subprocess.Popen(["makestr.x", "struct_enum.out", str(0), str(num_structs - 1)], stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) rs.communicate() if len(self.ordered_sites) > 0: original_latt = self.ordered_sites[0].lattice # Need to strip sites of site_properties, which would otherwise # result in an index error. Hence Structure is reconstructed in # the next step. ordered_structure = Structure( original_latt, [site.species_and_occu for site in self.ordered_sites], [site.frac_coords for site in self.ordered_sites]) inv_org_latt = np.linalg.inv(original_latt.matrix) for n in range(1, num_structs + 1): with open("vasp.{:06d}".format(n)) as f: data = f.read() data = re.sub("scale factor", "1", data) data = re.sub("(\d+)-(\d+)", r"\1 -\2", data) poscar = Poscar.from_string(data, self.index_species) sub_structure = poscar.structure #Enumeration may have resulted in a super lattice. We need to #find the mapping from the new lattice to the old lattice, and #perform supercell construction if necessary. new_latt = sub_structure.lattice sites = [] if len(self.ordered_sites) > 0: transformation = np.dot(new_latt.matrix, inv_org_latt) transformation = [[int(round(cell)) for cell in row] for row in transformation] logger.debug("Supercell matrix: {}".format(transformation)) s = Structure.from_sites(ordered_structure) s.make_supercell(transformation) sites.extend([site.to_unit_cell for site in s]) super_latt = sites[-1].lattice else: super_latt = new_latt for site in sub_structure: if site.specie.symbol != "X": # We exclude vacancies. sites.append(PeriodicSite(site.species_and_occu, site.frac_coords, super_latt).to_unit_cell) structs.append(Structure.from_sites(sorted(sites))) logger.debug("Read in a total of {} structures.".format(num_structs)) return structs
def get_s2_like_s1(self, struct1, struct2): """ Performs transformations on struct2 to put it in a basis similar to struct1 (without changing any of the inter-site distances) Args: struct1 (Structure): Reference structure struct2 (Structure): Structure to transform. Returns: A structure object similar to struct1, obtained by making a supercell, sorting, and translating struct2. """ trans = self.get_transformation(struct1, struct2) if trans is None: return None sc, t, mapping = trans temp = struct2.copy() temp.make_supercell(sc) temp.translate_sites(list(range(len(temp))), t) #translate sites to correct unit cell for i, j in enumerate(mapping[:len(struct1)]): if j is not None: vec = np.round(struct1[i].frac_coords - temp[j].frac_coords) temp.translate_sites(j, vec, to_unit_cell=False) return Structure.from_sites([temp.sites[i] for i in mapping if i is not None])
def _get_host(structure, species_to_remove): if species_to_remove: s = Structure.from_sites(structure) s.remove_species(species_to_remove) return s else: return structure
def get_sorted_structure(self): """ orders the structure according to increasing Z of the elements """ sites = sorted(self.sites, key=lambda site: site.specie.Z) structure = Structure.from_sites(sites) return AbiStructure(structure)
def best_first_ordering(self, structure, num_remove_dict): self.logger.debug("Performing best first ordering") starttime = time.time() self.logger.debug("Performing initial ewald sum...") ewaldsum = EwaldSummation(structure) self.logger.debug("Ewald sum took {} seconds.".format(time.time() - starttime)) starttime = time.time() ematrix = ewaldsum.total_energy_matrix to_delete = [] totalremovals = sum(num_remove_dict.values()) removed = {k: 0 for k in num_remove_dict.keys()} for i in range(totalremovals): maxindex = None maxe = float("-inf") maxindices = None for indices in num_remove_dict.keys(): if removed[indices] < num_remove_dict[indices]: for ind in indices: if ind not in to_delete: energy = sum(ematrix[:, ind]) + sum(ematrix[:, ind]) - ematrix[ind, ind] if energy > maxe: maxindex = ind maxe = energy maxindices = indices removed[maxindices] += 1 to_delete.append(maxindex) ematrix[:, maxindex] = 0 ematrix[maxindex, :] = 0 s = Structure.from_sites(structure.sites) s.remove_sites(to_delete) self.logger.debug("Minimizing Ewald took {} seconds.".format(time.time() - starttime)) return [{"energy": sum(sum(ematrix)), "structure": s.get_sorted_structure()}]
def test_magic(self): s = Structure.from_sites(self.structure) self.assertEqual(s, self.structure) self.assertNotEqual(s, None) s.apply_strain(0.5) self.assertNotEqual(s, self.structure) self.assertNotEqual(self.structure * 2, self.structure)
def apply_transformation(self, structure): s = Structure.from_sites(structure.sites) for i, sp in enumerate(self._species): s.insert(i, sp, self._coords[i], coords_are_cartesian=self._cartesian, validate_proximity=self._validate_proximity) return s.get_sorted_structure()
def __str__(self): """ Sorts a Structure (by fractional co-ordinate), and prints sites with magnetic information. This is useful over Structure.__str__ because sites are in a consistent order, which makes visual comparison between two identical Structures with different magnetic orderings easier. :return: """ frac_coords = self.structure.frac_coords sorted_indices = np.lexsort((frac_coords[:, 2], frac_coords[:, 1], frac_coords[:, 0])) s = Structure.from_sites([self.structure[idx] for idx in sorted_indices]) # adapted from Structure.__repr__ outs = ["Structure Summary", repr(s.lattice)] outs.append("Magmoms Sites") for site in s: if site.properties['magmom'] != 0: prefix = "{:+.2f} ".format(site.properties['magmom']) else: prefix = " " outs.append(prefix+repr(site)) return "\n".join(outs)
def get_s2_like_s1(self, struct1, struct2): """ Performs transformations on struct2 to put it in a basis similar to struct1 (without changing any of the inter-site distances) Args: struct1 (Structure): Reference structure struct2 (Structure): Structure to transform. Returns: A structure object similar to struct1, obtained by making a supercell, sorting, and translating struct2. """ if self._primitive_cell: raise ValueError("get_s2_like_s1 cannot be used with the primitive" " cell option") s1, s2, fu, s1_supercell = self._preprocess(struct1, struct2, False) ratio = fu if s1_supercell else 1/fu if s1_supercell and fu > 1: raise ValueError("Struct1 must be the supercell, " "not the other way around") temp = struct2.copy() if len(s1) * ratio >= len(s2): #s1 is superset match = self._strict_match(s1, s2, fu=fu, s1_supercell=False, use_rms=True, break_on_match=False) if match is None: return None temp.make_supercell(match[2]) temp.translate_sites(list(range(len(temp))), match[3], to_unit_cell=False) # translate sites to be in correct unit cell for i, j in enumerate(match[4]): vec = np.round(s1[j].frac_coords - temp[i].frac_coords) temp.translate_sites(i, vec, to_unit_cell=False) #invert the mapping, since it needs to be from s2 to s1 mapping = np.argsort(match[4]) else: #s2 is superset match = self._strict_match(s2, s1, fu=fu, s1_supercell=True, use_rms=True, break_on_match=False) if match is None: return None temp.make_supercell(match[2]) temp.translate_sites(list(range(len(temp))), -match[3], to_unit_cell=False) # translate sites to be in correct unit cell for i, j in enumerate(match[4]): vec = np.round(s1[i].frac_coords - temp[j].frac_coords) temp.translate_sites(j, vec, to_unit_cell=False) #add sites not included in the mapping not_included = list(range(len(temp))) for i in match[4]: not_included.remove(i) mapping = list(match[4]) + not_included return Structure.from_sites([temp.sites[i] for i in mapping])
def translate_sites(self, lattice): s = Structure(lattice, [self.central_subsite.specie] + [site.specie for site in self.peripheral_subsites], [self.central_subsite.site.coords] + [site.site.coords for site in self.peripheral_subsites], coords_are_cartesian=True) trans = TranslateSitesTransformation(range(len(s)), -(lattice.get_fractional_coords(s[0].coords))) new_s = trans.apply_transformation(s) trans2 = TranslateSitesTransformation(range(len(s)), (-0.5, -0.5, -0.5)) new_s = trans2.apply_transformation(Structure.from_sites(new_s.sites, to_unit_cell=True)) self.peripheral_subsites = [SubStructureSite.from_coords_and_specie(site.coords, site.specie) for site in new_s.sites[1:]] self.central_subsite = SubStructureSite.from_coords_and_specie(new_s[0].coords, new_s[0].specie)
def _process_species(self, structures): copied_structures = [] for s in structures: ss = Structure.from_sites(s) if self._ignored_species: ss.remove_species(self._ignored_species) copied_structures.append(ss) return copied_structures
def complete_ordering(self, structure, num_remove_dict): self.logger.debug("Performing complete ordering...") all_structures = [] from pymatgen.symmetry.finder import SymmetryFinder symprec = 0.2 s = SymmetryFinder(structure, symprec=symprec) self.logger.debug("Symmetry of structure is determined to be {}." .format(s.get_spacegroup_symbol())) sg = s.get_spacegroup() tested_sites = [] starttime = time.time() self.logger.debug("Performing initial ewald sum...") ewaldsum = EwaldSummation(structure) self.logger.debug("Ewald sum took {} seconds." .format(time.time() - starttime)) starttime = time.time() allcombis = [] for ind, num in num_remove_dict.items(): allcombis.append(itertools.combinations(ind, num)) count = 0 for allindices in itertools.product(*allcombis): sites_to_remove = [] indices_list = [] for indices in allindices: sites_to_remove.extend([structure[i] for i in indices]) indices_list.extend(indices) s_new = Structure.from_sites(structure.sites) s_new.remove_sites(indices_list) energy = ewaldsum.compute_partial_energy(indices_list) already_tested = False for i, tsites in enumerate(tested_sites): tenergy = all_structures[i]["energy"] if abs((energy - tenergy) / len(s_new)) < 1e-5 and \ sg.are_symmetrically_equivalent(sites_to_remove, tsites, symm_prec=symprec): already_tested = True if not already_tested: tested_sites.append(sites_to_remove) all_structures.append({"structure": s_new, "energy": energy}) count += 1 if count % 10 == 0: timenow = time.time() self.logger.debug("{} structures, {:.2f} seconds." .format(count, timenow - starttime)) self.logger.debug("Average time per combi = {} seconds" .format((timenow - starttime) / count)) self.logger.debug("{} symmetrically distinct structures found." .format(len(all_structures))) self.logger.debug("Total symmetrically distinct structures found = {}" .format(len(all_structures))) all_structures = sorted(all_structures, key=lambda s: s["energy"]) return all_structures
def apply_transformation(self, structure, return_ranked_list=False): #Make a mutable structure first mods = Structure.from_sites(structure) for sp, spin in self.mag_species_spin.items(): sp = get_el_sp(sp) oxi_state = getattr(sp, "oxi_state", 0) if spin: up = Specie(sp.symbol, oxi_state, {"spin": abs(spin)}) down = Specie(sp.symbol, oxi_state, {"spin": -abs(spin)}) mods.replace_species( {sp: Composition({up: self.order_parameter, down: 1 - self.order_parameter})}) else: mods.replace_species( {sp: Specie(sp.symbol, oxi_state, {"spin": spin})}) if mods.is_ordered: return [mods] if return_ranked_list > 1 else mods enum_args = self.enum_kwargs enum_args["min_cell_size"] = max(int( MagOrderingTransformation.determine_min_cell( structure, self.mag_species_spin, self.order_parameter)), enum_args.get("min_cell_size")) max_cell = self.enum_kwargs.get('max_cell_size') if max_cell: if enum_args["min_cell_size"] > max_cell: raise ValueError('Specified max cell size is smaller' ' than the minimum enumerable cell size') else: enum_args["max_cell_size"] = enum_args["min_cell_size"] t = EnumerateStructureTransformation(**enum_args) alls = t.apply_transformation(mods, return_ranked_list=return_ranked_list) try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if num_to_return == 1 or not return_ranked_list: return alls[0]["structure"] if num_to_return else alls m = StructureMatcher(comparator=SpinComparator()) grouped = m.group_structures([d["structure"] for d in alls]) alls = [{"structure": g[0], "energy": self.emodel.get_energy(g[0])} for g in grouped] self._all_structures = sorted(alls, key=lambda d: d["energy"]) return self._all_structures[0:num_to_return]
def get_structure_with_nodes(self): """ Get the modified structure with the voronoi nodes inserted. The species is set as a DummySpecie X. """ new_s = Structure.from_sites(self.structure) for v in self.vnodes: new_s.append("X", v.frac_coords) return new_s
def get_s2_like_s1(self, struct1, struct2, include_ignored_species=True): """ Performs transformations on struct2 to put it in a basis similar to struct1 (without changing any of the inter-site distances) Args: struct1 (Structure): Reference structure struct2 (Structure): Structure to transform. include_ignored_species (bool): Defaults to True, the ignored_species is also transformed to the struct1 lattice orientation, though obviously there is no direct matching to existing sites. Returns: A structure object similar to struct1, obtained by making a supercell, sorting, and translating struct2. """ s1, s2 = self._process_species([struct1, struct2]) trans = self.get_transformation(s1, s2) if trans is None: return None sc, t, mapping = trans sites = [site for site in s2] # Append the ignored sites at the end. sites.extend([site for site in struct2 if site not in s2]) temp = Structure.from_sites(sites) temp.make_supercell(sc) temp.translate_sites(list(range(len(temp))), t) # translate sites to correct unit cell for i, j in enumerate(mapping[:len(s1)]): if j is not None: vec = np.round(struct1[i].frac_coords - temp[j].frac_coords) temp.translate_sites(j, vec, to_unit_cell=False) sites = [temp.sites[i] for i in mapping if i is not None] if include_ignored_species: start = int(round(len(temp) / len(struct2) * len(s2))) sites.extend(temp.sites[start:]) return Structure.from_sites(sites)
def apply_transformation(self, structure): species_map = {} for k, v in self._species_map.items(): if isinstance(v, dict): value = {smart_element_or_specie(x): y for x, y in v.items()} else: value = smart_element_or_specie(v) species_map[smart_element_or_specie(k)] = value s = Structure.from_sites(structure.sites) s.replace_species(species_map) return s
def _process_species(self, structures): copied_structures = [] for s in structures: # We need the copies to be actual Structure to work properly, not # subclasses. So do type(s) == Structure. ss = s.copy() if type(s) == Structure else \ Structure.from_sites(s) if self._ignored_species: ss.remove_species(self._ignored_species) copied_structures.append(ss) return copied_structures
def enumerate_ordering(self, structure): # Generate the disordered structure first. s = Structure.from_sites(structure.sites) for indices, fraction in zip(self._indices, self._fractions): for ind in indices: new_sp = {sp: occu * fraction for sp, occu in structure[ind].species_and_occu.items()} s[ind] = new_sp # Perform enumeration from pymatgen.transformations.advanced_transformations import EnumerateStructureTransformation trans = EnumerateStructureTransformation() return trans.apply_transformation(s, 10000)
def get_percolated_node_edge(origin_struct, node_struct, rad_dict, probe_rad): """ The function "get_voronoi_node_edge" gave all node information, but we need the percolated nodes. This function conbine with function "get_voronoi_percolate_nodes" to delete the isolated nodes and correct the neighbor information in the Structure from get_voronoi_node_edge. """ _, vor_accessible_node_struct, _, _ = get_voronoi_percolate_nodes( origin_struct, rad_dict, probe_rad) # print vor_accessible_node_struct.lattice == node_struct.lattice # print vor_accessible_node_struct # node_struct_copy = copy.deepcopy(node_struct) unaccess_list = [] access_list = [] for i_index, i in enumerate(node_struct): match = 0 for j_index, j in enumerate(vor_accessible_node_struct): if i.is_periodic_image(j, tolerance=1e-3, check_lattice=True): match = 1 break if match == 0: unaccess_list.append(i_index) else: access_list.append(i_index) new_id = range(len(access_list)) old_new_corr = {} for i_index, i in enumerate(access_list): old_new_corr[i] = new_id[i_index] access_node_site_list = [copy.deepcopy(site) for site_index, site in enumerate(node_struct) \ if site_index in access_list] final_access_node_site_list = [] for i_index, i in enumerate(access_node_site_list): neighbor_nodes = [copy.deepcopy(neighbor) for neighbor in i.properties['neighbor_nodes'] \ if neighbor[0] in access_list] for j_index, j in enumerate(neighbor_nodes): new_id = old_new_corr[j[0]] neighbor_nodes[j_index] = copy.deepcopy([new_id] + j[1:]) properties = i.properties properties['neighbor_nodes'] = neighbor_nodes node_site = PeriodicSite(i.species_string, i.coords, i.lattice, to_unit_cell=False, coords_are_cartesian=True, properties=properties) final_access_node_site_list.append(node_site) if final_access_node_site_list: new_node_edge = Structure.from_sites(final_access_node_site_list, validate_proximity=False, to_unit_cell=False) else: new_node_edge = None return new_node_edge
def plot_images(self, outfile): """ Generates a POSCAR with the calculated diffusion path with respect to the first endpoint. :param outfile: Output file for the POSCAR """ sum_struct = self.__images[0].sites for image in self.__images: for site_i in self.__relax_sites: sum_struct.append(PeriodicSite(image.sites[site_i].specie, image.sites[site_i].frac_coords, self.__images[0].lattice, to_unit_cell=True, coords_are_cartesian=False)) sum_struct = Structure.from_sites(sum_struct, validate_proximity=False) p = Poscar(sum_struct) p.write_file(outfile)
def from_dict(cls, d): lattice = Lattice.from_dict(d["lattice"]) sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]] s = Structure.from_sites(sites) return Slab( lattice=lattice, species=s.species_and_occu, coords=s.frac_coords, miller_index=d["miller_index"], oriented_unit_cell=Structure.from_dict(d["oriented_unit_cell"]), shift=d["shift"], scale_factor=d["scale_factor"], site_properties=s.site_properties, energy=d["energy"] )
def add_vacuum_padding(slab, vacuum, hkl=[0,0,1]): """ add vacuum spacing to the given structure Args: slab: sructure/slab object to be padded vacuum: in angstroms hkl: miller index Returns: Structure object """ min_z = np.min([fcoord[2] for fcoord in slab.frac_coords]) slab.translate_sites(list(range(len(slab))), [0, 0, -min_z]) a, b, c = slab.lattice.matrix z = [coord[2] for coord in slab.cart_coords] zmax = np.max(z) zmin = np.min(z) thickness = zmax - zmin new_c = c / np.linalg.norm(c) * (thickness+vacuum) new_lattice = Lattice(np.array([a,b,new_c])) new_sites = [] for site in slab: new_sites.append(PeriodicSite(site.species_and_occu, site.coords, new_lattice, properties=site.properties, coords_are_cartesian=True)) new_struct = Structure.from_sites(new_sites) #center the slab avg_z = np.average([fcoord[2] for fcoord in new_struct.frac_coords]) new_struct.translate_sites(list(range(len(new_struct))), [0, 0, 0.5 - avg_z]) return Slab(new_struct.lattice, new_struct.species_and_occu, new_struct.frac_coords, hkl, Structure.from_sites(new_struct,to_unit_cell=True), shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=new_struct.site_properties)
def test_structures(self): import itertools import numpy as np from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice from pymatgen.core.sites import PeriodicSite from supercellor.supercell import make_supercell NTESTS = 100 #100 RADIUS = 100.0 EPS = 1e-3 DIAG = 4 NOISE_R = 2 tests_run = 0 np.random.seed(10) while (tests_run < NTESTS): R = DIAG * np.eye(3, dtype=int) - np.random.randint( NOISE_R, size=(3, 3)) + NOISE_R #np.random.randint(10, size=(3,3)) -5 S = RADIUS * np.eye(3) try: P = np.dot(np.linalg.inv(R), S) except np.linalg.LinAlgError: continue lattice = Lattice(P) if lattice.volume < 0.01 * RADIUS**3 / DIAG: print('skipping', lattice.volume) continue sites = [] try: for pos in itertools.product([-0.5, 0.5], repeat=3): sites.append( PeriodicSite("H", pos, lattice, coords_are_cartesian=True)) except np.linalg.LinAlgError: continue structure = Structure.from_sites(sites) supercell, scale = make_supercell(structure, distance=RADIUS - EPS, verbosity=0, wrap=True, standardize=True, do_niggli_first=True) self.assertTrue( np.sum(np.abs(supercell._lattice.matrix - S)) < EPS) tests_run += 1
def test_niggli(self): from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice from pymatgen.core.sites import PeriodicSite from supercellor.supercell import make_supercell import numpy as np import itertools np.random.seed(4) DIST = 0.99 lattice = Lattice(1 * np.eye(3) + 0.5 * (np.random.random((3, 3)) - 0.5)) sites = [] for pos in itertools.product([-0.5, 0.5], repeat=3): sites.append( PeriodicSite("H", pos, lattice, coords_are_cartesian=True)) structure = Structure.from_sites(sites) supercell1, scale1 = make_supercell(structure, distance=DIST, verbosity=1, wrap=True, standardize=True, do_niggli_first=False) supercell2, scale2 = make_supercell(structure, distance=DIST, verbosity=1, wrap=True, standardize=True, do_niggli_first=True) sa = SpacegroupAnalyzer(supercell1, symprec=1e-21, angle_tolerance=-1) supercell_refine = sa.get_refined_structure() for i in range(3): for j in range(3): self.assertTrue( abs(supercell1._lattice.matrix[i, j] - supercell2._lattice.matrix[i, j]) < 1e-1, '{} != {} for i,j={},{}'.format( supercell1._lattice.matrix[i, j], supercell2._lattice.matrix[i, j], i, j)) self.assertTrue( abs(supercell1._lattice.matrix[i, j] - supercell_refine._lattice.matrix[i, j]) < 1e-1, '{} != {} for i,j={},{}'.format( supercell1._lattice.matrix[i, j], supercell_refine._lattice.matrix[i, j], i, j))
def get_structure_with_only_magnetic_atoms(self, make_primitive=True): """ Returns a Structure with only magnetic atoms present. :return: Structure """ sites = [site for site in self.structure if abs(site.properties["magmom"]) > 0] structure = Structure.from_sites(sites) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) return structure
def fast_ordering(self, structure, num_remove_dict, num_to_return=1): """ This method uses the matrix form of ewaldsum to calculate the ewald sums of the potential structures. This is on the order of 4 orders of magnitude faster when there are large numbers of permutations to consider. There are further optimizations possible (doing a smarter search of permutations for example), but this wont make a difference until the number of permutations is on the order of 30,000. """ self.logger.debug("Performing fast ordering") starttime = time.time() self.logger.debug("Performing initial ewald sum...") ewaldmatrix = EwaldSummation(structure).total_energy_matrix self.logger.debug("Ewald sum took {} seconds." .format(time.time() - starttime)) starttime = time.time() m_list = [] for indices, num in num_remove_dict.items(): m_list.append([0, num, list(indices), None]) self.logger.debug("Calling EwaldMinimizer...") minimizer = EwaldMinimizer(ewaldmatrix, m_list, num_to_return, PartialRemoveSitesTransformation.ALGO_FAST) self.logger.debug("Minimizing Ewald took {} seconds." .format(time.time() - starttime)) all_structures = [] lowest_energy = minimizer.output_lists[0][0] num_atoms = sum(structure.composition.values()) for output in minimizer.output_lists: s = Structure.from_sites(structure.sites) del_indices = [] for manipulation in output[1]: if manipulation[1] is None: del_indices.append(manipulation[0]) else: s.replace(manipulation[0], manipulation[1]) s.remove_sites(del_indices) struct = s.get_sorted_structure() all_structures.append( {"energy": output[0], "energy_above_minimum": (output[0] - lowest_energy) / num_atoms, "structure": struct}) return all_structures
def test_equivalence_fort_py(self): from datetime import datetime import itertools import numpy as np from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice from pymatgen.core.sites import PeriodicSite from supercellor.supercell import make_supercell NTESTS = 10 #100 VERBOSITY = 0 for radius in np.arange(1.0, 3.1, 1.0): #RADIUS = 5.0 tests_run = 0 np.random.seed(10) # reinitialize seed! timings_f = 0.0 timings_p = 0.0 while (tests_run < NTESTS): P = np.eye(3)+ 0.1*(np.random.random((3,3)) -0.5) lattice = Lattice(P) if lattice.volume < 0.1: print('skipping', lattice.volume) continue sites = [] try: for pos in itertools.product([-0.5,0.5], repeat=3): sites.append(PeriodicSite("H", pos, lattice, coords_are_cartesian=True)) except np.linalg.LinAlgError: continue structure = Structure.from_sites(sites) n = datetime.now() supercell_p, scale_p = make_supercell(structure, distance=radius, verbosity=VERBOSITY, wrap=True, standardize=True, do_niggli_first=True, implementation='pyth') timings_p += (datetime.now()-n).microseconds n = datetime.now() supercell_f, scale_f = make_supercell(structure, distance=radius, verbosity=VERBOSITY, wrap=True, standardize=True, do_niggli_first=True, implementation='fort') timings_f += (datetime.now()-n).microseconds self.assertTrue(np.sum((scale_p-scale_f)**2) ==0) self.assertTrue(np.sum((supercell_f._lattice.matrix -supercell_p._lattice.matrix)**2) < 1e-6) #~ self.assertTrue(np.sum(np.abs(supercell._lattice.matrix - S))< EPS) tests_run += 1 print('Avg timing fortran impl rad={} {:.2e}'.format(radius, 1e-6*timings_f/ tests_run)) print('Avg timing python impl rad={} {:.2e}'.format(radius, 1e-6*timings_p/ tests_run))
def vtk(self): if StructureVis is None: raise NotImplementedError("vtk must be present to view.") lattice = self.structure.lattice vis = StructureVis() vis.set_structure(Structure.from_sites(self.structure)) for v in self.vnodes: vis.add_site(PeriodicSite("K", v.frac_coords, lattice)) vis.add_polyhedron( [PeriodicSite("S", c, lattice, coords_are_cartesian=True) for c in v.polyhedron_coords], PeriodicSite("Na", v.frac_coords, lattice), color="element", draw_edges=True, edges_color=(0, 0, 0)) vis.show()
def get_structure_with_only_magnetic_atoms(self, make_primitive=True): """ Returns a Structure with only magnetic atoms present. :return: Structure """ sites = [site for site in self.structure if abs(site.properties['magmom']) > 0] structure = Structure.from_sites(sites) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) return structure
def write_structures(structures, file_name, format): import tempfile import zipfile from os.path import basename, join import os format_name, Writer = format file_type = format_name if len(structures) > 1: result_archive = zipfile.ZipFile(join(os.getcwd(), basename(file_name) + '.zip'), mode='w') for name, structure in structures.items(): sorted_sites = list( sorted(structure.sites.copy(), key=lambda site: site.specie.symbol)) sorted_structure = Structure.from_sites(sorted_sites) with tempfile.NamedTemporaryFile() as tmpfile: Writer(sorted_structure).write_file(tmpfile.name) result_archive.write(tmpfile.name, arcname='{}.{}'.format(name, file_type)) result_archive.close() write_message('Archive file: {0}'.format( join(os.getcwd(), basename(file_name) + '.zip')), level=DEBUG) else: #There is just one file write only that one structure_key = list(structures.keys())[0] sorted_sites = list( sorted(structures[structure_key].sites.copy(), key=lambda site: site.specie.symbol)) sorted_structure = Structure.from_sites(sorted_sites) p = Writer(sorted_structure) fname = join(os.getcwd(), '{}.{}'.format(structure_key, file_type)) p.write_file(fname) write_message('Output file: {0}'.format(fname), level=DEBUG)
def enumerate_ordering(self, structure): # Generate the disordered structure first. s = Structure.from_sites(structure.sites) for indices, fraction in zip(self._indices, self._fractions): for ind in indices: new_sp = { sp: occu * fraction for sp, occu in structure[ind].species_and_occu.items() } s[ind] = new_sp # Perform enumeration from pymatgen.transformations.advanced_transformations import \ EnumerateStructureTransformation trans = EnumerateStructureTransformation() return trans.apply_transformation(s, 10000)
def get_sorted_structure(self, key=None, reverse=False) -> Structure: """ Get a sorted structure for the interface. The parameters have the same meaning as in list.sort. By default, sites are sorted by the electronegativity of the species. Args: key: Specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The default value is None (compare the elements directly). reverse (bool): If set to True, then the list elements are sorted as if each comparison were reversed. """ struct_copy = Structure.from_sites(self) struct_copy.sort(key=key, reverse=reverse) return struct_copy
def analyze_symmetry(self, tol): s = Structure.from_sites(self.framework) site_to_vindex = {} for i, v in enumerate(self.vnodes): s.append("Li", v.frac_coords) site_to_vindex[s[-1]] = i print(len(s)) finder = SpacegroupAnalyzer(s, tol) print(finder.get_space_group_operations()) symm_structure = finder.get_symmetrized_structure() print(len(symm_structure.equivalent_sites)) return [[site_to_vindex[site] for site in sites] for sites in symm_structure.equivalent_sites if sites[0].specie.symbol == "Li"]
def test_offset_vector(self): interface = self.ib.interfaces[0] init_lattice = interface.lattice.matrix.copy() self.assertArrayAlmostEqual(interface.offset_vector, np.array([0, 0, 2.5])) init_film = interface.film init_sub = interface.substrate tst = Structure.from_sites(interface.sites) interface.z_shift += 1 self.assertArrayAlmostEqual(interface.offset_vector, np.array([0, 0, 3.5])) tdm, idm = tst.distance_matrix, interface.distance_matrix diff = tdm - idm assert (tdm <= idm + 1e-10).all() assert (tdm + 0.5 < idm).any() self.assertArrayAlmostEqual(init_film.distance_matrix, interface.film.distance_matrix) self.assertArrayAlmostEqual(init_sub.distance_matrix, interface.substrate.distance_matrix) interface.z_shift -= 1 self.assertArrayAlmostEqual(interface.offset_vector, np.array([0, 0, 2.5])) idm = interface.distance_matrix assert (np.abs(tdm - idm) < 1e-10).all() interface.ab_shift += np.array([0.2, 0.2]) self.assertArrayAlmostEqual(interface.ab_shift, np.array([0.2, 0.2])) idm = interface.distance_matrix assert (np.abs(tdm - idm) > 0.9).any() self.assertArrayAlmostEqual(init_lattice, interface.lattice.matrix) self.assertArrayAlmostEqual(init_film.distance_matrix, interface.film.distance_matrix) self.assertArrayAlmostEqual(init_sub.distance_matrix, interface.substrate.distance_matrix) interface.ab_shift -= np.array([0.2, 0.2]) self.assertArrayAlmostEqual(interface.offset_vector, np.array([0, 0, 2.5])) idm = interface.distance_matrix assert (np.abs(tdm - idm) < 1e-10).all() self.assertArrayAlmostEqual(init_film.distance_matrix, interface.film.distance_matrix) self.assertArrayAlmostEqual(init_sub.distance_matrix, interface.substrate.distance_matrix)
def write_input(self, output_dir, make_dir_if_not_present=True, write_cif=False, write_path_cif=False, write_endpoint_inputs=False): """ NEB inputs has a special directory structure where inputs are in 00, 01, 02, .... Args: output_dir (str): Directory to output the VASP input files make_dir_if_not_present (bool): Set to True if you want the directory (and the whole path) to be created if it is not present. write_cif (bool): If true, writes a cif along with each POSCAR. write_path_cif (bool): If true, writes a cif for each image. write_endpoint_inputs (bool): If true, writes input files for running endpoint calculations. """ if make_dir_if_not_present and not os.path.exists(output_dir): os.makedirs(output_dir) self.incar.write_file(os.path.join(output_dir, 'INCAR')) self.kpoints.write_file(os.path.join(output_dir, 'KPOINTS')) self.potcar.write_file(os.path.join(output_dir, 'POTCAR')) for i, p in enumerate(self.poscars): d = os.path.join(output_dir, str(i).zfill(2)) if not os.path.exists(d): os.makedirs(d) p.write_file(os.path.join(d, 'POSCAR')) if write_cif: p.structure.to(filename=os.path.join(d, '{}.cif'.format(i))) if write_endpoint_inputs: end_point_param = MPRelaxSet( self.structures[0], user_incar_settings=self.user_incar_settings) for image in ['00', str(len(self.structures) - 1).zfill(2)]: end_point_param.incar.write_file(os.path.join(output_dir, image, 'INCAR')) end_point_param.kpoints.write_file(os.path.join(output_dir, image, 'KPOINTS')) end_point_param.potcar.write_file(os.path.join(output_dir, image, 'POTCAR')) if write_path_cif: sites = set() l = self.structures[0].lattice for site in chain(*(s.sites for s in self.structures)): sites.add(PeriodicSite(site.species_and_occu, site.frac_coords, l)) path = Structure.from_sites(sorted(sites)) path.to(filename=os.path.join(output_dir, 'path.cif'))
def get_s2_like_s1(self, struct1, struct2): """ Performs transformations on struct2 to put it in a basis similar to struct1 (without changing any of the inter-site distances) """ if self._primitive_cell: raise ValueError("get_s2_like_s1 cannot be used with the primitive" " cell option") s1, s2, fu, s1_supercell = self._preprocess(struct1, struct2, False) ratio = fu if s1_supercell else 1/fu if s1_supercell and fu > 1: raise ValueError("Struct1 must be the supercell, " "not the other way around") if len(s1) * ratio >= len(s2): #s1 is superset match = self._match(s1, s2, fu=fu, s1_supercell=s1_supercell, use_rms=True, break_on_match=False) else: #s2 is superset match = self._match(s2, s1, fu=fu, s1_supercell=(not s1_supercell), use_rms=True, break_on_match=False) if match is None: return None temp = struct2.copy() temp.make_supercell(match[2]) if len(struct1) >= len(temp): #invert the mapping, since it needs to be from s2 to s1 mapping = np.argsort(match[4]) tvec = match[3] else: #add sites not included in the mapping not_included = range(len(temp)) for i in match[4]: not_included.remove(i) mapping = list(match[4]) + not_included tvec = -match[3] temp.translate_sites(range(len(temp)), tvec) return Structure.from_sites([temp.sites[i] for i in mapping])
def get_oxi_state_decorated_structure(self, structure): """ Get an oxidation state decorated structure. This currently works only for ordered structures only. Args: structure: Structure to analyze Returns: A modified structure that is oxidation state decorated. Raises: ValueError if the valences cannot be determined. """ valences = self.get_valences(structure) s = Structure.from_sites(structure.sites) s.add_oxidation_state_by_site(valences) return s
def get_structure_with_only_magnetic_atoms(self, make_primitive: bool = True) -> Structure: """Returns a Structure with only magnetic atoms present. Args: make_primitive: Whether to make structure primitive after removing non-magnetic atoms (Default value = True) Returns: Structure """ sites = [site for site in self.structure if abs(site.properties["magmom"]) > 0] structure = Structure.from_sites(sites) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) return structure
def label_termination(slab: Structure) -> str: """Labels the slab surface termination""" frac_coords = slab.frac_coords n = len(frac_coords) if n == 1: # Clustering does not work when there is only one data point. form = slab.composition.reduced_formula sp_symbol = SpacegroupAnalyzer(slab, symprec=0.1).get_space_group_symbol() return f"{form}_{sp_symbol}_{len(slab)}" dist_matrix = np.zeros((n, n)) h = slab.lattice.c # Projection of c lattice vector in # direction of surface normal. for i, j in combinations(list(range(n)), 2): if i != j: cdist = frac_coords[i][2] - frac_coords[j][2] cdist = abs(cdist - round(cdist)) * h dist_matrix[i, j] = cdist dist_matrix[j, i] = cdist condensed_m = squareform(dist_matrix) z = linkage(condensed_m) clusters = fcluster(z, 0.25, criterion="distance") clustered_sites: dict[int, list[Site]] = {c: [] for c in clusters} for i, c in enumerate(clusters): clustered_sites[c].append(slab[i]) plane_heights = { np.average(np.mod([s.frac_coords[2] for s in sites], 1)): c for c, sites in clustered_sites.items() } top_plane_cluster = sorted(plane_heights.items(), key=lambda x: x[0])[-1][1] top_plane_sites = clustered_sites[top_plane_cluster] top_plane = Structure.from_sites(top_plane_sites) sp_symbol = SpacegroupAnalyzer(top_plane, symprec=0.1).get_space_group_symbol() form = top_plane.composition.reduced_formula return f"{form}_{sp_symbol}_{len(top_plane)}"
def __init__(self, structure, scaling_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1))): """ Create a supercell. Args: structure: pymatgen.core.structure Structure object. scaling_matrix: a matrix of transforming the lattice vectors. Defaults to the identity matrix. Has to be all integers. e.g., [[2,1,0],[0,3,0],[0,0,1]] generates a new structure with lattice vectors a' = 2a + b, b' = 3b, c' = c where a, b, and c are the lattice vectors of the original structure. """ self._original_structure = structure old_lattice = structure.lattice scale_matrix = np.array(scaling_matrix) new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix)) new_sites = [] def range_vec(i): return range( max(scale_matrix[:][:, i]) - min(scale_matrix[:][:, i]) + 1) for site in structure.sites: for (i, j, k) in itertools.product(range_vec(0), range_vec(1), range_vec(2)): fcoords = site.frac_coords + np.array([i, j, k]) coords = old_lattice.get_cartesian_coords(fcoords) new_coords = new_lattice.get_fractional_coords(coords) new_site = PeriodicSite(site.species_and_occu, new_coords, new_lattice, properties=site.properties) contains_site = False for s in new_sites: if s.is_periodic_image(new_site): contains_site = True break if not contains_site: new_sites.append(new_site) self._modified_structure = Structure.from_sites(new_sites)
def get_oxi_state_decorated_structure(self, structure): """ Get an oxidation state decorated structure. This currently works only for ordered structures only. Args: structure: Structure to analyze Returns: A modified structure that is oxidation state decorated. Raises: A ValueError is the valences cannot be determined. """ valences = self.get_valences(structure) s = Structure.from_sites(structure.sites) s.add_oxidation_state_by_site(valences) return s
def get_sorted_structure(self, key=None, reverse=False): """ Get a sorted copy of the structure. The parameters have the same meaning as in list.sort. By default, sites are sorted by the electronegativity of the species. Note that Slab has to override this because of the different __init__ args. Args: key: Specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The default value is None (compare the elements directly). reverse (bool): If set to True, then the list elements are sorted as if each comparison were reversed. """ sites = sorted(self, key=key, reverse=reverse) s = Structure.from_sites(sites) return Slab(s.lattice, s.species_and_occu, s.frac_coords, self.miller_index, self.oriented_unit_cell, self.shift, self.scale_factor, site_properties=s.site_properties)
def get_reconstructed_structure(components: List[Component], simplify_molecules: bool = True) -> Structure: """Reconstructs a structure from a list of components. Has the option to simplify molecular components into a single site positioned at the centre of mass of the molecular. If using this option, the components must have been generated with ``inc_molecule_graph=True``. Args: components: A list of structure components, generated using :obj:`pymatgen.analysis.dimensionality.get_structure_components`, with ``inc_molecule_graph=True``. simplify_molecules: Whether to simplify the molecular components into a single site positioned at the centre of mass of the molecule. Returns: The reconstructed structure. """ mol_sites = [] other_sites = [] if simplify_molecules: mol_components, components = filter_molecular_components(components) if mol_components: lattice = mol_components[0]["structure_graph"].structure.lattice mol_sites = [ PeriodicSite( c["structure_graph"].structure[0].specie, c["molecule_graph"].molecule.center_of_mass, lattice, coords_are_cartesian=True, ) for c in mol_components ] if components: other_sites = [ site for c in components for site in c["structure_graph"].structure ] return Structure.from_sites(other_sites + mol_sites)
def test_hnf_dmpi(self): import json, numpy as np, itertools, os from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice from pymatgen.core.sites import PeriodicSite from supercellor.supercell import make_supercell np.random.seed(50) N = 1 RMIN = 2 RMAX = 3 for trial in range(N): #~ for implementation in ('pyth', ): for implementation in ('fort', ): lattice = Lattice(1.0 * np.eye(3) + 0.2 * (np.random.random((3, 3)) - 0.5)) sites = [] for pos in itertools.product([-0.5, 0.5], repeat=3): sites.append( PeriodicSite("H", pos, lattice, coords_are_cartesian=True)) structure = Structure.from_sites(sites) for rad in range(RMIN, RMAX): supercell1, scale1 = make_supercell( structure, distance=rad, method='hnf', implementation=implementation, verbosity=0, wrap=True, standardize=False, do_niggli_first=False) reduced_supercell1 = supercell1.get_reduced_structure( reduction_algo=u'LLL') for dim in range(3): self.assertTrue( np.linalg.norm(reduced_supercell1._lattice. matrix[dim]) >= rad)
def slab_from_file(hkl, filename): """ reads in structure from the file and returns slab object. useful for reading in 2d/substrate structures from file. Args: hkl: miller index of the slab in the input file. filename: structure file in any format supported by pymatgen Returns: Slab object """ slab_input = Structure.from_file(filename) return Slab(slab_input.lattice, slab_input.species_and_occu, slab_input.frac_coords, hkl, Structure.from_sites(slab_input, to_unit_cell=True), shift=0, scale_factor=np.eye(3, dtype=np.int), site_properties=slab_input.site_properties)
def best_first_ordering(self, structure, num_remove_dict): self.logger.debug("Performing best first ordering") starttime = time.time() self.logger.debug("Performing initial ewald sum...") ewaldsum = EwaldSummation(structure) self.logger.debug("Ewald sum took {} seconds.".format(time.time() - starttime)) starttime = time.time() ematrix = ewaldsum.total_energy_matrix to_delete = [] totalremovals = sum(num_remove_dict.values()) removed = {k: 0 for k in num_remove_dict.keys()} for i in xrange(totalremovals): maxindex = None maxe = float("-inf") maxindices = None for indices in num_remove_dict.keys(): if removed[indices] < num_remove_dict[indices]: for ind in indices: if ind not in to_delete: energy = sum(ematrix[:, ind]) + \ sum(ematrix[:, ind]) - ematrix[ind, ind] if energy > maxe: maxindex = ind maxe = energy maxindices = indices removed[maxindices] += 1 to_delete.append(maxindex) ematrix[:, maxindex] = 0 ematrix[maxindex, :] = 0 s = Structure.from_sites(structure.sites) s.remove_sites(to_delete) self.logger.debug( "Minimizing Ewald took {} seconds.".format(time.time() - starttime)) return [{ "energy": sum(sum(ematrix)), "structure": s.get_sorted_structure() }]
def from_dict(cls, d): """ :param d: dict :return: Creates slab from dict. """ lattice = Lattice.from_dict(d["lattice"]) sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]] s = Structure.from_sites(sites) optional = dict( in_plane_offset=d.get("in_plane_offset"), gap=d.get("gap"), vacuum_over_film=d.get("vacuum_over_film"), interface_properties=d.get("interface_properties"), ) return Interface( lattice=lattice, species=s.species_and_occu, coords=s.frac_coords, site_properties=s.site_properties, **{k: v for k, v in optional.items() if v is not None}, )
def __str__(self): """ Sorts a Structure (by fractional co-ordinate), and prints sites with magnetic information. This is useful over Structure.__str__ because sites are in a consistent order, which makes visual comparison between two identical Structures with different magnetic orderings easier. """ frac_coords = self.structure.frac_coords sorted_indices = np.lexsort((frac_coords[:, 2], frac_coords[:, 1], frac_coords[:, 0])) s = Structure.from_sites([self.structure[idx] for idx in sorted_indices]) # adapted from Structure.__repr__ outs = ["Structure Summary", repr(s.lattice)] outs.append("Magmoms Sites") for site in s: if site.properties["magmom"] != 0: prefix = "{:+.2f} ".format(site.properties["magmom"]) else: prefix = " " outs.append(prefix + repr(site)) return "\n".join(outs)
def from_dict(cls, d): """ :param d: Dict representation :return: Interface """ lattice = Lattice.from_dict(d["lattice"]) sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]] s = Structure.from_sites(sites) return Interface( lattice=lattice, species=s.species_and_occu, coords=s.frac_coords, sub_plane=d["sub_plane"], film_plane=d["film_plane"], sub_init_cell=d["sub_init_cell"], film_init_cell=d["film_init_cell"], modified_sub_structure=d["modified_sub_structure"], modified_film_structure=d["modified_film_structure"], strained_sub_structure=d["strained_sub_structure"], strained_film_structure=d["strained_film_structure"], site_properties=s.site_properties, init_inplane_shift=d["init_inplane_shift"], )
def copy(self): return Structure.from_sites(self)
def vac_antisite_def_struct_gen(mpid, mapi_key, cellmax, struct_file=None): if not mpid and not struct_file: print ("============\nERROR: Provide an mpid\n============") return # Get primitive structure from the Materials Project DB if not struct_file: if not mapi_key: with MPRester() as mp: struct = mp.get_structure_by_material_id(mpid) else: with MPRester(mapi_key) as mp: struct = mp.get_structure_by_material_id(mpid) else: struct = Structure.from_file(struct_file) sga = SpacegroupAnalyzer(struct) prim_struct = sga.find_primitive() #prim_struct_sites = len(prim_struct.sites) #conv_struct = sga.get_conventional_standard_structure() #conv_struct_sites = len(conv_struct.sites) #conv_prim_ratio = int(conv_struct_sites / prim_struct_sites) # Default VASP settings def_vasp_incar_param = {'ISIF':2, 'EDIFF':1e-6, 'EDIFFG':0.001,} kpoint_den = 15000 # Create bulk structure and associated VASP files sc_scale = get_sc_scale(inp_struct=prim_struct, final_site_no=cellmax) blk_sc = prim_struct.copy() blk_sc.make_supercell(scaling_matrix=sc_scale) site_no = blk_sc.num_sites # Rescale if needed while site_no > cellmax: max_sc_dim = max(sc_scale) i = sc_scale.index(max_sc_dim) sc_scale[i] -= 1 blk_sc = prim_struct.copy() blk_sc.make_supercell(scaling_matrix=sc_scale) site_no = blk_sc.num_sites blk_str_sites = set(blk_sc.sites) custom_kpoints = Kpoints.automatic_density(blk_sc, kppa=kpoint_den) mpvis = MPMetalRelaxSet(blk_sc, user_incar_settings=def_vasp_incar_param, user_kpoints_settings=custom_kpoints) if mpid: root_fldr = mpid else: root_fldr = struct.composition.reduced_formula fin_dir = os.path.join(root_fldr, 'bulk') mpvis.write_input(fin_dir) if not mpid: # write the input structure if mpid is not used struct.to(fmt='poscar', filename=os.path.join(fin_dir, 'POSCAR.uc')) # Create each defect structure and associated VASP files # First find all unique defect sites periodic_struct = sga.get_symmetrized_structure() unique_sites = list(set([periodic_struct.find_equivalent_sites(site)[0] \ for site in periodic_struct.sites])) temp_struct = Structure.from_sites(sorted(unique_sites)) prim_struct2 = SpacegroupAnalyzer(temp_struct).find_primitive() prim_struct2.lattice = prim_struct.lattice # a little hacky for i, site in enumerate(prim_struct2.sites): vac = Vacancy(structure=prim_struct, defect_site=site) vac_sc = vac.generate_defect_structure(supercell=sc_scale) # Get vacancy site information vac_str_sites = set(vac_sc.sites) vac_sites = blk_str_sites - vac_str_sites vac_site = next(iter(vac_sites)) site_mult = vac.get_multiplicity() vac_site_specie = vac_site.specie vac_symbol = vac_site_specie.symbol custom_kpoints = Kpoints.automatic_density(vac_sc, kppa=kpoint_den) mpvis = MPMetalRelaxSet(vac_sc, user_incar_settings=def_vasp_incar_param, user_kpoints_settings=custom_kpoints) vac_dir = 'vacancy_{}_mult-{}_sitespecie-{}'.format( str(i+1), site_mult, vac_symbol) fin_dir = os.path.join(root_fldr, vac_dir) mpvis.write_input(fin_dir) # Antisites generation at the vacancy site struct_species = blk_sc.species for specie in set(struct_species) - set([vac_site_specie]): specie_symbol = specie.symbol anti_sc = vac_sc.copy() anti_sc.append(specie, vac_site.frac_coords) mpvis = MPMetalRelaxSet(anti_sc, user_incar_settings=def_vasp_incar_param, user_kpoints_settings=custom_kpoints) anti_dir = 'antisite_{}_mult-{}_sitespecie-{}_subspecie-{}'.format( str(i+1), site_mult, vac_symbol, specie_symbol) fin_dir = os.path.join(root_fldr, anti_dir) mpvis.write_input(fin_dir)
def set_structure(self, structure, reset_camera=True): """ Add a structure to the visualizer. Args: structure: structure to visualize reset_camera: Set to True to reset the camera to a default determined based on the structure. """ self.ren.RemoveAllViewProps() has_lattice = hasattr(structure, "lattice") if has_lattice: s = Structure.from_sites(structure, to_unit_cell=True) s.make_supercell(self.supercell) else: s = structure inc_coords = [] for site in s: self.add_site(site) inc_coords.append(site.coords) count = 0 labels = ["a", "b", "c"] colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] if has_lattice: matrix = s.lattice.matrix if self.show_unit_cell and has_lattice: #matrix = s.lattice.matrix self.add_text([0, 0, 0], "o") for vec in matrix: self.add_line((0, 0, 0), vec, colors[count]) self.add_text(vec, labels[count], colors[count]) count += 1 for (vec1, vec2) in itertools.permutations(matrix, 2): self.add_line(vec1, vec1 + vec2) for (vec1, vec2, vec3) in itertools.permutations(matrix, 3): self.add_line(vec1 + vec2, vec1 + vec2 + vec3) if self.show_bonds or self.show_polyhedron: elements = sorted(s.composition.elements, key=lambda a: a.X) anion = elements[-1] def contains_anion(site): for sp in site.species_and_occu.keys(): if sp.symbol == anion.symbol: return True return False anion_radius = anion.average_ionic_radius for site in s: exclude = False max_radius = 0 color = np.array([0, 0, 0]) for sp, occu in site.species_and_occu.items(): if sp.symbol in self.excluded_bonding_elements \ or sp == anion: exclude = True break max_radius = max(max_radius, sp.average_ionic_radius) color = color + \ occu * np.array(self.el_color_mapping.get(sp.symbol, [0, 0, 0])) if not exclude: max_radius = (1 + self.poly_radii_tol_factor) * \ (max_radius + anion_radius) nn = structure.get_neighbors(site, max_radius) nn_sites = [] for nnsite, dist in nn: if contains_anion(nnsite): nn_sites.append(nnsite) if not in_coord_list(inc_coords, nnsite.coords): self.add_site(nnsite) if self.show_bonds: self.add_bonds(nn_sites, site) if self.show_polyhedron: color = [i / 255 for i in color] self.add_polyhedron(nn_sites, site, color) if self.show_help: self.helptxt_actor = vtk.vtkActor2D() self.helptxt_actor.VisibilityOn() self.helptxt_actor.SetMapper(self.helptxt_mapper) self.ren.AddActor(self.helptxt_actor) self.display_help() camera = self.ren.GetActiveCamera() if reset_camera: if has_lattice: #Adjust the camera for best viewing lengths = s.lattice.abc pos = (matrix[1] + matrix[2]) * 0.5 + \ matrix[0] * max(lengths) / lengths[0] * 3.5 camera.SetPosition(pos) camera.SetViewUp(matrix[2]) camera.SetFocalPoint((matrix[0] + matrix[1] + matrix[2]) * 0.5) else: origin = s.center_of_mass max_site = max( s, key=lambda site: site.distance_from_point(origin)) camera.SetPosition(origin + 5 * (max_site.coords - origin)) camera.SetFocalPoint(s.center_of_mass) self.structure = structure self.title = s.composition.formula
def __init__(self, initial_structure, miller_index, min_slab_size, min_vacuum_size, lll_reduce=False, center_slab=False, primitive=True, max_normal_search=None): """ Calculates the slab scale factor and uses it to generate a unit cell of the initial structure that has been oriented by its miller index. Also stores the initial information needed later on to generate a slab. Args: initial_structure (Structure): Initial input structure. Note that to ensure that the miller indices correspond to usual crystallographic definitions, you should supply a conventional unit cell structure. miller_index ([h, k, l]): Miller index of plane parallel to surface. Note that this is referenced to the input structure. If you need this to be based on the conventional cell, you should supply the conventional structure. min_slab_size (float): In Angstroms min_vac_size (float): In Angstroms lll_reduce (bool): Whether to perform an LLL reduction on the eventual structure. center_slab (bool): Whether to center the slab in the cell with equal vacuum spacing from the top and bottom. primitive (bool): Whether to reduce any generated slabs to a primitive cell (this does **not** mean the slab is generated from a primitive cell, it simply means that after slab generation, we attempt to find shorter lattice vectors, which lead to less surface area and smaller cells). max_normal_search (int): If set to a positive integer, the code will conduct a search for a normal lattice vector that is as perpendicular to the surface as possible by considering multiples linear combinations of lattice vectors up to max_normal_search. This has no bearing on surface energies, but may be useful as a preliminary step to generating slabs for absorption and other sizes. It is typical that this will not be the smallest possible cell for simulation. Normality is not guaranteed, but the oriented cell will have the c vector as normal as possible (within the search range) to the surface. A value of up to the max absolute Miller index is usually sufficient. """ latt = initial_structure.lattice miller_index = reduce_vector(miller_index) #Calculate the surface normal using the reciprocal lattice vector. recp = latt.reciprocal_lattice_crystallographic normal = recp.get_cartesian_coords(miller_index) normal /= np.linalg.norm(normal) slab_scale_factor = [] non_orth_ind = [] eye = np.eye(3, dtype=np.int) for i, j in enumerate(miller_index): if j == 0: # Lattice vector is perpendicular to surface normal, i.e., # in plane of surface. We will simply choose this lattice # vector as one of the basis vectors. slab_scale_factor.append(eye[i]) else: #Calculate projection of lattice vector onto surface normal. d = abs(np.dot(normal, latt.matrix[i])) / latt.abc[i] non_orth_ind.append((i, d)) # We want the vector that has maximum magnitude in the # direction of the surface normal as the c-direction. # Results in a more "orthogonal" unit cell. c_index, dist = max(non_orth_ind, key=lambda t: t[1]) if len(non_orth_ind) > 1: lcm_miller = lcm(*[miller_index[i] for i, d in non_orth_ind]) for (i, di), (j, dj) in itertools.combinations(non_orth_ind, 2): l = [0, 0, 0] l[i] = -int(round(lcm_miller / miller_index[i])) l[j] = int(round(lcm_miller / miller_index[j])) slab_scale_factor.append(l) if len(slab_scale_factor) == 2: break if max_normal_search is None: slab_scale_factor.append(eye[c_index]) else: index_range = sorted(reversed( range(-max_normal_search, max_normal_search + 1)), key=lambda x: abs(x)) candidates = [] for uvw in itertools.product(index_range, index_range, index_range): if (not any(uvw)) or abs( np.linalg.det(slab_scale_factor + [uvw])) < 1e-8: continue vec = latt.get_cartesian_coords(uvw) l = np.linalg.norm(vec) cosine = abs(np.dot(vec, normal) / l) candidates.append((uvw, cosine, l)) if abs(abs(cosine) - 1) < 1e-8: # If cosine of 1 is found, no need to search further. break # We want the indices with the maximum absolute cosine, # but smallest possible length. uvw, cosine, l = max(candidates, key=lambda x: (x[1], -l)) slab_scale_factor.append(uvw) slab_scale_factor = np.array(slab_scale_factor) # Let's make sure we have a left-handed crystallographic system if np.linalg.det(slab_scale_factor) < 0: slab_scale_factor *= -1 # Make sure the slab_scale_factor is reduced to avoid # unnecessarily large slabs reduced_scale_factor = [reduce_vector(v) for v in slab_scale_factor] slab_scale_factor = np.array(reduced_scale_factor) single = initial_structure.copy() single.make_supercell(slab_scale_factor) self.oriented_unit_cell = Structure.from_sites(single, to_unit_cell=True) self.parent = initial_structure self.lll_reduce = lll_reduce self.center_slab = center_slab self.slab_scale_factor = slab_scale_factor self.miller_index = miller_index self.min_vac_size = min_vacuum_size self.min_slab_size = min_slab_size self.primitive = primitive self._normal = normal a, b, c = self.oriented_unit_cell.lattice.matrix self._proj_height = abs(np.dot(normal, c))
def _get_structures(self, num_structs): structs = [] if ".py" in makestr_cmd: options = ["-input", "struct_enum.out", str(1), str(num_structs)] else: options = ["struct_enum.out", str(0), str(num_structs - 1)] rs = subprocess.Popen([makestr_cmd] + options, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) stdout, stderr = rs.communicate() if stderr: logger.warning(stderr.decode()) # sites retrieved from enumlib will lack site properties # to ensure consistency, we keep track of what site properties # are missing and set them to None # TODO: improve this by mapping ordered structure to original # disorded structure, and retrieving correct site properties disordered_site_properties = {} if len(self.ordered_sites) > 0: original_latt = self.ordered_sites[0].lattice # Need to strip sites of site_properties, which would otherwise # result in an index error. Hence Structure is reconstructed in # the next step. site_properties = {} for site in self.ordered_sites: for k, v in site.properties.items(): disordered_site_properties[k] = None if k in site_properties: site_properties[k].append(v) else: site_properties[k] = [v] ordered_structure = Structure( original_latt, [site.species for site in self.ordered_sites], [site.frac_coords for site in self.ordered_sites], site_properties=site_properties) inv_org_latt = np.linalg.inv(original_latt.matrix) for file in glob.glob('vasp.*'): with open(file) as f: data = f.read() data = re.sub(r'scale factor', "1", data) data = re.sub(r'(\d+)-(\d+)', r'\1 -\2', data) poscar = Poscar.from_string(data, self.index_species) sub_structure = poscar.structure # Enumeration may have resulted in a super lattice. We need to # find the mapping from the new lattice to the old lattice, and # perform supercell construction if necessary. new_latt = sub_structure.lattice sites = [] if len(self.ordered_sites) > 0: transformation = np.dot(new_latt.matrix, inv_org_latt) transformation = [[int(round(cell)) for cell in row] for row in transformation] logger.debug("Supercell matrix: {}".format(transformation)) s = ordered_structure * transformation sites.extend([site.to_unit_cell() for site in s]) super_latt = sites[-1].lattice else: super_latt = new_latt for site in sub_structure: if site.specie.symbol != "X": # We exclude vacancies. sites.append( PeriodicSite( site.species, site.frac_coords, super_latt, to_unit_cell=True, properties=disordered_site_properties)) else: logger.debug("Skipping sites that include species X.") structs.append(Structure.from_sites(sorted(sites))) logger.debug("Read in a total of {} structures.".format(num_structs)) return structs
def get_sg_info(ss): finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec) return finder.get_space_group_number()