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 _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 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 __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 __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 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 _gen_input_file(self, working_dir): """ Generate the necessary struct_enum.in file for enumlib. See enumlib documentation for details. """ coord_format = "{:.6f} {:.6f} {:.6f}" # Using symmetry finder, get the symmetrically distinct sites. fitter = SymmetryFinder(self.structure, self.symm_prec) symmetrized_structure = fitter.get_symmetrized_structure() logger.debug("Spacegroup {} ({}) with {} distinct sites".format( fitter.get_spacegroup_symbol(), fitter.get_spacegroup_number(), len(symmetrized_structure.equivalent_sites)) ) """ Enumlib doesn"t work when the number of species get too large. To simplify matters, we generate the input file only with disordered sites and exclude the ordered sites from the enumeration. The fact that different disordered sites with the exact same species may belong to different equivalent sites is dealt with by having determined the spacegroup earlier and labelling the species differently. """ # index_species and index_amounts store mappings between the indices # used in the enum input file, and the actual species and amounts. index_species = [] index_amounts = [] #Let"s group and sort the sites by symmetry. site_symmetries = [] for sites in symmetrized_structure.equivalent_sites: finder = SymmetryFinder(Structure.from_sites(sites), self.symm_prec) sgnum = finder.get_spacegroup_number() site_symmetries.append((sites, sgnum)) site_symmetries = sorted(site_symmetries, key=lambda s: s[1]) #Stores the ordered sites, which are not enumerated. min_sg_num = site_symmetries[0][1] ordered_sites = [] disordered_sites = [] coord_str = [] min_disordered_sg = 300 for (sites, sgnum) in site_symmetries: if sites[0].is_ordered: ordered_sites.append(sites) else: min_disordered_sg = min(min_disordered_sg, sgnum) sp_label = [] species = {k: v for k, v in sites[0].species_and_occu.items()} if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol: #Let us first make add a dummy element for every single #site whose total occupancies don't sum to 1. species[DummySpecie("X")] = 1 - sum(species.values()) for sp in species.keys(): if sp not in index_species: index_species.append(sp) sp_label.append(len(index_species) - 1) index_amounts.append(species[sp] * len(sites)) else: ind = index_species.index(sp) sp_label.append(ind) index_amounts[ind] += species[sp] * len(sites) sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)]) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) #It could be that some of the ordered sites has a lower symmetry than #the disordered sites. So we consider the lowest symmetry sites as #disordered in our enumeration. if min_disordered_sg > min_sg_num: logger.debug("Ordered sites have lower symmetry than disordered.") sites = ordered_sites.pop(0) index_species.append(sites[0].specie) index_amounts.append(len(sites)) sp_label = len(index_species) - 1 logger.debug("Lowest symmetry {} sites are included in enum." .format(sites[0].specie)) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) self.ordered_sites = [] for sites in ordered_sites: self.ordered_sites.extend(sites) self.index_species = index_species lattice = self.structure.lattice output = [self.structure.formula, "bulk"] for vec in lattice.matrix: output.append(coord_format.format(*vec)) output.append("{}".format(len(index_species))) output.append("{}".format(len(coord_str))) output.extend(coord_str) output.append("{} {}".format(self.min_cell_size, self.max_cell_size)) output.append(str(self.enum_precision_parameter)) output.append("partial") #To get a reasonable number of structures, we fix concentrations to the #range expected in the original structure. total_amounts = sum(index_amounts) for amt in index_amounts: conc = amt / total_amounts if abs(conc * 100 - round(conc * 100)) < 1e-5: output.append("{} {} {}".format(int(round(conc * 100)), int(round(conc * 100)), 100)) else: min_conc = int(math.floor(conc * 100)) output.append("{} {} {}".format(min_conc - 1, min_conc + 1, 100)) output.append("") logger.debug("Generated input file:\n{}".format("\n".join(output))) with open(os.path.join(working_dir, "struct_enum.in"), "w") as f: f.write("\n".join(output))
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 (for an ordered structure), e.g., [1, 1, -2] or a list of lists with the valences for each fractional element of each site in the structure (for an unordered structure), e.g., [[2, 4], [3], [-2], [-2], [-2]] Raises: A ValueError if 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 = [] if structure.is_ordered: 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)) else: full_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_unordered(test_site, nn) all_prob.append(prob) full_all_prob.extend(prob.values()) vals = [] for (elsp, occ) in get_z_ordered_elmap( test_site.species_and_occu): val = list(prob[elsp.symbol].keys()) #Sort valences in order of decreasing probability. val = sorted(val, key=lambda v: -prob[elsp.symbol][v]) # Retain probabilities that are at least 1/100 of highest # prob. vals.append( filter(lambda v: prob[elsp.symbol][v] > 0.001 * prob[elsp.symbol][val[0]], val)) valences.append(vals) #make variables needed for recursion if structure.is_ordered: 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]) else: nsites = np.array(map(len, equi_sites)) tmp = [] attrib = [] for insite, nsite in enumerate(nsites): for val in valences[insite]: tmp.append(nsite) attrib.append(insite) new_nsites = np.array(tmp) fractions = [] elements = [] for sites in equi_sites: for sp, occu in get_z_ordered_elmap(sites[0].species_and_occu): elements.append(sp.symbol) fractions.append(occu) fractions = np.array(fractions, np.float) new_valences = [] for vals in valences: for val in vals: new_valences.append(val) vmin = np.array(map(min, new_valences), np.float) vmax = np.array(map(max, new_valences), np.float) self._n = 0 self._best_score = 0 self._best_vset = None def evaluate_assignment(v_set): el_oxi = collections.defaultdict(list) jj = 0 for i, sites in enumerate(equi_sites): for specie, occu in get_z_ordered_elmap( sites[0].species_and_occu): el_oxi[specie.symbol].append(v_set[jj]) jj += 1 max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if max_diff > 2: return score = reduce(operator.mul, [all_prob[attrib[iv]][elements[iv]][vv] for iv, vv 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 *= new_nsites highest *= fractions highest = np.sum(highest) lowest = vmin.copy() lowest[:i] = assigned lowest *= new_nsites lowest *= fractions lowest = np.sum(lowest) if (highest < -self.charge_neutrality_tolerance or lowest > self.charge_neutrality_tolerance): self._n += 1 return if i == len(new_valences): evaluate_assignment(assigned) self._n += 1 return else: for v in new_valences[i]: new_assigned = list(assigned) _recurse(new_assigned + [v]) _recurse() if self._best_vset: if structure.is_ordered: 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: assigned = {} new_best_vset = [] for ii in range(len(equi_sites)): new_best_vset.append(list()) for ival, val in enumerate(self._best_vset): new_best_vset[attrib[ival]].append(val) for val, sites in zip(new_best_vset, equi_sites): for site in sites: assigned[site] = val return [[int(frac_site) for frac_site in assigned[site]] for site in structure] else: raise ValueError("Valences cannot be assigned!")
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!")
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 SymmetryFinderTest(unittest.TestCase): 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) def test_get_space_symbol(self): self.assertEqual(self.sg.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.disordered_sg.get_spacegroup_symbol(), "P4_2/nmc") self.assertEqual(self.sg3.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.sg4.get_spacegroup_symbol(), "R-3m") def test_get_space_number(self): self.assertEqual(self.sg.get_spacegroup_number(), 62) self.assertEqual(self.disordered_sg.get_spacegroup_number(), 137) self.assertEqual(self.sg4.get_spacegroup_number(), 166) def test_get_hall(self): self.assertEqual(self.sg.get_hall(), '-P 2ac 2n') self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n') def test_get_pointgroup(self): self.assertEqual(self.sg.get_point_group(), 'mmm') self.assertEqual(self.disordered_sg.get_point_group(), '4/mmm') def test_get_symmetry_dataset(self): ds = self.sg.get_symmetry_dataset() self.assertEqual(ds['international'], 'Pnma') def test_get_crystal_system(self): crystal_system = self.sg.get_crystal_system() self.assertEqual('orthorhombic', crystal_system) self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system()) def test_get_symmetry_operations(self): fracsymmops = self.sg.get_symmetry_operations() symmops = self.sg.get_symmetry_operations(True) self.assertEqual(len(symmops), 8) latt = self.structure.lattice for fop, op in zip(fracsymmops, symmops): for site in self.structure: newfrac = fop.operate(site.frac_coords) newcart = op.operate(site.coords) self.assertTrue( np.allclose(latt.get_fractional_coords(newcart), newfrac)) found = False newsite = PeriodicSite(site.species_and_occu, newcart, latt, coords_are_cartesian=True) for testsite in self.structure: if newsite.is_periodic_image(testsite, 1e-3): found = True break self.assertTrue(found) 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 test_get_symmetrized_structure(self): symm_struct = self.sg.get_symmetrized_structure() for a in symm_struct.lattice.angles: self.assertEqual(a, 90) self.assertEqual(len(symm_struct.equivalent_sites), 5) symm_struct = self.disordered_sg.get_symmetrized_structure() self.assertEqual(len(symm_struct.equivalent_sites), 8) self.assertEqual(map(len, symm_struct.equivalent_sites), [16, 4, 8, 4, 2, 8, 8, 8]) s1 = symm_struct.equivalent_sites[1][1] s2 = symm_struct[symm_struct.equivalent_indices[1][1]] self.assertEqual(s1, s2) self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1) 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 test_get_ir_reciprocal_mesh(self): grid = self.sg.get_ir_reciprocal_mesh() self.assertEquals(len(grid), 216) self.assertAlmostEquals(grid[1][0][0], 0.1) self.assertAlmostEquals(grid[1][0][1], 0.0) self.assertAlmostEquals(grid[1][0][2], 0.0) self.assertEquals(grid[1][1], 2) def test_get_conventional_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999) self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296) self.assertAlmostEqual(conv.lattice.c, 5.373703587040775) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998) self.assertAlmostEqual(conv.lattice.b, 31.437979757624728) self.assertAlmostEqual(conv.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 14.033435583000625) self.assertAlmostEqual(conv.lattice.b, 3.96052850731) self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 120) self.assertAlmostEqual(conv.lattice.a, 3.699919902005897) self.assertAlmostEqual(conv.lattice.b, 3.699919902005897) self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003) def test_get_primitive_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001) self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001) self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001) self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 105.015053349) self.assertAlmostEqual(prim.lattice.beta, 105.015053349) self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999) self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001) self.assertAlmostEqual(prim.lattice.beta, 105.856239333) self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001) self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 164.985257335) self.assertAlmostEqual(prim.lattice.a, 15.854897098324196) self.assertAlmostEqual(prim.lattice.b, 15.854897098324196) self.assertAlmostEqual(prim.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999) self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779) self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569) self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 120) self.assertAlmostEqual(prim.lattice.a, 3.699919902005897) self.assertAlmostEqual(prim.lattice.b, 3.699919902005897) self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)
def symmetry_reduced_voronoi_nodes(structure, rad_dict, high_accuracy_flag=False, symm_flag=True): """ Obtain symmetry reduced voronoi nodes using Zeo++ and pymatgen.symmetry.finder.SymmetryFinder Args: strucutre: pymatgen Structure object rad_dict: Dictionary containing radii of spcies in the structure high_accuracy_flag: Flag denotting whether to use high accuracy version of Zeo++ symm_flag: Flag denoting whether to return symmetrically distinct sites only Returns: Symmetrically distinct voronoi nodes as pymatgen Strucutre """ 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]) def cmp_memoize_last_site(f): #Compares and stores last site def not_duplicates(site1, site2): if site1.distance(site2) < 1e-5: return False else: return True cmp_memoize_last_site.cache = None def helper(x): if not cmp_memoize_last_site.cache: cmp_memoize_last_site.cache = f(x) return True y = f(x) if not_duplicates(cmp_memoize_last_site.cache, y): cmp_memoize_last_site.cache = y return True else: return False return helper @cmp_memoize_last_site def check_not_duplicates(site): return site if not symm_flag: if not high_accuracy_flag: vor_node_struct, vor_facecenter_struct = get_voronoi_nodes( structure, rad_dict) return vor_node_struct.sites, vor_facecenter_struct.sites else: # Only the nodes are from high accuracy voronoi decomposition vor_node_struct, vor_facecenter_struct = \ get_high_accuracy_voronoi_nodes(structure, rad_dict) # Before getting the symmetry, remove the duplicates vor_node_struct.sites.sort(key=lambda site: site.voronoi_radius) #print type(vor_node_struct.sites[0]) dist_sites = filter(check_not_duplicates, vor_node_struct.sites) return dist_sites, vor_facecenter_struct.sites if not high_accuracy_flag: vor_node_struct, vor_facecenter_struct = get_voronoi_nodes( structure, rad_dict) vor_node_symmetry_finder = SymmetryFinder(vor_node_struct, symprec=1e-1) vor_node_symm_struct = vor_node_symmetry_finder.get_symmetrized_structure( ) node_equiv_sites_list = vor_node_symm_struct.equivalent_sites node_dist_sites = [] for equiv_sites in node_equiv_sites_list: add_closest_equiv_site(node_dist_sites, equiv_sites) vor_fc_symmetry_finder = SymmetryFinder(vor_facecenter_struct, symprec=1e-1) vor_fc_symm_struct = vor_fc_symmetry_finder.get_symmetrized_structure() facecenter_equiv_sites_list = vor_fc_symm_struct.equivalent_sites facecenter_dist_sites = [] for equiv_sites in facecenter_equiv_sites_list: add_closest_equiv_site(facecenter_dist_sites, equiv_sites) if not facecenter_equiv_sites_list: # Fix this so doesn't arise facecenter_dist_sites = vor_facecenter_struct.sites return node_dist_sites, facecenter_dist_sites else: # Only the nodes are from high accuracy voronoi decomposition vor_node_struct, vor_facecenter_struct = \ get_high_accuracy_voronoi_nodes(structure, rad_dict) # Before getting the symmetry, remove the duplicates vor_node_struct.sites.sort(key=lambda site: site.voronoi_radius) #print type(vor_node_struct.sites[0]) dist_sites = filter(check_not_duplicates, vor_node_struct.sites) # Increase the symmetry precision to 0.25 spg = SymmetryFinder(structure, symprec=2.5e-1).get_spacegroup() # Remove symmetrically equivalent sites i = 0 while (i < len(dist_sites) - 1): sites1 = [dist_sites[i]] sites2 = [dist_sites[i + 1]] if spg.are_symmetrically_equivalent(sites1, sites2): del dist_sites[i + 1] else: i = i + 1 node_dist_sites = dist_sites vor_fc_symmetry_finder = SymmetryFinder(vor_facecenter_struct, symprec=1e-1) vor_fc_symm_struct = vor_fc_symmetry_finder.get_symmetrized_structure() facecenter_equiv_sites_list = vor_fc_symm_struct.equivalent_sites facecenter_dist_sites = [] for equiv_sites in facecenter_equiv_sites_list: add_closest_equiv_site(facecenter_dist_sites, equiv_sites) if not facecenter_equiv_sites_list: # Fix this so doesn't arise facecenter_dist_sites = vor_facecenter_struct.sites return node_dist_sites, facecenter_dist_sites
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 (for an ordered structure), e.g., [1, 1, -2] or a list of lists with the valences for each fractional element of each site in the structure (for an unordered structure), e.g., [[2, 4], [3], [-2], [-2], [-2]] Raises: A ValueError if 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 = [] if structure.is_ordered: 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)) else: full_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_unordered(test_site, nn) all_prob.append(prob) full_all_prob.extend(prob.values()) vals = [] for (elsp, occ) in get_z_ordered_elmap(test_site.species_and_occu): val = list(prob[elsp.symbol].keys()) #Sort valences in order of decreasing probability. val = sorted(val, key=lambda v: -prob[elsp.symbol][v]) # Retain probabilities that are at least 1/100 of highest # prob. vals.append( filter( lambda v: prob[elsp.symbol][v] > 0.001 * prob[ elsp.symbol][val[0]], val)) valences.append(vals) #make variables needed for recursion if structure.is_ordered: 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]) else: nsites = np.array(map(len, equi_sites)) tmp = [] attrib = [] for insite, nsite in enumerate(nsites): for val in valences[insite]: tmp.append(nsite) attrib.append(insite) new_nsites = np.array(tmp) fractions = [] elements = [] for sites in equi_sites: for sp, occu in get_z_ordered_elmap(sites[0].species_and_occu): elements.append(sp.symbol) fractions.append(occu) fractions = np.array(fractions, np.float) new_valences = [] for vals in valences: for val in vals: new_valences.append(val) vmin = np.array(map(min, new_valences), np.float) vmax = np.array(map(max, new_valences), np.float) self._n = 0 self._best_score = 0 self._best_vset = None def evaluate_assignment(v_set): el_oxi = collections.defaultdict(list) jj = 0 for i, sites in enumerate(equi_sites): for specie, occu in get_z_ordered_elmap( sites[0].species_and_occu): el_oxi[specie.symbol].append(v_set[jj]) jj += 1 max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if max_diff > 2: return score = reduce(operator.mul, [ all_prob[attrib[iv]][elements[iv]][vv] for iv, vv 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 *= new_nsites highest *= fractions highest = np.sum(highest) lowest = vmin.copy() lowest[:i] = assigned lowest *= new_nsites lowest *= fractions lowest = np.sum(lowest) if (highest < -self.charge_neutrality_tolerance or lowest > self.charge_neutrality_tolerance): self._n += 1 return if i == len(new_valences): evaluate_assignment(assigned) self._n += 1 return else: for v in new_valences[i]: new_assigned = list(assigned) _recurse(new_assigned + [v]) _recurse() if self._best_vset: if structure.is_ordered: 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: assigned = {} new_best_vset = [] for ii in range(len(equi_sites)): new_best_vset.append(list()) for ival, val in enumerate(self._best_vset): new_best_vset[attrib[ival]].append(val) for val, sites in zip(new_best_vset, equi_sites): for site in sites: assigned[site] = val return [[int(frac_site) for frac_site in assigned[site]] for site in structure] else: raise ValueError("Valences cannot be assigned!")
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)) #Based on the max allowed permutations, determine the number of #candidates per site. num_perm = 0 selected_valences = [[v.pop(0)] for v in valences] while num_perm < self.max_permutations: max_prob = 0 ind = -1 for i, v in enumerate(valences): if len(v) > 0 and all_prob[i][v[0]] > max_prob: max_prob = v[0] ind = i if ind == -1: break else: selected_valences[ind].append(valences[ind].pop(0)) num_perm = reduce(operator.mul, map(len, selected_valences)) scores = {} #Find valid valence combinations and score them by total probability. for v_set in itertools.product(*selected_valences): total = 0 el_oxi = collections.defaultdict(list) for i, sites in enumerate(equi_sites): total += v_set[i] * len(sites) el_oxi[sites[0].specie.symbol].append(v_set[i]) #Calculate the maximum range in oxidation states for each element. max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if total == 0 and max_diff <= 1: #Cell has to be charge neutral. And the maximum difference in #oxidation state for each element cannot exceed 1. score = reduce(operator.mul, [all_prob[i][v] for i, v in enumerate(v_set)]) scores[tuple(v_set)] = score if scores: best = max(scores.keys(), key=lambda k: scores[k]) assigned = {} for val, sites in zip(best, equi_sites): for site in sites: assigned[site] = val return [int(assigned[site]) for site in structure] else: raise ValueError("Valences cannot be assigned!")
def symmetry_reduced_voronoi_nodes( structure, rad_dict, high_accuracy_flag=False, symm_flag=True): """ Obtain symmetry reduced voronoi nodes using Zeo++ and pymatgen.symmetry.finder.SymmetryFinder Args: strucutre: pymatgen Structure object rad_dict: Dictionary containing radii of spcies in the structure high_accuracy_flag: Flag denotting whether to use high accuracy version of Zeo++ symm_flag: Flag denoting whether to return symmetrically distinct sites only Returns: Symmetrically distinct voronoi nodes as pymatgen Strucutre """ 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]) def cmp_memoize_last_site(f): #Compares and stores last site def not_duplicates(site1, site2): if site1.distance(site2) < 1e-5: return False else: return True cmp_memoize_last_site.cache = None def helper(x): if not cmp_memoize_last_site.cache: cmp_memoize_last_site.cache = f(x) return True y = f(x) if not_duplicates(cmp_memoize_last_site.cache, y): cmp_memoize_last_site.cache = y return True else: return False return helper @cmp_memoize_last_site def check_not_duplicates(site): return site if not symm_flag: if not high_accuracy_flag: vor_node_struct, vor_facecenter_struct = get_voronoi_nodes( structure, rad_dict) return vor_node_struct.sites, vor_facecenter_struct.sites else: # Only the nodes are from high accuracy voronoi decomposition vor_node_struct, vor_facecenter_struct = \ get_high_accuracy_voronoi_nodes(structure, rad_dict) # Before getting the symmetry, remove the duplicates vor_node_struct.sites.sort(key = lambda site: site.voronoi_radius) #print type(vor_node_struct.sites[0]) dist_sites = filter(check_not_duplicates, vor_node_struct.sites) return dist_sites, vor_facecenter_struct.sites if not high_accuracy_flag: vor_node_struct, vor_facecenter_struct = get_voronoi_nodes( structure, rad_dict) vor_node_symmetry_finder = SymmetryFinder(vor_node_struct, symprec=1e-1) vor_node_symm_struct = vor_node_symmetry_finder.get_symmetrized_structure() node_equiv_sites_list = vor_node_symm_struct.equivalent_sites node_dist_sites = [] for equiv_sites in node_equiv_sites_list: add_closest_equiv_site(node_dist_sites, equiv_sites) vor_fc_symmetry_finder = SymmetryFinder( vor_facecenter_struct, symprec=1e-1) vor_fc_symm_struct = vor_fc_symmetry_finder.get_symmetrized_structure() facecenter_equiv_sites_list = vor_fc_symm_struct.equivalent_sites facecenter_dist_sites = [] for equiv_sites in facecenter_equiv_sites_list: add_closest_equiv_site(facecenter_dist_sites, equiv_sites) if not facecenter_equiv_sites_list: # Fix this so doesn't arise facecenter_dist_sites = vor_facecenter_struct.sites return node_dist_sites, facecenter_dist_sites else: # Only the nodes are from high accuracy voronoi decomposition vor_node_struct, vor_facecenter_struct = \ get_high_accuracy_voronoi_nodes(structure, rad_dict) # Before getting the symmetry, remove the duplicates vor_node_struct.sites.sort(key = lambda site: site.voronoi_radius) #print type(vor_node_struct.sites[0]) dist_sites = filter(check_not_duplicates, vor_node_struct.sites) # Increase the symmetry precision to 0.25 spg = SymmetryFinder(structure,symprec=2.5e-1).get_spacegroup() # Remove symmetrically equivalent sites i = 0 while (i < len(dist_sites)-1): sites1 = [dist_sites[i]] sites2 = [dist_sites[i+1]] if spg.are_symmetrically_equivalent(sites1,sites2): del dist_sites[i+1] else: i = i+1 node_dist_sites = dist_sites vor_fc_symmetry_finder = SymmetryFinder( vor_facecenter_struct, symprec=1e-1) vor_fc_symm_struct = vor_fc_symmetry_finder.get_symmetrized_structure() facecenter_equiv_sites_list = vor_fc_symm_struct.equivalent_sites facecenter_dist_sites = [] for equiv_sites in facecenter_equiv_sites_list: add_closest_equiv_site(facecenter_dist_sites, equiv_sites) if not facecenter_equiv_sites_list: # Fix this so doesn't arise facecenter_dist_sites = vor_facecenter_struct.sites return node_dist_sites, facecenter_dist_sites
class SymmetryFinderTest(unittest.TestCase): 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) def test_get_space_symbol(self): self.assertEqual(self.sg.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.disordered_sg.get_spacegroup_symbol(), "P4_2/nmc") self.assertEqual(self.sg3.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.sg4.get_spacegroup_symbol(), "R-3m") def test_get_space_number(self): self.assertEqual(self.sg.get_spacegroup_number(), 62) self.assertEqual(self.disordered_sg.get_spacegroup_number(), 137) self.assertEqual(self.sg4.get_spacegroup_number(), 166) def test_get_hall(self): self.assertEqual(self.sg.get_hall(), '-P 2ac 2n') self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n') def test_get_pointgroup(self): self.assertEqual(self.sg.get_point_group(), 'mmm') self.assertEqual(self.disordered_sg.get_point_group(), '4/mmm') def test_get_symmetry_dataset(self): ds = self.sg.get_symmetry_dataset() self.assertEqual(ds['international'], 'Pnma') def test_get_crystal_system(self): crystal_system = self.sg.get_crystal_system() self.assertEqual('orthorhombic', crystal_system) self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system()) def test_get_symmetry_operations(self): fracsymmops = self.sg.get_symmetry_operations() symmops = self.sg.get_symmetry_operations(True) self.assertEqual(len(symmops), 8) latt = self.structure.lattice for fop, op in zip(fracsymmops, symmops): for site in self.structure: newfrac = fop.operate(site.frac_coords) newcart = op.operate(site.coords) self.assertTrue(np.allclose(latt.get_fractional_coords(newcart), newfrac)) found = False newsite = PeriodicSite(site.species_and_occu, newcart, latt, coords_are_cartesian=True) for testsite in self.structure: if newsite.is_periodic_image(testsite, 1e-3): found = True break self.assertTrue(found) 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 test_get_symmetrized_structure(self): symm_struct = self.sg.get_symmetrized_structure() for a in symm_struct.lattice.angles: self.assertEqual(a, 90) self.assertEqual(len(symm_struct.equivalent_sites), 5) symm_struct = self.disordered_sg.get_symmetrized_structure() self.assertEqual(len(symm_struct.equivalent_sites), 8) self.assertEqual(map(len, symm_struct.equivalent_sites), [16,4,8,4,2,8,8,8]) s1 = symm_struct.equivalent_sites[1][1] s2 = symm_struct[symm_struct.equivalent_indices[1][1]] self.assertEqual(s1, s2) self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1) 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 test_get_ir_reciprocal_mesh(self): grid=self.sg.get_ir_reciprocal_mesh() self.assertEquals(len(grid), 216) self.assertAlmostEquals(grid[1][0][0], 0.1) self.assertAlmostEquals(grid[1][0][1], 0.0) self.assertAlmostEquals(grid[1][0][2], 0.0) self.assertEquals(grid[1][1], 2) def test_get_conventional_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999) self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296) self.assertAlmostEqual(conv.lattice.c, 5.373703587040775) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998) self.assertAlmostEqual(conv.lattice.b, 31.437979757624728) self.assertAlmostEqual(conv.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 14.033435583000625) self.assertAlmostEqual(conv.lattice.b, 3.96052850731) self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 120) self.assertAlmostEqual(conv.lattice.a, 3.699919902005897) self.assertAlmostEqual(conv.lattice.b, 3.699919902005897) self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003) def test_get_primitive_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001) self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001) self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001) self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 105.015053349) self.assertAlmostEqual(prim.lattice.beta, 105.015053349) self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999) self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001) self.assertAlmostEqual(prim.lattice.beta, 105.856239333) self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001) self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 164.985257335) self.assertAlmostEqual(prim.lattice.a, 15.854897098324196) self.assertAlmostEqual(prim.lattice.b, 15.854897098324196) self.assertAlmostEqual(prim.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999) self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779) self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569) self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SymmetryFinder(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 120) self.assertAlmostEqual(prim.lattice.a, 3.699919902005897) self.assertAlmostEqual(prim.lattice.b, 3.699919902005897) self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)
def _gen_input_file(self, working_dir): """ Generate the necessary struct_enum.in file for enumlib. See enumlib documentation for details. """ coord_format = "{:.6f} {:.6f} {:.6f}" # Using symmetry finder, get the symmetrically distinct sites. fitter = SymmetryFinder(self.structure, self.symm_prec) symmetrized_structure = fitter.get_symmetrized_structure() logger.debug("Spacegroup {} ({}) with {} distinct sites".format( fitter.get_spacegroup_symbol(), fitter.get_spacegroup_number(), len(symmetrized_structure.equivalent_sites))) """ Enumlib doesn"t work when the number of species get too large. To simplify matters, we generate the input file only with disordered sites and exclude the ordered sites from the enumeration. The fact that different disordered sites with the exact same species may belong to different equivalent sites is dealt with by having determined the spacegroup earlier and labelling the species differently. """ # index_species and index_amounts store mappings between the indices # used in the enum input file, and the actual species and amounts. index_species = [] index_amounts = [] #Let"s group and sort the sites by symmetry. site_symmetries = [] for sites in symmetrized_structure.equivalent_sites: finder = SymmetryFinder(Structure.from_sites(sites), self.symm_prec) sgnum = finder.get_spacegroup_number() site_symmetries.append((sites, sgnum)) site_symmetries = sorted(site_symmetries, key=lambda s: s[1]) #Stores the ordered sites, which are not enumerated. min_sg_num = site_symmetries[0][1] ordered_sites = [] disordered_sites = [] coord_str = [] min_disordered_sg = 300 for (sites, sgnum) in site_symmetries: if sites[0].is_ordered: ordered_sites.append(sites) else: min_disordered_sg = min(min_disordered_sg, sgnum) sp_label = [] species = {k: v for k, v in sites[0].species_and_occu.items()} if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol: #Let us first make add a dummy element for every single #site whose total occupancies don't sum to 1. species[DummySpecie("X")] = 1 - sum(species.values()) for sp in species.keys(): if sp not in index_species: index_species.append(sp) sp_label.append(len(index_species) - 1) index_amounts.append(species[sp] * len(sites)) else: ind = index_species.index(sp) sp_label.append(ind) index_amounts[ind] += species[sp] * len(sites) sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)]) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) #It could be that some of the ordered sites has a lower symmetry than #the disordered sites. So we consider the lowest symmetry sites as #disordered in our enumeration. if min_disordered_sg > min_sg_num: logger.debug("Ordered sites have lower symmetry than disordered.") sites = ordered_sites.pop(0) index_species.append(sites[0].specie) index_amounts.append(len(sites)) sp_label = len(index_species) - 1 logger.debug( "Lowest symmetry {} sites are included in enum.".format( sites[0].specie)) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) self.ordered_sites = [] for sites in ordered_sites: self.ordered_sites.extend(sites) self.index_species = index_species lattice = self.structure.lattice output = [self.structure.formula, "bulk"] for vec in lattice.matrix: output.append(coord_format.format(*vec)) output.append("{}".format(len(index_species))) output.append("{}".format(len(coord_str))) output.extend(coord_str) output.append("{} {}".format(self.min_cell_size, self.max_cell_size)) output.append(str(self.enum_precision_parameter)) output.append("partial") #To get a reasonable number of structures, we fix concentrations to the #range expected in the original structure. total_amounts = sum(index_amounts) for amt in index_amounts: conc = amt / total_amounts if abs(conc * 100 - round(conc * 100)) < 1e-5: output.append("{} {} {}".format(int(round(conc * 100)), int(round(conc * 100)), 100)) else: min_conc = int(math.floor(conc * 100)) output.append("{} {} {}".format(min_conc - 1, min_conc + 1, 100)) output.append("") logger.debug("Generated input file:\n{}".format("\n".join(output))) with open(os.path.join(working_dir, "struct_enum.in"), "w") as f: f.write("\n".join(output))