def test_apply_transformation(self): trans = MagOrderingTransformation({"Fe": 5}) p = Poscar.from_file(os.path.join(test_dir, 'POSCAR.LiFePO4'), check_for_POTCAR=False) s = p.structure alls = trans.apply_transformation(s, 10) self.assertEqual(len(alls), 3) f = SymmetryFinder(alls[0]["structure"], 0.1) self.assertEqual(f.get_spacegroup_number(), 31) model = IsingModel(5, 5) trans = MagOrderingTransformation({"Fe": 5}, energy_model=model) alls2 = trans.apply_transformation(s, 10) #Ising model with +J penalizes similar neighbor magmom. self.assertNotEqual(alls[0]["structure"], alls2[0]["structure"]) self.assertEqual(alls[0]["structure"], alls2[2]["structure"]) from pymatgen.io.smartio import read_structure s = read_structure(os.path.join(test_dir, 'Li2O.cif')) #Li2O doesn't have magnetism of course, but this is to test the # enumeration. trans = MagOrderingTransformation({"Li+": 1}, max_cell_size=3) alls = trans.apply_transformation(s, 100) self.assertEqual(len(alls), 10)
def __init__(self, struct, source='', comment=''): """ Args: struct: Structure object, See pymatgen.core.structure.Structure. source: User supplied identifier, i.e. for Materials Project this would be the material ID number comment: comment for first header line """ if struct.is_ordered: self._struct = struct self._source = source self._site_symbols = [] self._natoms = [] sym = SymmetryFinder(struct) data = sym.get_symmetry_dataset() self._space_number = data["number"] self._space_group = data["international"] syms = [site.specie.symbol for site in struct] for (s, data) in itertools.groupby(syms): self._site_symbols.append(s) self._natoms.append(len(tuple(data))) if comment == '': self._comment = 'None Given' else: self._comment = comment else: raise ValueError("Structure with partial occupancies cannot be " "converted into atomic coordinates!")
def __init__(self, structures, spacegroup, symm_prec=0.1): """ Args: structures: Sequence of structures to test. spacegroup: A spacegroup to test the structures. symm_prec: The symmetry precision to test with. """ structure_symm = {} self.symm_prec = symm_prec self.spacegroup = spacegroup logger.debug("Computing spacegroups...") for i, s in enumerate(structures): finder = SymmetryFinder(s, symm_prec) structure_symm[s] = finder.get_spacegroup_number() logger.debug("Structure {} has spacegroup {}" .format(i, structure_symm[s])) sorted_structures = sorted(structures, key=lambda s: -structure_symm[s]) unique_groups = [] for i, group in itertools.groupby(sorted_structures, key=lambda s: structure_symm[s]): logger.debug("Processing group of structures with sg number {}" .format(i)) subgroups = self._fit_group(group) unique_groups.extend(subgroups) self.unique_groups = unique_groups
def plot_chgint(args): chgcar = Chgcar.from_file(args.filename[0]) s = chgcar.structure if args.inds: atom_ind = map(int, args.inds[0].split(",")) else: finder = SymmetryFinder(s, symprec=0.1) sites = [ sites[0] for sites in finder.get_symmetrized_structure().equivalent_sites ] atom_ind = [s.sites.index(site) for site in sites] from pymatgen.util.plotting_utils import get_publication_quality_plot plt = get_publication_quality_plot(12, 8) for i in atom_ind: d = chgcar.get_integrated_diff(i, args.radius, 30) plt.plot(d[:, 0], d[:, 1], label="Atom {} - {}".format(i, s[i].species_string)) plt.legend(loc="upper left") plt.xlabel("Radius (A)") plt.ylabel("Integrated charge (e)") plt.tight_layout() plt.show()
def from_structure(cls, structure, has_timerev=True, symprec=1e-5, angle_tolerance=5): """ Takes a :class:`Structure` object. Uses pyspglib to perform various symmetry finding operations. Args: structure: :class:`Structure` object has_timerev: True is time-reversal symmetry is included. symprec: Tolerance for symmetry finding angle_tolerance: Angle tolerance for symmetry finding. .. warning:: AFM symmetries are not supported. """ # Call spglib to get the list of symmetry operations. finder = SymmetryFinder(structure, symprec=symprec, angle_tolerance=angle_tolerance) data = finder.get_symmetry_dataset() symrel = data["rotations"], return cls(spgid=data["number"], symrel=symrel, tnons=data["translations"], symafm=len(symrel) * [1], has_timerev=has_timerev, inord="C")
def from_structure(cls, structure, has_timerev=True, symprec=1e-5, angle_tolerance=5): """ Takes a `Structure` object. Uses pyspglib to perform various symmetry finding operations. Args: structure: Structure object has_timerev: True is time-reversal symmetry is included. symprec: Tolerance for symmetry finding angle_tolerance: Angle tolerance for symmetry finding. .. warning:: AFM symmetries are not supported. """ # Call spglib to get the list of symmetry operations. finder = SymmetryFinder(structure, symprec=symprec, angle_tolerance=angle_tolerance) data = finder.get_symmetry_dataset() symrel = data["rotations"], return cls(spgid=data["number"], symrel=symrel, tnons=data["translations"], symafm=len(symrel) * [1], has_timerev=has_timerev, inord="C")
def symmetrize(self): tensor = self._reduced_tensor if self._is_real_space: real_lattice = self._lattice else: real_lattice = self._lattice.reciprocal_lattice # Create a fake structure to get point group real_structure = Structure(real_lattice,["H"],[[0,0,0]]) real_finder = SymmetryFinder(real_structure) real_symmops = real_finder.get_point_group_operations(cartesian=True) cartesian_tensor = self.cartesian_tensor sym_tensor = np.zeros((3,3)) my_tensor = cartesian_tensor for real_sym in real_symmops: mat = real_sym.rotation_matrix prod_sym = np.dot(np.transpose(mat),np.dot(cartesian_tensor,mat)) sym_tensor = sym_tensor + prod_sym sym_tensor = sym_tensor/len(real_symmops) self._reduced_tensor = from_cart_to_red(sym_tensor,self._lattice)
def __init__(self, structure, min_cell_size=1, max_cell_size=1, symm_prec=0.1, enum_precision_parameter=0.001, refine_structure=False): """ Initializes the adapter with a structure and some parameters. Args: structure: An input structure. min_cell_size (int): The minimum cell size wanted. Defaults to 1. max_cell_size (int): The maximum cell size wanted. Defaults to 1. symm_prec (float): Symmetry precision. Defaults to 0.1. enum_precision_parameter (float): Finite precision parameter for enumlib. Default of 0.001 is usually ok, but you might need to tweak it for certain cells. refine_structure (bool): If you are starting from a structure that has been relaxed via some electronic structure code, it is usually much better to start with symmetry determination and then obtain a refined structure. The refined structure have cell parameters and atomic positions shifted to the expected symmetry positions, which makes it much less sensitive precision issues in enumlib. If you are already starting from an experimental cif, refinement should have already been done and it is not necessary. Defaults to False. """ if refine_structure: finder = SymmetryFinder(structure, symm_prec) self.structure = finder.get_refined_structure() else: self.structure = structure self.min_cell_size = min_cell_size self.max_cell_size = max_cell_size self.symm_prec = symm_prec self.enum_precision_parameter = enum_precision_parameter
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) mod = StructureEditor(structure) mod.delete_sites(indices_list) s_new = mod.modified_structure 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 test_get_ir_kpoints(self): v = Vasprun(os.path.join(test_dir, 'vasprun_Si_bands.xml')) finder = SymmetryFinder(v.final_structure) actual_kpts = v.actual_kpoints mapping = finder.get_ir_kpoints_mapping(actual_kpts) irr_kpts = finder.get_ir_kpoints(actual_kpts) self.assertLess(len(irr_kpts), len(actual_kpts)) self.assertEqual(len(irr_kpts), len(set(mapping)))
def get_primitive(fname): poscar = Poscar.from_file(fname) finder = SymmetryFinder(poscar.structure) spg_num = finder.get_spacegroup_number() primitive = finder.get_primitive_standard_structure() return spg_num, primitive
def main(): def str_examples(): examples = """ Usage example:\n cif2spg.py silicon.cif => Open CIF file and visualize info on the spacegroup. cif2spg.py -g silicon.cif => Same as above but use the GUI. """ return examples def show_examples_and_exit(err_msg=None, error_code=1): """Display the usage of the script.""" sys.stderr.write(str_examples()) if err_msg: sys.stderr.write("Fatal Error\n" + err_msg + "\n") sys.exit(error_code) parser = argparse.ArgumentParser(epilog=str_examples(), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('cif_file', nargs=1, help="CIF File") parser.add_argument('-g', '--gui', action="store_true", help="Enable GUI") # Parse command line. try: options = parser.parse_args() except: show_examples_and_exit(error_code=1) cif_file = options.cif_file[0] structure = abilab.Structure.from_file(cif_file) #print(structure.to_abivars()) finder = SymmetryFinder(structure, symprec=1e-5, angle_tolerance=5) data = finder.get_symmetry_dataset() from pprint import pprint if not options.gui: pprint(data) else: from StringIO import StringIO import wx from abipy.gui.editor import SimpleTextViewer stream = StringIO() pprint(data, stream=stream) stream.seek(0) text = "".join(stream) app = wx.App() frame = SimpleTextViewer(None, text=text) frame.Show() app.MainLoop() return 0
def get_poscar(self, structure): """ Get a POSCAR file with a primitive standardized cell of the giving structure. Args: structure (Structure/IStructure): structure to get POSCAR """ sym_finder = SymmetryFinder(structure, symprec=self.sym_prec) return Poscar(sym_finder.get_primitive_standard_structure())
def test_get_refined_structure(self): for a in self.sg.get_refined_structure().lattice.angles: self.assertEqual(a, 90) refined = self.disordered_sg.get_refined_structure() for a in refined.lattice.angles: self.assertEqual(a, 90) self.assertEqual(refined.lattice.a, refined.lattice.b) parser = CifParser(os.path.join(test_dir, 'Li2O.cif')) s = parser.get_structures()[0] sg = SymmetryFinder(s, 0.001) self.assertEqual(sg.get_refined_structure().num_sites, 4 * s.num_sites)
def __init__(self, structure, min_cell_size=1, max_cell_size=1, symm_prec=0.1, enum_precision_parameter=0.001, refine_structure=False): if refine_structure: finder = SymmetryFinder(structure, symm_prec) self.structure = finder.get_refined_structure() else: self.structure = structure self.min_cell_size = min_cell_size self.max_cell_size = max_cell_size self.symm_prec = symm_prec self.enum_precision_parameter = enum_precision_parameter
def get_structure_hash(self, structure): """ Hash for structure. Args: structure: A structure Returns: Reduced formula for the structure is used as a hash for the SpeciesComparator. """ f = SymmetryFinder(structure, 0.1) return "{} {}".format(f.get_spacegroup_number(), structure.composition.reduced_formula)
def _symmetry_reduced_voronoi_nodes(structure, rad_dict): """ Obtain symmetry reduced voronoi nodes using Zeo++ and pymatgen.symmetry.finder.SymmetryFinder Args: strucutre: pymatgen Structure object Returns: Symmetrically distinct voronoi nodes as pymatgen Strucutre """ vor_node_struct = get_voronoi_nodes(structure, rad_dict) vor_symmetry_finder = SymmetryFinder(vor_node_struct, symprec=1e-1) vor_symm_struct = vor_symmetry_finder.get_symmetrized_structure() #print vor_symm_struct.lattice #print vor_symm_struct.lattice.abc, vor_symm_struct.lattice.angles #print vor_node_struct.lattice #print vor_node_struct.lattice.abc, vor_node_struct.lattice.angles equiv_sites_list = vor_symm_struct.equivalent_sites def add_closest_equiv_site(dist_sites, equiv_sites): if not dist_sites: dist_sites.append(equiv_sites[0]) else: avg_dists = [] for site in equiv_sites: dists = [ site.distance(dst_site, jimage=[0, 0, 0]) for dst_site in dist_sites ] avg_dist = sum(dists) / len(dist_sites) avg_dists.append(avg_dist) min_avg_dist = min(avg_dists) ind = avg_dists.index(min_avg_dist) dist_sites.append(equiv_sites[ind]) dist_sites = [] for equiv_sites in equiv_sites_list: add_closest_equiv_site(dist_sites, equiv_sites) #lat = structure.lattice #sp = [site.specie for site in sites] # "Al" because to Zeo++ #coords = [site.coords for site in sites] #vor_node_radii = [site.properties['voronoi_radius'] for site in sites] #vor_node_struct = Structure(lat, sp, coords, # coords_are_cartesian=True, # site_properties={'voronoi_radius':vor_node_radii} # ) return dist_sites
def _make_struc_file(self, file_name): sym = SymmetryFinder(self._bs._structure, symprec=0.01) with open(file_name, "w") as f: f.write(self._bs._structure.composition.formula + " " + str(sym.get_spacegroup_symbol()) + "\n") for i in range(3): line = "" for j in range(3): line += "%12.5f" % (Length(self._bs._structure.lattice.matrix[i][j], "ang").to("bohr")) f.write(line + "\n") ops = sym.get_symmetry_dataset()["rotations"] f.write(str(len(ops)) + "\n") for c in ops: f.write("\n".join([" ".join([str(int(i)) for i in row]) for row in c])) f.write("\n")
def parse_symmetry(args): tolerance = float(args.tolerance[0]) for filename in args.filenames: s = read_structure(filename) if args.spacegroup: finder = SymmetryFinder(s, tolerance) dataset = finder.get_symmetry_dataset() print filename print "Spacegroup : {}".format(dataset["international"]) print "Int number : {}".format(dataset["number"]) print "Hall symbol : {}".format(dataset["hall"]) print
def symmetry_reduced_voronoi_nodes(structure, rad_dict): """ Obtain symmetry reduced voronoi nodes using Zeo++ and pymatgen.symmetry.finder.SymmetryFinder Args: strucutre: pymatgen Structure object Returns: Symmetrically distinct voronoi nodes as pymatgen Strucutre """ vor_node_struct = get_voronoi_nodes(structure, rad_dict) vor_symmetry_finder = SymmetryFinder(vor_node_struct, symprec=1e-1) vor_symm_struct = vor_symmetry_finder.get_symmetrized_structure() #print vor_symm_struct.lattice #print vor_symm_struct.lattice.abc, vor_symm_struct.lattice.angles #print vor_node_struct.lattice #print vor_node_struct.lattice.abc, vor_node_struct.lattice.angles equiv_sites_list = vor_symm_struct.equivalent_sites def add_closest_equiv_site(dist_sites, equiv_sites): if not dist_sites: dist_sites.append(equiv_sites[0]) else: avg_dists = [] for site in equiv_sites: dists = [site.distance(dst_site, jimage=[0, 0, 0]) for dst_site in dist_sites] avg_dist = sum(dists) / len(dist_sites) avg_dists.append(avg_dist) min_avg_dist = min(avg_dists) ind = avg_dists.index(min_avg_dist) dist_sites.append(equiv_sites[ind]) dist_sites = [] for equiv_sites in equiv_sites_list: add_closest_equiv_site(dist_sites, equiv_sites) #lat = structure.lattice #sp = [site.specie for site in sites] # "Al" because to Zeo++ #coords = [site.coords for site in sites] #vor_node_radii = [site.properties['voronoi_radius'] for site in sites] #vor_node_struct = Structure(lat, sp, coords, # coords_are_cartesian=True, # site_properties={'voronoi_radius':vor_node_radii} # ) return dist_sites
def test_find_primitive(self): """ F m -3 m Li2O testing of converting to primitive cell """ parser = CifParser(os.path.join(test_dir, 'Li2O.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure) primitive_structure = s.find_primitive() self.assertEqual(primitive_structure.formula, "Li2 O1") # This isn't what is expected. All the angles should be 60 self.assertAlmostEqual(primitive_structure.lattice.alpha, 60) self.assertAlmostEqual(primitive_structure.lattice.beta, 60) self.assertAlmostEqual(primitive_structure.lattice.gamma, 60) self.assertAlmostEqual(primitive_structure.lattice.volume, structure.lattice.volume / 4.0)
def __init__(self, structure, valences, radii): """ Args: structure: pymatgen.core.structure.Structure valences: valences of elements as a dictionary radii: Radii of elements as a dictionary """ self._structure = structure self._valence_dict = valences self._rad_dict = radii # Store symmetrically distinct sites, their coordination numbers # coordinated_sites, effective charge symm_finder = SymmetryFinder(self._structure) symm_structure = symm_finder.get_symmetrized_structure() equiv_site_seq = symm_structure.equivalent_sites self._defect_sites = [] for equiv_sites in equiv_site_seq: self._defect_sites.append(equiv_sites[0]) self._vac_site_indices = [] for site in self._defect_sites: for i in range(len(self._structure.sites)): if site == self._structure[i]: self._vac_site_indices.append(i) coord_finder = VoronoiCoordFinder(self._structure) self._defectsite_coord_no = [] self._defect_coord_sites = [] for i in self._vac_site_indices: self._defectsite_coord_no.append( coord_finder.get_coordination_number(i) ) self._defect_coord_sites.append( coord_finder.get_coordinated_sites(i) ) # Store the ionic radii for the elements in the structure # (Used to computing the surface are and volume) # Computed based on valence of each element self._vac_eff_charges = None self._vol = None self._sa = None
def setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg = SymmetryFinder(self.structure, 0.001) parser = CifParser(os.path.join(test_dir, 'Li10GeP2S12.cif')) self.disordered_structure = parser.get_structures()[0] self.disordered_sg = SymmetryFinder(self.disordered_structure, 0.001) s = p.structure editor = StructureEditor(s) site = s[0] editor.delete_site(0) editor.append_site(site.species_and_occu, site.frac_coords) self.sg3 = SymmetryFinder(editor.modified_structure, 0.001) parser = CifParser(os.path.join(test_dir, 'Graphite.cif')) graphite = parser.get_structures()[0] self.sg4 = SymmetryFinder(graphite, 0.001)
def setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg = SymmetryFinder(self.structure, 0.001) parser = CifParser(os.path.join(test_dir, 'Li10GeP2S12.cif')) self.disordered_structure = parser.get_structures()[0] self.disordered_sg = SymmetryFinder(self.disordered_structure, 0.001) s = p.structure.copy() site = s[0] del s[0] s.append(site.species_and_occu, site.frac_coords) self.sg3 = SymmetryFinder(s, 0.001) parser = CifParser(os.path.join(test_dir, 'Graphite.cif')) graphite = parser.get_structures()[0] graphite.add_site_property("magmom", [0.1] * len(graphite)) self.sg4 = SymmetryFinder(graphite, 0.001)
class SpacegroupTest(unittest.TestCase): def setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg1 = SymmetryFinder(self.structure, 0.001).get_spacegroup() def test_are_symmetrically_equivalent(self): sites1 = [self.structure[i] for i in [0, 1]] sites2 = [self.structure[i] for i in [2, 3]] self.assertTrue( self.sg1.are_symmetrically_equivalent(sites1, sites2, 1e-3)) sites1 = [self.structure[i] for i in [0, 1]] sites2 = [self.structure[i] for i in [0, 2]] self.assertFalse( self.sg1.are_symmetrically_equivalent(sites1, sites2, 1e-3))
def add_snl(self, snl): snl_id = self._get_next_snl_id() sf = SymmetryFinder(snl.structure, SPACEGROUP_TOLERANCE) sf.get_spacegroup() mpsnl = MPStructureNL.from_snl(snl, snl_id, sf.get_spacegroup_number(), sf.get_spacegroup_symbol(), sf.get_hall(), sf.get_crystal_system(), sf.get_lattice_type()) snlgroup, add_new = self.add_mpsnl(mpsnl) return mpsnl, snlgroup.snlgroup_id
def get_symetry(lines, output): latt_tmp = [[float(x) for x in y.split()] for y in lines[0:3]] trans = [[float(x) for x in y.split()] for y in lines[3:6]] sites = [[float(x) for x in y.split()[0:3]] for y in lines[6:]] elements = [x.split()[3] for x in lines[6:]] latt = np.dot(np.array(trans),np.array(latt_tmp)) latt_obj = mg.Lattice(latt) structure = mg.Structure(latt, elements, sites) finder = SymmetryFinder(structure) out = finder.get_spacegroup_symbol() + "\n" #structure = finder.find_primitive() #structure = finder.get_primitive_standard_structure() poscar = Poscar(structure) #cif = CifWriter(structure) out += poscar.__str__() #print(cif) return out
class SpacegroupTest(unittest.TestCase): def setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg1 = SymmetryFinder(self.structure, 0.001).get_spacegroup() def test_are_symmetrically_equivalent(self): sites1 = [self.structure[i] for i in [0, 1]] sites2 = [self.structure[i] for i in [2, 3]] self.assertTrue(self.sg1.are_symmetrically_equivalent(sites1, sites2, 1e-3)) sites1 = [self.structure[i] for i in [0, 1]] sites2 = [self.structure[i] for i in [0, 2]] self.assertFalse(self.sg1.are_symmetrically_equivalent(sites1, sites2, 1e-3))
def __init__(self, structure, valences, radii): """ Args: structure: pymatgen.core.structure.Structure valences: valences of elements as a dictionary radii: Radii of elements as a dictionary """ self._structure = structure self._valence_dict = valences self._rad_dict = radii # Store symmetrically distinct sites, their coordination numbers # coordinated_sites, effective charge symm_finder = SymmetryFinder(self._structure) symm_structure = symm_finder.get_symmetrized_structure() equiv_site_seq = symm_structure.equivalent_sites self._defect_sites = [] for equiv_sites in equiv_site_seq: self._defect_sites.append(equiv_sites[0]) self._vac_site_indices = [] for site in self._defect_sites: for i in range(len(self._structure.sites)): if site == self._structure[i]: self._vac_site_indices.append(i) coord_finder = VoronoiCoordFinder(self._structure) self._defectsite_coord_no = [] self._defect_coord_sites = [] for i in self._vac_site_indices: self._defectsite_coord_no.append( coord_finder.get_coordination_number(i)) self._defect_coord_sites.append( coord_finder.get_coordinated_sites(i)) # Store the ionic radii for the elements in the structure # (Used to computing the surface are and volume) # Computed based on valence of each element self._vac_eff_charges = None self._vol = None self._sa = None
def structure_lines(self, structure, cell_flg=True, frac_flg=True, anion_shell_flg=True, cation_shell_flg=False, symm_flg=True): """ Generates GULP input string corresponding to pymatgen structure. Args: structure: pymatgen Structure object cell_flg (default = True): Option to use lattice parameters. fractional_flg (default = True): If True, fractional coordinates are used. Else, cartesian coodinates in Angstroms are used. ****** GULP convention is to use fractional coordinates for periodic structures and cartesian coordinates for non-periodic structures. ****** anion_shell_flg (default = True): If True, anions are considered polarizable. cation_shell_flg (default = False): If True, cations are considered polarizable. symm_flg (default = True): If True, symmetry information is also written. Returns: string containing structure for GULP input """ gin = "" if cell_flg: gin += "cell\n" l = structure.lattice lat_str = map(str, [l.a, l.b, l.c, l.alpha, l.beta, l.gamma]) gin += " ".join(lat_str) + "\n" if frac_flg: gin += "frac\n" coord_attr = "frac_coords" else: gin += "cart\n" coord_attr = "coords" for site in structure.sites: coord = map(str, list(getattr(site, coord_attr))) specie = site.specie core_site_desc = specie.symbol + " core " + " ".join(coord) + "\n" gin += core_site_desc if ((specie in _anions and anion_shell_flg) or (specie in _cations and cation_shell_flg)): shel_site_desc = specie.symbol + " shel " + " ".join( coord) + "\n" gin += shel_site_desc else: pass if symm_flg: gin += "space\n" gin += str(SymmetryFinder(structure).get_spacegroup_number()) + "\n" return gin
def _make_struc_file(self, file_name): sym = SymmetryFinder(self._bs._structure, symprec=0.01) with open(file_name, 'w') as f: f.write(self._bs._structure.composition.formula+" " + str(sym.get_spacegroup_symbol())+"\n") for i in range(3): line = '' for j in range(3): line += "%12.5f" % ( Length(self._bs._structure.lattice.matrix[i][j], "ang").to("bohr")) f.write(line+'\n') ops = sym.get_symmetry_dataset()['rotations'] f.write(str(len(ops))+"\n") for c in ops: f.write('\n'.join([' '.join([str(int(i)) for i in row]) for row in c])) f.write('\n')
def get_structure(vasp_run, outcar=None, initial_structure=False, additional_info=False): """ Process structure for static calculations from previous run. Args: vasp_run: Vasprun object that contains the final structure from previous run. outcar: Outcar object that contains the magnetization info from previous run. initial_structure: Whether to return the structure from previous run. Default is False. additional_info: Whether to return additional symmetry info related to the structure. If True, return a list of the refined structure ( conventional cell), the conventional standard structure, the symmetry dataset and symmetry operations of the structure (see SymmetryFinder doc for details) Returns: Returns the magmom-decorated structure that can be passed to get Vasp input files, e.g. get_kpoints. """ #TODO: fix magmom for get_*_structures if vasp_run.is_spin: if outcar and outcar.magnetization: magmom = {"magmom": [i['tot'] for i in outcar.magnetization]} else: magmom = { "magmom": vasp_run.to_dict['input']['parameters']['MAGMOM'] } else: magmom = None structure = vasp_run.final_structure if magmom: structure = structure.copy(site_properties=magmom) sym_finder = SymmetryFinder(structure, symprec=0.01) if initial_structure: return structure elif additional_info: info = [ sym_finder.get_refined_structure(), sym_finder.get_conventional_standard_structure(), sym_finder.get_symmetry_dataset(), sym_finder.get_symmetry_operations() ] return [sym_finder.get_primitive_standard_structure(), info] else: return sym_finder.get_primitive_standard_structure()
def get_structure(vasp_run, outcar=None, initial_structure=False, refined_structure=False): """ Process structure for static calculations from previous run. Args: vasp_run: Vasprun object that contains the final structure from previous run. outcar: Outcar object that contains the magnetization info from previous run. initial_structure: Whether to return the structure from previous run. Default is False. refined_structure: Whether to return the refined structure (conventional cell) Returns: Returns the magmom-decorated structure that can be passed to get Vasp input files, e.g. get_kpoints. """ #TODO: fix magmom for get_*_structures if vasp_run.is_spin: if outcar and outcar.magnetization: magmom = {"magmom": [i['tot'] for i in outcar.magnetization]} else: magmom = { "magmom": vasp_run.to_dict['input']['parameters'] ['MAGMOM']} else: magmom = None structure = vasp_run.final_structure if magmom: structure = structure.copy(site_properties=magmom) sym_finder = SymmetryFinder(structure, symprec=0.01) if initial_structure: return structure elif refined_structure: return (sym_finder.get_primitive_standard_structure(), sym_finder.get_refined_structure()) else: return sym_finder.get_primitive_standard_structure()
def __init__(self, struct, source='', comment=''): if struct.is_ordered: self._struct = struct self._source = source self._site_symbols = [] self._natoms = [] sym = SymmetryFinder(struct) data = sym.get_symmetry_dataset() self._space_number = data["number"] self._space_group = data["international"] syms = [site.specie.symbol for site in struct] for (s, data) in itertools.groupby(syms): self._site_symbols.append(s) self._natoms.append(len(tuple(data))) if comment == '': self._comment = 'None Given' else: self._comment = comment else: raise ValueError("Structure with partial occupancies cannot be " "converted into atomic coordinates!")
def _generate_entry(structure, params, name, energy, pressure): comp = structure.composition entry = {"structure": structure.to_dict, "name": name, "energy": energy, "energy_per_site": energy / comp.num_atoms, "pressure": pressure, "potential": {"name": "lennard_jones", "params": params.to_dict}} # Set the composition and formulas for the system el_amt = comp.get_el_amt_dict() entry.update({"unit_cell_formula": comp.to_dict, "reduced_cell_formula": comp.to_reduced_dict, "elements": list(el_amt.keys()), "nelements": len(el_amt), "pretty_formula": comp.reduced_formula, "anonymous_formula": comp.anonymized_formula, "nsites": comp.num_atoms, "chemsys": "-".join(sorted(el_amt.keys()))}) # Figure out the symmetry group sg = SymmetryFinder(structure, normalised_symmetry_precision(structure), -1) entry["spacegroup"] = {"symbol": unicode(sg.get_spacegroup_symbol(), errors="ignore"), "number": sg.get_spacegroup_number(), "point_group": unicode(sg.get_point_group(), errors="ignore"), "source": "spglib", "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall()} return entry
def __init__(self, structure, min_cell_size=1, max_cell_size=1, symm_prec=0.1, enum_precision_parameter=0.001, refine_structure=False): """ Args: structure: An input structure. min_cell_size: The minimum cell size wanted. Must be an int. Defaults to 1. max_cell_size: The maximum cell size wanted. Must be an int. Defaults to 1. symm_prec: Symmetry precision. Defaults to 0.1. enum_precision_parameter: Finite precision parameter for enumlib. Default of 0.001 is usually ok, but you might need to tweak it for certain cells. refine_structure: If you are starting from a structure that has been relaxed via some electronic structure code, it is usually much better to start with symmetry determination and then obtain a refined structure. The refined structure have cell parameters and atomic positions shifted to the expected symmetry positions, which makes it much less sensitive precision issues in enumlib. If you are already starting from an experimental cif, refinment should have already been done and it is not necessary. Defaults to False. """ if refine_structure: finder = SymmetryFinder(structure, symm_prec) self.structure = finder.get_refined_structure() else: self.structure = structure self.min_cell_size = min_cell_size self.max_cell_size = max_cell_size self.symm_prec = symm_prec self.enum_precision_parameter = enum_precision_parameter
def main(): parser = argparse.ArgumentParser(epilog=str_examples(), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('cif_file', nargs=1, help="CIF File") parser.add_argument('-g', '--gui', action="store_true", help="Enable GUI") # Parse command line. try: options = parser.parse_args() except: show_examples_and_exit(error_code=1) cif_file = options.cif_file[0] structure = abilab.Structure.from_file(cif_file) #print(structure.to_abivars()) finder = SymmetryFinder(structure, symprec=1e-5, angle_tolerance=5) data = finder.get_symmetry_dataset() from pprint import pprint if not options.gui: pprint(data) else: from StringIO import StringIO import wx from abipy.gui.editor import SimpleTextViewer stream = StringIO() pprint(data, stream=stream) stream.seek(0) text = "".join(stream) app = wx.App() frame = SimpleTextViewer(None, text=text) frame.Show() app.MainLoop() return 0
def process_res(cls, resfile): fullpath = os.path.abspath(resfile) res = Res.from_file(resfile) d = res.to_dict d["file_name"] = fullpath s = res.structure # Set the composition and formulas for the system comp = s.composition el_amt = s.composition.get_el_amt_dict() d.update({"unit_cell_formula": comp.to_dict, "reduced_cell_formula": comp.to_reduced_dict, "elements": list(el_amt.keys()), "nelements": len(el_amt), "pretty_formula": comp.reduced_formula, "anonymous_formula": comp.anonymized_formula, "nsites": comp.num_atoms, "chemsys": "-".join(sorted(el_amt.keys()))}) #d["density"] = s.density # Figure out the symmetry group sg = SymmetryFinder(s, util.normalised_symmetry_precision(s), -1) d["spacegroup"] = {"symbol": unicode(sg.get_spacegroup_symbol(), errors="ignore"), "number": sg.get_spacegroup_number(), "point_group": unicode(sg.get_point_group(), errors="ignore"), "source": "spglib", "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall()} return d
def produce(irreps): os.makedirs('irrep') for i, irrep in enumerate(irreps): poscar = Poscar(irrep) symbols = poscar.site_symbols natoms = poscar.natoms name_dict = {'Al': 'A', 'Ti': 'B'} tmp = ["{0}{1}".format(name_dict[x], y) for x, y in zip(symbols, natoms)] finder = SymmetryFinder(irrep) spg_num = finder.get_spacegroup_number() spg = "_".join(finder.get_spacegroup_symbol().split('/')) dirname = "No." + "{0:03d}".format(i) + "_" + spg + "_" + "".join(tmp) poscar.comment += " (#" + str(spg_num) + ": " + spg + ")" standard = finder.get_conventional_standard_structure() stand_pos = Poscar(standard) stand_pos.comment += " (#" + str(spg_num) + ": " + spg + ")" os.makedirs(os.path.join('irrep', dirname)) poscar.write_file(os.path.join('irrep', dirname, 'POSCAR.prim')) stand_pos.write_file(os.path.join('irrep', dirname, 'POSCAR.std'))
def monkhorst_automatic(cls, structure, ngkpt, chksymbreak=None, use_symmetries=True, use_time_reversal=True, comment=None): """ Convenient static constructor for an automatic Monkhorst-Pack mesh. Args: structure: paymatgen structure object. ngkpt: Subdivisions N_1, N_2 and N_3 along reciprocal lattice vectors. use_symmetries: Use spatial symmetries to reduce the number of k-points. use_time_reversal: Use time-reversal symmetry to reduce the number of k-points. Returns: KSampling object """ sg = SymmetryFinder(structure) #sg.get_crystal_system() #sg.get_point_group() # TODO nshiftk = 1 #shiftk = 3*(0.5,) # this is the default shiftk = 3 * (0.5, ) #if lattice.ishexagonal: #elif lattice.isbcc #elif lattice.isfcc return cls.monkhorst( ngkpt, shiftk=shiftk, chksymbreak=chksymbreak, use_symmetries=use_symmetries, use_time_reversal=use_time_reversal, comment=comment if comment else "Automatic Monkhorst-Pack scheme", )
def get_kpoints(self, structure, kpoints_density=1000): """ Get a KPOINTS file for NonSCF calculation. In "Line" mode, kpoints are generated along high symmetry lines. In "Uniform" mode, kpoints are Gamma-centered mesh grid. Kpoints are written explicitly in both cases. Args: kpoints_density: kpoints density for the reciprocal cell of structure. Suggest to use a large kpoints_density. Might need to increase the default value when calculating metallic materials. """ if self.mode == "Line": kpath = HighSymmKpath(structure) cart_k_points, k_points_labels = kpath.get_kpoints() frac_k_points = [ kpath._prim_rec.get_fractional_coords(k) for k in cart_k_points ] return Kpoints(comment="Non SCF run along symmetry lines", style="Reciprocal", num_kpts=len(frac_k_points), kpts=frac_k_points, labels=k_points_labels, kpts_weights=[1] * len(cart_k_points)) else: num_kpoints = kpoints_density * \ structure.lattice.reciprocal_lattice.volume kpoints = Kpoints.automatic_density( structure, num_kpoints * structure.num_sites) mesh = kpoints.kpts[0] ir_kpts = SymmetryFinder(structure, symprec=0.01)\ .get_ir_reciprocal_mesh(mesh) kpts = [] weights = [] for k in ir_kpts: kpts.append(k[0]) weights.append(int(k[1])) return Kpoints(comment="Non SCF run on uniform grid", style="Reciprocal", num_kpts=len(ir_kpts), kpts=kpts, kpts_weights=weights)
def apply_transformation(self, structure, return_ranked_list=False): """ Return either a single ordered structure or a sequence of all ordered structures. Args: structure: Structure to order. return_ranked_list: Boolean stating whether or not multiple structures are returned. If return_ranked_list is a number, that number of structures is returned. Returns: Depending on returned_ranked list, either a transformed structure or a list of dictionaries, where each dictionary is of the form {"structure" = .... , "other_arguments"} The list of ordered structures is ranked by ewald energy / atom, if the input structure is an oxidation state decorated structure. Otherwise, it is ranked by number of sites, with smallest number of sites first. """ try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if structure.is_ordered: raise ValueError("Enumeration can be carried out only on " "disordered structures!") if self.refine_structure: finder = SymmetryFinder(structure, self.symm_prec) structure = finder.get_refined_structure() contains_oxidation_state = True for sp in structure.composition.elements: if not hasattr(sp, "oxi_state"): contains_oxidation_state = False break adaptor = EnumlibAdaptor(structure, min_cell_size=self.min_cell_size, max_cell_size=self.max_cell_size, symm_prec=self.symm_prec, refine_structure=False) adaptor.run() structures = adaptor.structures original_latt = structure.lattice inv_latt = np.linalg.inv(original_latt.matrix) ewald_matrices = {} all_structures = [] for s in structures: new_latt = s.lattice transformation = np.dot(new_latt.matrix, inv_latt) transformation = tuple([ tuple([int(round(cell)) for cell in row]) for row in transformation ]) if contains_oxidation_state: if transformation not in ewald_matrices: s = Structure.from_sites(structure.sites) s.make_supercell(transformation) ewald = EwaldSummation(s) ewald_matrices[transformation] = ewald else: ewald = ewald_matrices[transformation] energy = ewald.compute_sub_structure(s) all_structures.append({ "num_sites": len(s), "energy": energy, "structure": s }) else: all_structures.append({"num_sites": len(s), "structure": s}) def sort_func(s): return s["energy"] / s["num_sites"] if contains_oxidation_state \ else s["num_sites"] self._all_structures = sorted(all_structures, key=sort_func) if return_ranked_list: return self._all_structures[0:num_to_return] else: return self._all_structures[0]["structure"]
def generate_doc(cls, dir_name, vasprun_files, parse_dos, additional_fields): """ Process aflow style runs, where each run is actually a combination of two vasp runs. """ try: fullpath = os.path.abspath(dir_name) #Defensively copy the additional fields first. This is a MUST. #Otherwise, parallel updates will see the same object and inserts #will be overridden!! d = {k: v for k, v in additional_fields.items()} \ if additional_fields else {} d["dir_name"] = fullpath d["schema_version"] = VaspToDbTaskDrone.__version__ d["calculations"] = [ cls.process_vasprun(dir_name, taskname, filename, parse_dos) for taskname, filename in vasprun_files.items() ] d1 = d["calculations"][0] d2 = d["calculations"][-1] #Now map some useful info to the root level. for root_key in [ "completed_at", "nsites", "unit_cell_formula", "reduced_cell_formula", "pretty_formula", "elements", "nelements", "cif", "density", "is_hubbard", "hubbards", "run_type" ]: d[root_key] = d2[root_key] d["chemsys"] = "-".join(sorted(d2["elements"])) d["input"] = {"crystal": d1["input"]["crystal"]} vals = sorted(d2["reduced_cell_formula"].values()) d["anonymous_formula"] = { string.ascii_uppercase[i]: float(vals[i]) for i in xrange(len(vals)) } d["output"] = { "crystal": d2["output"]["crystal"], "final_energy": d2["output"]["final_energy"], "final_energy_per_atom": d2["output"]["final_energy_per_atom"] } d["name"] = "aflow" d["pseudo_potential"] = { "functional": "pbe", "pot_type": "paw", "labels": d2["input"]["potcar"] } if len(d["calculations"]) == 2 or \ vasprun_files.keys()[0] != "relax1": d["state"] = "successful" if d2["has_vasp_completed"] \ else "unsuccessful" else: d["state"] = "stopped" s = Structure.from_dict(d2["output"]["crystal"]) if not s.is_valid(): d["state"] = "errored_bad_structure" d["analysis"] = get_basic_analysis_and_error_checks(d) sg = SymmetryFinder(Structure.from_dict(d["output"]["crystal"]), 0.1) d["spacegroup"] = { "symbol": sg.get_spacegroup_symbol(), "number": sg.get_spacegroup_number(), "point_group": unicode(sg.get_point_group(), errors="ignore"), "source": "spglib", "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall() } d["last_updated"] = datetime.datetime.today() d["type"] = "VASP" #PS return d except Exception as ex: logger.error("Error in " + os.path.abspath(dir_name) + ".\nError msg: " + str(ex)) return None
def generate_doc(cls, dir_name, vasprun_files, parse_dos, additional_fields): """Process aflow style and NEB runs.""" print "TTM DEBUG: In generate_doc for NEB task drone." try: fullpath = os.path.abspath(dir_name) #Defensively copy the additional fields first. This is a MUST. #Otherwise, parallel updates will see the same object and inserts #will be overridden!! d = {k: v for k, v in additional_fields.items()} \ if additional_fields else {} d["dir_name"] = fullpath print "TTM DEBUG: fullpath: ", fullpath d["schema_version"] = NEBToDbTaskDrone.__version__ d["calculations"] = [ cls.process_vasprun(dir_name, taskname, filename, parse_dos) for taskname, filename in vasprun_files.items() ] d1 = d["calculations"][0] d2 = d["calculations"][-1] #Now map some useful info to the root level. for root_key in [ "completed_at", "nsites", "unit_cell_formula", "reduced_cell_formula", "pretty_formula", "elements", "nelements", "cif", "density", "is_hubbard", "hubbards", "run_type" ]: d[root_key] = d2[root_key] d["chemsys"] = "-".join(sorted(d2["elements"])) d["input"] = {"crystal": d1["input"]["crystal"]} vals = sorted(d2["reduced_cell_formula"].values()) d["anonymous_formula"] = { string.ascii_uppercase[i]: float(vals[i]) for i in xrange(len(vals)) } d["output"] = { "crystal": d2["output"]["crystal"], "final_energy": d2["output"]["final_energy"], "final_energy_per_atom": d2["output"]["final_energy_per_atom"] } d["name"] = "aflow" d["pseudo_potential"] = { "functional": "pbe", "pot_type": "paw", "labels": d2["input"]["potcar"] } if len(d["calculations"]) == 2 or \ vasprun_files.keys()[0] != "relax1": d["state"] = "successful" if d2["has_vasp_completed"] \ else "unsuccessful" else: d["state"] = "stopped" s = Structure.from_dict(d2["output"]["crystal"]) if not s.is_valid(): d["state"] = "errored_bad_structure" d["analysis"] = get_basic_analysis_and_error_checks(d) sg = SymmetryFinder(Structure.from_dict(d["output"]["crystal"]), 0.1) d["spacegroup"] = { "symbol": sg.get_spacegroup_symbol(), "number": sg.get_spacegroup_number(), "point_group": unicode(sg.get_point_group(), errors="ignore"), "source": "spglib", "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall() } d["last_updated"] = datetime.datetime.today() # Process NEB runs. The energy and magnetic moments for each image are listed. # Some useful values are calculated. # Number of NEB images print "TTM DEBUG: At NEB processing stage." image_list = [] for i in xrange(0, 9): append = "0" + str(i) newpath = os.path.join(fullpath, append) if os.path.exists(newpath): image_list.append(newpath) d["num_images"] = len(image_list) print "TTM DEBUG: Image list:", image_list # Image energies and magnetic moments for specific folders list_image_energies = [] list_image_mags = [] for i in xrange(0, len(image_list)): append = "0" + str(i) oszicar = os.path.join(fullpath, append, "OSZICAR") if not os.path.isfile(oszicar): return None val_energy = util2.getEnergy(oszicar) val_mag = util2.getMag(oszicar) d["E_" + append] = val_energy d["mag_" + append] = val_mag list_image_energies.append(val_energy) list_image_mags.append(val_mag) print "TTM DEBUG: first occurrence list_image_mags", list_image_mags # List of image energies and magnetic moments in order image_energies = ' '.join(map(str, list_image_energies)) d["image_energies"] = image_energies # An simple way to visualize relative image energies and magnetic moments energy_contour = "-x-" if len(image_list) == 0: return None for i in xrange(1, len(image_list)): if (list_image_energies[i] > list_image_energies[i - 1]): energy_contour += "/-x-" elif list_image_energies[i] < list_image_energies[i - 1]: energy_contour += "\\-x-" else: energy_contour += "=-x-" d["energy_contour"] = energy_contour print "TTM DEBUG: energy contour:", energy_contour # Difference between the first and maximum energies and magnetic moments deltaE_firstmax = max(list_image_energies) - list_image_energies[0] d["deltaE_firstmax"] = deltaE_firstmax # Difference between the last and maximum energies and magnetic moments deltaE_lastmax = max(list_image_energies) - list_image_energies[-1] d["deltaE_lastmax"] = deltaE_lastmax # Difference between the endpoint energies and magnetic moments deltaE_endpoints = list_image_energies[-1] - list_image_energies[0] d["deltaE_endpoints"] = deltaE_endpoints # Difference between the minimum and maximum energies and magnetic moments deltaE_maxmin = max(list_image_energies) - min(list_image_energies) d["deltaE_maxmin"] = deltaE_maxmin #INDENT THE NEXT LINES: if not (list_image_mags[0] == None): #if ISPIN not 2, no mag info image_mags = ' '.join(map(str, list_image_mags)) d["image_mags"] = image_mags mag_contour = "-o-" if len(image_list) == 0: return None for i in xrange(1, len(image_list)): if (list_image_mags[i] > list_image_mags[i - 1]): mag_contour += "/-o-" elif list_image_mags[i] < list_image_mags[i - 1]: mag_contour += "\\-o-" else: mag_contour += "=-o-" d["mag_contour"] = mag_contour deltaM_firstmax = max(list_image_mags) - list_image_mags[0] d["deltaM_firstmax"] = deltaM_firstmax deltaM_lastmax = max(list_image_mags) - list_image_mags[-1] d["deltaM_lastmax"] = deltaM_lastmax deltaM_endpoints = list_image_mags[-1] - list_image_mags[0] d["deltaM_endpoints"] = deltaM_endpoints deltaM_maxmin = max(list_image_mags) - min(list_image_mags) d["deltaM_maxmin"] = deltaM_maxmin d["type"] = "NEB" return d except Exception as ex: logger.error("Error in " + os.path.abspath(dir_name) + ".\nError msg: " + str(ex)) return None
def get_valences(self, structure): """ Returns a list of valences for the structure. This currently works only for ordered structures only. Args: structure: Structure to analyze Returns: A list of valences for each site in the structure, e.g., [1, 1, -2]. Raises: A ValueError is the valences cannot be determined. """ els = [Element(el.symbol) for el in structure.composition.elements] if not set(els).issubset(set(BV_PARAMS.keys())): raise ValueError( "Structure contains elements not in set of BV parameters!") #Perform symmetry determination and get sites grouped by symmetry. if self.symm_tol: finder = SymmetryFinder(structure, self.symm_tol) symm_structure = finder.get_symmetrized_structure() equi_sites = symm_structure.equivalent_sites else: equi_sites = [[site] for site in structure] #Sort the equivalent sites by decreasing electronegativity. equi_sites = sorted( equi_sites, key=lambda sites: -sites[0].species_and_occu.average_electroneg) #Get a list of valences and probabilities for each symmetrically #distinct site. valences = [] all_prob = [] for sites in equi_sites: test_site = sites[0] nn = structure.get_neighbors(test_site, self.max_radius) prob = self._calc_site_probabilities(test_site, nn) all_prob.append(prob) val = list(prob.keys()) #Sort valences in order of decreasing probability. val = sorted(val, key=lambda v: -prob[v]) #Retain probabilities that are at least 1/100 of highest prob. valences.append( filter(lambda v: prob[v] > 0.01 * prob[val[0]], val)) #make variables needed for recursion nsites = np.array(map(len, equi_sites)) vmin = np.array(map(min, valences)) vmax = np.array(map(max, valences)) self._n = 0 self._best_score = 0 self._best_vset = None def evaluate_assignment(v_set): el_oxi = collections.defaultdict(list) for i, sites in enumerate(equi_sites): el_oxi[sites[0].specie.symbol].append(v_set[i]) max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if max_diff > 1: return score = reduce(operator.mul, [all_prob[i][v] for i, v in enumerate(v_set)]) if score > self._best_score: self._best_vset = v_set self._best_score = score def _recurse(assigned=[]): #recurses to find permutations of valences based on whether a #charge balanced assignment can still be found if self._n > self.max_permutations: return i = len(assigned) highest = vmax.copy() highest[:i] = assigned highest *= nsites highest = np.sum(highest) lowest = vmin.copy() lowest[:i] = assigned lowest *= nsites lowest = np.sum(lowest) if highest < 0 or lowest > 0: self._n += 1 return if i == len(valences): evaluate_assignment(assigned) self._n += 1 return else: for v in valences[i]: new_assigned = list(assigned) _recurse(new_assigned + [v]) _recurse() if self._best_vset: assigned = {} for val, sites in zip(self._best_vset, equi_sites): for site in sites: assigned[site] = val return [int(assigned[site]) for site in structure] else: raise ValueError("Valences cannot be assigned!")
class HighSymmKpath(object): """ This class looks for path along high symmetry lines in the Brillouin Zone. It is based on Setyawan, W., & Curtarolo, S. (2010). High-throughput electronic band structure calculations: Challenges and tools. Computational Materials Science, 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010 The symmetry is determined by spglib through the SymmetryFinder class """ def __init__(self, structure, symprec=0.01, angle_tolerance=5): """ Args: structure: Structure object symprec: Tolerance for symmetry finding angle_tolerance: Angle tolerance for symmetry finding. """ self._structure = structure self._sym = SymmetryFinder(structure, symprec=symprec, angle_tolerance=angle_tolerance) self._prim = self._sym\ .get_primitive_standard_structure() self._conv = self._sym.get_conventional_standard_structure() self._prim_rec = self._prim.lattice.reciprocal_lattice self._kpath = None lattice_type = self._sym.get_lattice_type() spg_symbol = self._sym.get_spacegroup_symbol() if lattice_type == "cubic": if "P" in spg_symbol: self._kpath = self.cubic() elif "F" in spg_symbol: self._kpath = self.fcc() elif "I" in spg_symbol: self._kpath = self.bcc() else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "tetragonal": if "P" in spg_symbol: self._kpath = self.tet() elif "I" in spg_symbol: a = self._conv.lattice.abc[0] c = self._conv.lattice.abc[2] if c < a: self._kpath = self.bctet1(c, a) else: self._kpath = self.bctet2(c, a) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "orthorhombic": a = self._conv.lattice.abc[0] b = self._conv.lattice.abc[1] c = self._conv.lattice.abc[2] if "P" in spg_symbol: self._kpath = self.orc() elif "F" in spg_symbol: if 1 / a ** 2 > 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf1(a, b, c) elif 1 / a ** 2 < 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf2(a, b, c) else: self._kpath = self.orcf3(a, b, c) elif "I" in spg_symbol: self._kpath = self.orci(a, b, c) elif "C" in spg_symbol: self._kpath = self.orcc(a, b, c) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "hexagonal": self._kpath = self.hex() elif lattice_type == "rhombohedral": alpha = self._prim.lattice.lengths_and_angles[1][0] if alpha < 90: self._kpath = self.rhl1(alpha * pi / 180) else: self._kpath = self.rhl2(alpha * pi / 180) elif lattice_type == "monoclinic": a, b, c = self._conv.lattice.abc alpha = self._conv.lattice.lengths_and_angles[1][0] if "P" in spg_symbol: self._kpath = self.mcl(b, c, alpha * pi / 180) elif "C" in spg_symbol: kgamma = self._prim_rec.lengths_and_angles[1][2] if kgamma > 90: self._kpath = self.mclc1(a, b, c, alpha * pi / 180) if kgamma == 90: self._kpath = self.mclc2(a, b, c, alpha * pi / 180) if kgamma < 90: if b * cos(alpha * pi / 180) / c\ + b ** 2 * sin(alpha) ** 2 / a ** 2 < 1: self._kpath = self.mclc3(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha) ** 2 / a ** 2 == 1: self._kpath = self.mclc4(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha) ** 2 / a ** 2 > 1: self._kpath = self.mclc5(a, b, c, alpha * pi / 180) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "triclinic": kalpha = self._prim_rec.lengths_and_angles[1][0] kbeta = self._prim_rec.lengths_and_angles[1][1] kgamma = self._prim_rec.lengths_and_angles[1][2] if kalpha > 90 and kbeta > 90 and kgamma > 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma < 90: self._kpath = self.trib() if kalpha > 90 and kbeta > 90 and kgamma == 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma == 90: self._kpath = self.trib() else: warn("Unknown lattice type %s" % lattice_type) @property def structure(self): """ Returns: The standardized primitive structure """ return self._prim @property def kpath(self): """ Returns: The symmetry line path in reciprocal space """ return self._kpath def get_kpoints(self, line_density=20): """ Returns: the kpoints along the paths in cartesian coordinates together with the labels for symmetry points -Wei """ list_k_points = [] sym_point_labels = [] for b in self.kpath['path']: for i in range(1, len(b)): start = np.array(self.kpath['kpoints'][b[i - 1]]) end = np.array(self.kpath['kpoints'][b[i]]) distance = np.linalg.norm( self._prim_rec.get_cartesian_coords(start) - self._prim_rec.get_cartesian_coords(end)) nb = int(ceil(distance * line_density)) sym_point_labels.extend([b[i - 1]] + [''] * (nb - 1) + [b[i]]) list_k_points.extend( [self._prim_rec.get_cartesian_coords(start) + float(i) / float(nb) * (self._prim_rec.get_cartesian_coords(end) - self._prim_rec.get_cartesian_coords(start)) for i in range(0, nb + 1)]) return list_k_points, sym_point_labels def get_kpath_plot(self, **kwargs): """ Gives the plot (as a matplotlib object) of the symmetry line path in the Brillouin Zone. Returns: `matplotlib` figure. ================ ============================================================== kwargs Meaning ================ ============================================================== show True to show the figure (Default). savefig 'abc.png' or 'abc.eps'* to save the figure to a file. ================ ============================================================== """ import itertools import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d def _plot_shape_skeleton(bz, style): for iface in range(len(bz)): for line in itertools.combinations(bz[iface], 2): for jface in range(len(bz)): if iface < jface and line[0] in bz[jface]\ and line[1] in bz[jface]: ax.plot([line[0][0], line[1][0]], [line[0][1], line[1][1]], [line[0][2], line[1][2]], style) def _plot_lattice(lattice): vertex1 = lattice.get_cartesian_coords([0.0, 0.0, 0.0]) vertex2 = lattice.get_cartesian_coords([1.0, 0.0, 0.0]) ax.plot([vertex1[0], vertex2[0]], [vertex1[1], vertex2[1]], [vertex1[2], vertex2[2]], color='g', linewidth=3) vertex2 = lattice.get_cartesian_coords([0.0, 1.0, 0.0]) ax.plot([vertex1[0], vertex2[0]], [vertex1[1], vertex2[1]], [vertex1[2], vertex2[2]], color='g', linewidth=3) vertex2 = lattice.get_cartesian_coords([0.0, 0.0, 1.0]) ax.plot([vertex1[0], vertex2[0]], [vertex1[1], vertex2[1]], [vertex1[2], vertex2[2]], color='g', linewidth=3) def _plot_kpath(kpath, lattice): for line in kpath['path']: for k in range(len(line) - 1): vertex1 = lattice.get_cartesian_coords(kpath['kpoints'] [line[k]]) vertex2 = lattice.get_cartesian_coords(kpath['kpoints'] [line[k + 1]]) ax.plot([vertex1[0], vertex2[0]], [vertex1[1], vertex2[1]], [vertex1[2], vertex2[2]], color='r', linewidth=3) def _plot_labels(kpath, lattice): for k in kpath['kpoints']: label = k if k.startswith("\\") or k.find("_") != -1: label = "$" + k + "$" off = 0.01 ax.text(lattice.get_cartesian_coords(kpath['kpoints'][k])[0] + off, lattice.get_cartesian_coords(kpath['kpoints'][k])[1] + off, lattice.get_cartesian_coords(kpath['kpoints'][k])[2] + off, label, color='b', size='25') ax.scatter([lattice.get_cartesian_coords( kpath['kpoints'][k])[0]], [lattice.get_cartesian_coords( kpath['kpoints'][k])[1]], [lattice.get_cartesian_coords( kpath['kpoints'][k])[2]], color='b') fig = plt.figure() ax = axes3d.Axes3D(fig) _plot_lattice(self._prim_rec) _plot_shape_skeleton(self._prim_rec.get_wigner_seitz_cell(), '-k') _plot_kpath(self.kpath, self._prim_rec) _plot_labels(self.kpath, self._prim_rec) ax.axis("off") show = kwargs.pop("show", True) if show: plt.show() savefig = kwargs.pop("savefig", None) if savefig: fig.savefig(savefig) return fig def cubic(self): self.name = "CUB" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'X': np.array([0.0, 0.5, 0.0]), 'R': np.array([0.5, 0.5, 0.5]), 'M': np.array([0.5, 0.5, 0.0])} path = [["\Gamma", "X", "M", "\Gamma", "R", "X"], ["M", "R"]] return {'kpoints': kpoints, 'path': path} def fcc(self): self.name = "FCC" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'K': np.array([3.0 / 8.0, 3.0 / 8.0, 3.0 / 4.0]), 'L': np.array([0.5, 0.5, 0.5]), 'U': np.array([5.0 / 8.0, 1.0 / 4.0, 5.0 / 8.0]), 'W': np.array([0.5, 1.0 / 4.0, 3.0 / 4.0]), 'X': np.array([0.5, 0.0, 0.5])} path = [["\Gamma", "X", "W", "K", "\Gamma", "L", "U", "W", "L", "K"], ["U", "X"]] return {'kpoints': kpoints, 'path': path} def bcc(self): self.name = "BCC" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'H': np.array([0.5, -0.5, 0.5]), 'P': np.array([0.25, 0.25, 0.25]), 'N': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "H", "N", "\Gamma", "P", "H"], ["P", "N"]] return {'kpoints': kpoints, 'path': path} def tet(self): self.name = "TET" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([0.5, 0.5, 0.5]), 'M': np.array([0.5, 0.5, 0.0]), 'R': np.array([0.0, 0.5, 0.5]), 'X': np.array([0.0, 0.5, 0.0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "X", "M", "\Gamma", "Z", "R", "A", "Z"], ["X", "R"], ["M", "A"]] return {'kpoints': kpoints, 'path': path} def bctet1(self, c, a): self.name = "BCT1" eta = (1 + c ** 2 / a ** 2) / 4.0 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'M': np.array([-0.5, 0.5, 0.5]), 'N': np.array([0.0, 0.5, 0.0]), 'P': np.array([0.25, 0.25, 0.25]), 'X': np.array([0.0, 0.0, 0.5]), 'Z': np.array([eta, eta, -eta]), 'Z_1': np.array([-eta, 1 - eta, eta])} path = [["\Gamma", "X", "M", "\Gamma", "Z", "P", "N", "Z_1", "M"], ["X", "P"]] return {'kpoints': kpoints, 'path': path} def bctet2(self, c, a): self.name = "BCT2" eta = (1 + a ** 2 / c ** 2) / 4.0 zeta = a ** 2 / (2 * c ** 2) kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'N': np.array([0.0, 0.5, 0.0]), 'P': np.array([0.25, 0.25, 0.25]), '\Sigma': np.array([-eta, eta, eta]), '\Sigma_1': np.array([eta, 1 - eta, -eta]), 'X': np.array([0.0, 0.0, 0.5]), 'Y': np.array([-zeta, zeta, 0.5]), 'Y_1': np.array([0.5, 0.5, -zeta]), 'Z': np.array([0.5, 0.5, -0.5])} path = [["\Gamma", "X", "Y", "\Sigma", "\Gamma", "Z", "\Sigma_1", "N", "P", "Y_1", "Z"], ["X", "P"]] return {'kpoints': kpoints, 'path': path} def orc(self): self.name = "ORC" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'R': np.array([0.5, 0.5, 0.5]), 'S': np.array([0.5, 0.5, 0.0]), 'T': np.array([0.0, 0.5, 0.5]), 'U': np.array([0.5, 0.0, 0.5]), 'X': np.array([0.5, 0.0, 0.0]), 'Y': np.array([0.0, 0.5, 0.0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "X", "S", "Y", "\Gamma", "Z", "U", "R", "T", "Z"], ["Y", "T"], ["U", "X"], ["S", "R"]] return {'kpoints': kpoints, 'path': path} def orcf1(self, a, b, c): self.name = "ORCF1" zeta = (1 + a ** 2 / b ** 2 - a ** 2 / c ** 2) / 4 eta = (1 + a ** 2 / b ** 2 + a ** 2 / c ** 2) / 4 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([0.5, 0.5 + zeta, zeta]), 'A_1': np.array([0.5, 0.5 - zeta, 1 - zeta]), 'L': np.array([0.5, 0.5, 0.5]), 'T': np.array([1, 0.5, 0.5]), 'X': np.array([0.0, eta, eta]), 'X_1': np.array([1, 1 - eta, 1 - eta]), 'Y': np.array([0.5, 0.0, 0.5]), 'Z': np.array([0.5, 0.5, 0.0])} path = [["\Gamma", "Y", "T", "Z", "\Gamma", "X", "A_1", "Y"], ["T", "X_1"], ["X", "A", "Z"], ["L", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def orcf2(self, a, b, c): self.name = "ORCF2" phi = (1 + c ** 2 / b ** 2 - c ** 2 / a ** 2) / 4 eta = (1 + a ** 2 / b ** 2 - a ** 2 / c ** 2) / 4 delta = (1 + b ** 2 / a ** 2 - b ** 2 / c ** 2) / 4 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'C': np.array([0.5, 0.5 - eta, 1 - eta]), 'C_1': np.array([0.5, 0.5 + eta, eta]), 'D': np.array([0.5 - delta, 0.5, 1 - delta]), 'D_1': np.array([0.5 + delta, 0.5, delta]), 'L': np.array([0.5, 0.5, 0.5]), 'H': np.array([1 - phi, 0.5 - phi, 0.5]), 'H_1': np.array([phi, 0.5 + phi, 0.5]), 'X': np.array([0.0, 0.5, 0.5]), 'Y': np.array([0.5, 0.0, 0.5]), 'Z': np.array([0.5, 0.5, 0.0])} path = [["\Gamma", "Y", "C", "D", "X", "\Gamma", "Z", "D_1", "H", "C"], ["C_1", "Z"], ["X", "H_1"], ["H", "Y"], ["L", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def orcf3(self, a, b, c): self.name = "ORCF3" zeta = (1 + a ** 2 / b ** 2 - a ** 2 / c ** 2) / 4 eta = (1 + a ** 2 / b ** 2 + a ** 2 / c ** 2) / 4 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([0.5, 0.5 + zeta, zeta]), 'A_1': np.array([0.5, 0.5 - zeta, 1 - zeta]), 'L': np.array([0.5, 0.5, 0.5]), 'T': np.array([1, 0.5, 0.5]), 'X': np.array([0.0, eta, eta]), 'X_1': np.array([1, 1 - eta, 1 - eta]), 'Y': np.array([0.5, 0.0, 0.5]), 'Z': np.array([0.5, 0.5, 0.0])} path = [["\Gamma", "Y", "T", "Z", "\Gamma", "X", "A_1", "Y"], ["X", "A", "Z"], ["L", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def orci(self, a, b, c): self.name = "ORCI" zeta = (1 + a ** 2 / c ** 2) / 4 eta = (1 + b ** 2 / c ** 2) / 4 delta = (b ** 2 - a ** 2) / (4 * c ** 2) mu = (a ** 2 + b ** 2) / (4 * c ** 2) kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'L': np.array([-mu, mu, 0.5 - delta]), 'L_1': np.array([mu, -mu, 0.5 + delta]), 'L_2': np.array([0.5 - delta, 0.5 + delta, -mu]), 'R': np.array([0.0, 0.5, 0.0]), 'S': np.array([0.5, 0.0, 0.0]), 'T': np.array([0.0, 0.0, 0.5]), 'W': np.array([0.25, 0.25, 0.25]), 'X': np.array([-zeta, zeta, zeta]), 'X_1': np.array([zeta, 1 - zeta, -zeta]), 'Y': np.array([eta, -eta, eta]), 'Y_1': np.array([1 - eta, eta, -eta]), 'Z': np.array([0.5, 0.5, -0.5])} path = [["\Gamma", "X", "L", "T", "W", "R", "X_1", "Z", "\Gamma", "Y", "S", "W"], ["L_1", "Y"], ["Y_1", "Z"]] return {'kpoints': kpoints, 'path': path} def orcc(self, a, b, c): self.name = "ORCC" zeta = (1 + a ** 2 / b ** 2) / 4 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([zeta, zeta, 0.5]), 'A_1': np.array([-zeta, 1 - zeta, 0.5]), 'R': np.array([0.0, 0.5, 0.5]), 'S': np.array([0.0, 0.5, 0.0]), 'T': np.array([-0.5, 0.5, 0.5]), 'X': np.array([zeta, zeta, 0.0]), 'X_1': np.array([-zeta, 1 - zeta, 0.0]), 'Y': np.array([-0.5, 0.5, 0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "X", "S", "R", "A", "Z", "\Gamma", "Y", "X_1", "A_1", "T", "Y"], ["Z", "T"]] return {'kpoints': kpoints, 'path': path} def hex(self): self.name = "HEX" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([0.0, 0.0, 0.5]), 'H': np.array([1.0 / 3.0, 1.0 / 3.0, 0.5]), 'K': np.array([1.0 / 3.0, 1.0 / 3.0, 0.0]), 'L': np.array([0.5, 0.0, 0.5]), 'M': np.array([0.5, 0.0, 0.0])} path = [["\Gamma", "M", "K", "\Gamma", "A", "L", "H", "A"], ["L", "M"], ["K", "H"]] return {'kpoints': kpoints, 'path': path} def rhl1(self, alpha): self.name = "RHL1" eta = (1 + 4 * cos(alpha)) / (2 + 4 * cos(alpha)) nu = 3.0 / 4.0 - eta / 2.0 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'B': np.array([eta, 0.5, 1.0 - eta]), 'B_1': np.array([1.0 / 2.0, 1.0 - eta, eta - 1.0]), 'F': np.array([0.5, 0.5, 0.0]), 'L': np.array([0.5, 0.0, 0.0]), 'L_1': np.array([0.0, 0.0, -0.5]), 'P': np.array([eta, nu, nu]), 'P_1': np.array([1.0 - nu, 1.0 - nu, 1.0 - eta]), 'P_2': np.array([nu, nu, eta - 1.0]), 'Q': np.array([1.0 - nu, nu, 0.0]), 'X': np.array([nu, 0.0, -nu]), 'Z': np.array([0.5, 0.5, 0.5])} path = [["\Gamma", "L", "B_1"], ["B", "Z", "\Gamma", "X"], ["Q", "F", "P_1", "Z"], ["L", "P"]] return {'kpoints': kpoints, 'path': path} def rhl2(self, alpha): self.name = "RHL2" eta = 1 / (2 * tan(alpha / 2.0) ** 2) nu = 3.0 / 4.0 - eta / 2.0 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'F': np.array([0.5, -0.5, 0.0]), 'L': np.array([0.5, 0.0, 0.0]), 'P': np.array([1 - nu, -nu, 1 - nu]), 'P_1': np.array([nu, nu - 1.0, nu - 1.0]), 'Q': np.array([eta, eta, eta]), 'Q_1': np.array([1.0 - eta, -eta, -eta]), 'Z': np.array([0.5, -0.5, 0.5])} path = [["\Gamma", "P", "Z", "Q", "\Gamma", "F", "P_1", "Q_1", "L", "Z"]] return {'kpoints': kpoints, 'path': path} def mcl(self, b, c, alpha): self.name = "MCL" eta = (1 - b * cos(alpha) / c) / (2 * sin(alpha) ** 2) nu = 0.5 - eta * c * cos(alpha) / b kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'A': np.array([0.5, 0.5, 0.0]), 'C': np.array([0.0, 0.5, 0.5]), 'D': np.array([0.5, 0.0, 0.5]), 'D_1': np.array([0.5, 0.5, -0.5]), 'E': np.array([0.5, 0.5, 0.5]), 'H': np.array([0.0, eta, 1.0 - nu]), 'H_1': np.array([0.0, 1.0 - eta, nu]), 'H_2': np.array([0.0, eta, -nu]), 'M': np.array([0.5, eta, 1.0 - nu]), 'M_1': np.array([0.5, 1 - eta, nu]), 'M_2': np.array([0.5, 1 - eta, nu]), 'X': np.array([0.0, 0.5, 0.0]), 'Y': np.array([0.0, 0.0, 0.5]), 'Y_1': np.array([0.0, 0.0, -0.5]), 'Z': np.array([0.5, 0.0, 0.0])} path = [["\Gamma", "Y", "H", "C", "E", "M_1", "A", "X", "H_1"], ["M", "D", "Z"], ["Y", "D"]] return {'kpoints': kpoints, 'path': path} def mclc1(self, a, b, c, alpha): self.name = "MCLC1" zeta = (2 - b * cos(alpha) / c) / (4 * sin(alpha) ** 2) eta = 0.5 + 2 * zeta * c * cos(alpha) / b psi = 0.75 - a ** 2 / (4 * b ** 2 * sin(alpha) ** 2) phi = psi + (0.75 - psi) * b * cos(alpha) / c kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'N': np.array([0.5, 0.0, 0.0]), 'N_1': np.array([0.0, -0.5, 0.0]), 'F': np.array([1 - zeta, 1 - zeta, 1 - eta]), 'F_1': np.array([zeta, zeta, eta]), 'F_2': np.array([-zeta, -zeta, 1 - eta]), #'F_3': np.array([1 - zeta, -zeta, 1 - eta]), 'I': np.array([phi, 1 - phi, 0.5]), 'I_1': np.array([1 - phi, phi - 1, 0.5]), 'L': np.array([0.5, 0.5, 0.5]), 'M': np.array([0.5, 0.0, 0.5]), 'X': np.array([1 - psi, psi - 1, 0.0]), 'X_1': np.array([psi, 1 - psi, 0.0]), 'X_2': np.array([psi - 1, -psi, 0.0]), 'Y': np.array([0.5, 0.5, 0.0]), 'Y_1': np.array([-0.5, -0.5, 0.0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "Y", "F", "L", "I"], ["I_1", "Z", "F_1"], ["Y", "X_1"], ["X", "\Gamma", "N"], ["M", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def mclc2(self, a, b, c, alpha): self.name = "MCLC2" zeta = (2 - b * cos(alpha) / c) / (4 * sin(alpha) ** 2) eta = 0.5 + 2 * zeta * c * cos(alpha) / b psi = 0.75 - a ** 2 / (4 * b ** 2 * sin(alpha) ** 2) phi = psi + (0.75 - psi) * b * cos(alpha) / c kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'N': np.array([0.5, 0.0, 0.0]), 'N_1': np.array([0.0, -0.5, 0.0]), 'F': np.array([1 - zeta, 1 - zeta, 1 - eta]), 'F_1': np.array([zeta, zeta, eta]), 'F_2': np.array([-zeta, -zeta, 1 - eta]), 'F_3': np.array([1 - zeta, -zeta, 1 - eta]), 'I': np.array([phi, 1 - phi, 0.5]), 'I_1': np.array([1 - phi, phi - 1, 0.5]), 'L': np.array([0.5, 0.5, 0.5]), 'M': np.array([0.5, 0.0, 0.5]), 'X': np.array([1 - psi, psi - 1, 0.0]), 'X_1': np.array([psi, 1 - psi, 0.0]), 'X_2': np.array([psi - 1, -psi, 0.0]), 'Y': np.array([0.5, 0.5, 0.0]), 'Y_1': np.array([-0.5, -0.5, 0.0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "Y", "F", "L", "I"], ["I_1", "Z", "F_1"], ["N", "\Gamma", "M"]] return {'kpoints': kpoints, 'path': path} def mclc3(self, a, b, c, alpha): self.name = "MCLC3" mu = (1 + b ** 2 / a ** 2) / 4.0 delta = b * c * cos(alpha) / (2 * a ** 2) zeta = mu - 0.25 + (1 - b * cos(alpha) / c)\ / (4 * sin(alpha) ** 2) eta = 0.5 + 2 * zeta * c * cos(alpha) / b phi = 1 + zeta - 2 * mu psi = eta - 2 * delta kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'F': np.array([1 - phi, 1 - phi, 1 - psi]), 'F_1': np.array([phi, phi - 1, psi]), 'F_2': np.array([1 - phi, -phi, 1 - psi]), 'H': np.array([zeta, zeta, eta]), 'H_1': np.array([1 - zeta, -zeta, 1 - eta]), 'H_2': np.array([-zeta, -zeta, 1 - eta]), 'I': np.array([0.5, -0.5, 0.5]), 'M': np.array([0.5, 0.0, 0.5]), 'N': np.array([0.5, 0.0, 0.0]), 'N_1': np.array([0.0, -0.5, 0.0]), 'X': np.array([0.5, -0.5, 0.0]), 'Y': np.array([mu, mu, delta]), 'Y_1': np.array([1 - mu, -mu, -delta]), 'Y_2': np.array([-mu, -mu, -delta]), 'Y_3': np.array([mu, mu - 1, delta]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "Y", "F", "H", "Z", "I", "F_1"], ["H_1", "Y_1", "X", "\Gamma", "N"], ["M", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def mclc4(self, a, b, c, alpha): self.name = "MCLC4" mu = (1 + b ** 2 / a ** 2) / 4.0 delta = b * c * cos(alpha) / (2 * a ** 2) zeta = mu - 0.25 + (1 - b * cos(alpha) / c)\ / (4 * sin(alpha) ** 2) eta = 0.5 + 2 * zeta * c * cos(alpha) / b phi = 1 + zeta - 2 * mu psi = eta - 2 * delta kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'F': np.array([1 - phi, 1 - phi, 1 - psi]), 'F_1': np.array([phi, phi - 1, psi]), 'F_2': np.array([1 - phi, -phi, 1 - psi]), 'H': np.array([zeta, zeta, eta]), 'H_1': np.array([1 - zeta, -zeta, 1 - eta]), 'H_2': np.array([-zeta, -zeta, 1 - eta]), 'I': np.array([0.5, -0.5, 0.5]), 'M': np.array([0.5, 0.0, 0.5]), 'N': np.array([0.5, 0.0, 0.0]), 'N_1': np.array([0.0, -0.5, 0.0]), 'X': np.array([0.5, -0.5, 0.0]), 'Y': np.array([mu, mu, delta]), 'Y_1': np.array([1 - mu, -mu, -delta]), 'Y_2': np.array([-mu, -mu, -delta]), 'Y_3': np.array([mu, mu - 1, delta]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "Y", "F", "H", "Z", "I"], ["H_1", "Y_1", "X", "\Gamma", "N"], ["M", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def mclc5(self, a, b, c, alpha): self.name = "MCLC5" zeta = (b ** 2 / a ** 2 + (1 - b * cos(alpha) / c) / sin(alpha) ** 2) / 4 eta = 0.5 + 2 * zeta * c * cos(alpha) / b mu = eta / 2 + b ** 2 / (4 * a ** 2) \ - b * c * cos(alpha) / (2 * a ** 2) nu = 2 * mu - zeta rho = 1 - zeta * a ** 2 / b ** 2 omega = (4 * nu - 1 - b ** 2 * sin(alpha) ** 2 / a ** 2)\ * c / (2 * b * cos(alpha)) delta = zeta * c * cos(alpha) / b + omega / 2 - 0.25 kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'F': np.array([nu, nu, omega]), 'F_1': np.array([1 - nu, 1 - nu, 1 - omega]), 'F_2': np.array([nu, nu - 1, omega]), 'H': np.array([zeta, zeta, eta]), 'H_1': np.array([1 - zeta, -zeta, 1 - eta]), 'H_2': np.array([-zeta, -zeta, 1 - eta]), 'I': np.array([rho, 1 - rho, 0.5]), 'I_1': np.array([1 - rho, rho - 1, 0.5]), 'L': np.array([0.5, 0.5, 0.5]), 'M': np.array([0.5, 0.0, 0.5]), 'N': np.array([0.5, 0.0, 0.0]), 'N_1': np.array([0.0, -0.5, 0.0]), 'X': np.array([0.5, -0.5, 0.0]), 'Y': np.array([mu, mu, delta]), 'Y_1': np.array([1 - mu, -mu, -delta]), 'Y_2': np.array([-mu, -mu, -delta]), 'Y_3': np.array([mu, mu - 1, delta]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["\Gamma", "Y", "F", "L", "I"], ["I_1", "Z", "H", "F_1"], ["H_1", "Y_1", "X", "\Gamma", "N"], ["M", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def tria(self): self.name = "TRI1a" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'L': np.array([0.5, 0.5, 0.0]), 'M': np.array([0.0, 0.5, 0.5]), 'N': np.array([0.5, 0.0, 0.5]), 'R': np.array([0.5, 0.5, 0.5]), 'X': np.array([0.5, 0.0, 0.0]), 'Y': np.array([0.0, 0.5, 0.0]), 'Z': np.array([0.0, 0.0, 0.5])} path = [["X", "\Gamma", "Y"], ["L", "\Gamma", "Z"], ["N", "\Gamma", "M"], ["R", "\Gamma"]] return {'kpoints': kpoints, 'path': path} def trib(self): self.name = "TRI1b" kpoints = {'\Gamma': np.array([0.0, 0.0, 0.0]), 'L': np.array([0.5, -0.5, 0.0]), 'M': np.array([0.0, 0.0, 0.5]), 'N': np.array([-0.5, -0.5, 0.5]), 'R': np.array([0.0, -0.5, 0.5]), 'X': np.array([0.0, -0.5, 0.0]), 'Y': np.array([0.5, 0.0, 0.0]), 'Z': np.array([-0.5, 0.0, 0.5])} path = [["X", "\Gamma", "Y"], ["L", "\Gamma", "Z"], ["N", "\Gamma", "M"], ["R", "\Gamma"]] return {'kpoints': kpoints, 'path': path}
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 setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg1 = SymmetryFinder(self.structure, 0.001).get_spacegroup()
def __init__(self, structure, symprec=0.01, angle_tolerance=5): """ Args: structure: Structure object symprec: Tolerance for symmetry finding angle_tolerance: Angle tolerance for symmetry finding. """ self._structure = structure self._sym = SymmetryFinder(structure, symprec=symprec, angle_tolerance=angle_tolerance) self._prim = self._sym\ .get_primitive_standard_structure() self._conv = self._sym.get_conventional_standard_structure() self._prim_rec = self._prim.lattice.reciprocal_lattice self._kpath = None lattice_type = self._sym.get_lattice_type() spg_symbol = self._sym.get_spacegroup_symbol() if lattice_type == "cubic": if "P" in spg_symbol: self._kpath = self.cubic() elif "F" in spg_symbol: self._kpath = self.fcc() elif "I" in spg_symbol: self._kpath = self.bcc() else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "tetragonal": if "P" in spg_symbol: self._kpath = self.tet() elif "I" in spg_symbol: a = self._conv.lattice.abc[0] c = self._conv.lattice.abc[2] if c < a: self._kpath = self.bctet1(c, a) else: self._kpath = self.bctet2(c, a) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "orthorhombic": a = self._conv.lattice.abc[0] b = self._conv.lattice.abc[1] c = self._conv.lattice.abc[2] if "P" in spg_symbol: self._kpath = self.orc() elif "F" in spg_symbol: if 1 / a ** 2 > 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf1(a, b, c) elif 1 / a ** 2 < 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf2(a, b, c) else: self._kpath = self.orcf3(a, b, c) elif "I" in spg_symbol: self._kpath = self.orci(a, b, c) elif "C" in spg_symbol: self._kpath = self.orcc(a, b, c) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "hexagonal": self._kpath = self.hex() elif lattice_type == "rhombohedral": alpha = self._prim.lattice.lengths_and_angles[1][0] if alpha < 90: self._kpath = self.rhl1(alpha * pi / 180) else: self._kpath = self.rhl2(alpha * pi / 180) elif lattice_type == "monoclinic": a, b, c = self._conv.lattice.abc alpha = self._conv.lattice.lengths_and_angles[1][0] if "P" in spg_symbol: self._kpath = self.mcl(b, c, alpha * pi / 180) elif "C" in spg_symbol: kgamma = self._prim_rec.lengths_and_angles[1][2] if kgamma > 90: self._kpath = self.mclc1(a, b, c, alpha * pi / 180) if kgamma == 90: self._kpath = self.mclc2(a, b, c, alpha * pi / 180) if kgamma < 90: if b * cos(alpha * pi / 180) / c\ + b ** 2 * sin(alpha) ** 2 / a ** 2 < 1: self._kpath = self.mclc3(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha) ** 2 / a ** 2 == 1: self._kpath = self.mclc4(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha) ** 2 / a ** 2 > 1: self._kpath = self.mclc5(a, b, c, alpha * pi / 180) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "triclinic": kalpha = self._prim_rec.lengths_and_angles[1][0] kbeta = self._prim_rec.lengths_and_angles[1][1] kgamma = self._prim_rec.lengths_and_angles[1][2] if kalpha > 90 and kbeta > 90 and kgamma > 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma < 90: self._kpath = self.trib() if kalpha > 90 and kbeta > 90 and kgamma == 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma == 90: self._kpath = self.trib() else: warn("Unknown lattice type %s" % lattice_type)
def __init__(self, struct, find_spacegroup=False): block = CifFile.CifBlock() latt = struct.lattice comp = struct.composition no_oxi_comp = Composition(comp.formula) spacegroup = ("P 1", 1) if find_spacegroup: sf = SymmetryFinder(struct, 0.001) spacegroup = (sf.get_spacegroup_symbol(), sf.get_spacegroup_number()) block["_symmetry_space_group_name_H-M"] = spacegroup[0] for cell_attr in ['a', 'b', 'c']: block["_cell_length_" + cell_attr] = str(getattr(latt, cell_attr)) for cell_attr in ['alpha', 'beta', 'gamma']: block["_cell_angle_" + cell_attr] = float(getattr(latt, cell_attr)) block["_chemical_name_systematic"] = "Generated by pymatgen" block["_symmetry_Int_Tables_number"] = spacegroup[1] block["_chemical_formula_structural"] = str( no_oxi_comp.reduced_formula) block["_chemical_formula_sum"] = str(no_oxi_comp.formula) block["_cell_volume"] = str(latt.volume) reduced_comp = Composition.from_formula(no_oxi_comp.reduced_formula) el = no_oxi_comp.elements[0] amt = comp[el] fu = int(amt / reduced_comp[Element(el.symbol)]) block["_cell_formula_units_Z"] = str(fu) block.AddCifItem( ([["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"]], [[["1"], ["x, y, z"]]])) contains_oxidation = True try: symbol_to_oxinum = { str(el): float(el.oxi_state) for el in comp.elements } except AttributeError: symbol_to_oxinum = {el.symbol: 0 for el in comp.elements} contains_oxidation = False if contains_oxidation: block.AddCifItem( ([["_atom_type_symbol", "_atom_type_oxidation_number"]], [[symbol_to_oxinum.keys(), symbol_to_oxinum.values()]])) atom_site_type_symbol = [] atom_site_symmetry_multiplicity = [] atom_site_fract_x = [] atom_site_fract_y = [] atom_site_fract_z = [] atom_site_attached_hydrogens = [] atom_site_B_iso_or_equiv = [] atom_site_label = [] atom_site_occupancy = [] count = 1 for site in struct: for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(str(sp)) atom_site_symmetry_multiplicity.append("1") atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_attached_hydrogens.append("0") atom_site_B_iso_or_equiv.append(".") atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(str(occu)) count += 1 block["_atom_site_type_symbol"] = atom_site_type_symbol block.AddToLoop("_atom_site_type_symbol", {"_atom_site_label": atom_site_label}) block.AddToLoop("_atom_site_type_symbol", { "_atom_site_symmetry_multiplicity": atom_site_symmetry_multiplicity }) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_x": atom_site_fract_x}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_y": atom_site_fract_y}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_z": atom_site_fract_z}) block.AddToLoop( "_atom_site_type_symbol", {"_atom_site_attached_hydrogens": atom_site_attached_hydrogens}) block.AddToLoop( "_atom_site_type_symbol", {"_atom_site_B_iso_or_equiv": atom_site_B_iso_or_equiv}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_occupancy": atom_site_occupancy}) self._cf = CifFile.CifFile() # AJ says: CIF Block names cannot be more than 75 characters or you # get an Exception self._cf[comp.reduced_formula[0:74]] = block