def get_basic_analysis_and_error_checks(d): initial_vol = d["input"]["crystal"]["lattice"]["volume"] final_vol = d["output"]["crystal"]["lattice"]["volume"] delta_vol = final_vol - initial_vol percent_delta_vol = delta_vol / initial_vol coord_num = get_coordination_numbers(d) calc = d["calculations"][-1] gap = calc["output"]["bandgap"] cbm = calc["output"]["cbm"] vbm = calc["output"]["vbm"] is_direct = calc["output"]["is_gap_direct"] if abs(percent_delta_vol) > 0.20: warning_msgs = ["Volume change > 20%"] else: warning_msgs = [] bv_struct = Structure.from_dict(d["output"]["crystal"]) try: bva = BVAnalyzer() bv_struct = bva.get_oxi_state_decorated_structure(bv_struct) except ValueError as e: logger.error("Valence cannot be determined due to {e}." .format(e=e)) except Exception as ex: logger.error("BVAnalyzer error {e}.".format(e=str(ex))) return {"delta_volume": delta_vol, "percent_delta_volume": percent_delta_vol, "warnings": warning_msgs, "coordination_numbers": coord_num, "bandgap": gap, "cbm": cbm, "vbm": vbm, "is_gap_direct": is_direct, "bv_structure": bv_struct.to_dict}
def tersoff_potential(self, structure): """ Generate the species, tersoff potential lines for an oxide structure Args: structure: pymatgen.core.structure.Structure """ bv = BVAnalyzer() el = [site.species_string for site in structure.sites] valences = bv.get_valences(structure) el_val_dict = dict(zip(el, valences)) gin = "species \n" qerfstring = "qerfc\n" for key in el_val_dict.keys(): if key != "O" and el_val_dict[key] % 1 != 0: raise SystemError("Oxide has mixed valence on metal") specie_string = key + " core " + str(el_val_dict[key]) + "\n" gin += specie_string qerfstring += key + " " + key + " 0.6000 10.0000 \n" gin += "# noelectrostatics \n Morse \n" met_oxi_ters = Tersoff_pot().data for key in el_val_dict.keys(): if key != "O": metal = key + "(" + str(int(el_val_dict[key])) + ")" ters_pot_str = met_oxi_ters[metal] gin += ters_pot_str gin += qerfstring return gin
def from_py_struct(structure: pymatgen.core.Structure): """Create a SmactStructure from a pymatgen Structure object. Args: structure: A pymatgen Structure. Returns: :class:`~.SmactStructure` """ if not isinstance(structure, pymatgen.core.Structure): raise TypeError("Structure must be a pymatgen.core.Structure instance.") bva = BVAnalyzer() struct = bva.get_oxi_state_decorated_structure(structure) sites, species = SmactStructure.__parse_py_sites(struct) lattice_mat = struct.lattice.matrix lattice_param = 1.0 return SmactStructure( species, lattice_mat, sites, lattice_param, sanitise_species=True, )
def __init__(self, defect): """ Args: defect(Defect): pymatgen Defect object """ self.defect = defect try: bv = BVAnalyzer() struct_valences = bv.get_valences(self.defect.bulk_structure) site_index = self.defect.bulk_structure.get_sites_in_sphere( self.defect.site.coords, 0.1, include_index=True)[0][2] def_site_valence = struct_valences[site_index] except Exception: # sometimes valences cant be assigned def_site_valence = 0 if isinstance(defect, Vacancy): self.charges = [-1 * def_site_valence] elif isinstance(defect, Substitution): #(minimize difference with host site specie) probable_chgs = [ox - def_site_valence for ox in self.defect.site.specie.oxidation_states] self.charges = [min(probable_chgs, key=abs)] elif isinstance(defect, Interstitial): self.charges = [0] else: raise ValueError("Defect Type not recognized.")
def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)
def __init__( self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015, ): """ Args: symm_tol (float): Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius (float): Maximum radius in Angstrom used to find nearest neighbors. max_permutations (int): Maximum number of permutations of oxidation states to test. distance_scale_factor (float): A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures, which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ self.symm_tol = symm_tol self.max_radius = max_radius self.max_permutations = max_permutations self.distance_scale_factor = distance_scale_factor self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)
def __init__(self, defect): """ Args: defect(Defect): pymatgen Defect object """ self.defect = defect try: bv = BVAnalyzer() struct_valences = bv.get_valences(self.defect.bulk_structure) site_index = self.defect.bulk_structure.get_sites_in_sphere( self.defect.site.coords, 0.1, include_index=True)[0][2] def_site_valence = struct_valences[site_index] except Exception: # sometimes valences cant be assigned def_site_valence = 0 if isinstance(defect, Vacancy): self.charges = [-1 * def_site_valence] elif isinstance(defect, Substitution): #(minimize difference with host site specie) probable_chgs = [ ox - def_site_valence for ox in self.defect.site.specie.oxidation_states ] self.charges = [min(probable_chgs, key=abs)] elif isinstance(defect, Interstitial): self.charges = [0] else: raise ValueError("Defect Type not recognized.")
def add_bv_structure(doc): struc = Structure.from_dict(doc["structure"]) try: bva = BVAnalyzer() bv_struct = bva.get_oxi_state_decorated_structure(struc) doc["bv_structure"] = bv_struct.as_dict() except Exception as e: print("BVAnalyzer error: {}".format(e))
def get_basic_analysis_and_error_checks(d, max_force_threshold=0.5, volume_change_threshold=0.2): initial_vol = d["input"]["crystal"]["lattice"]["volume"] final_vol = d["output"]["crystal"]["lattice"]["volume"] delta_vol = final_vol - initial_vol percent_delta_vol = delta_vol / initial_vol coord_num = get_coordination_numbers(d) calc = d["calculations"][-1] gap = calc["output"]["bandgap"] cbm = calc["output"]["cbm"] vbm = calc["output"]["vbm"] is_direct = calc["output"]["is_gap_direct"] warning_msgs = [] error_msgs = [] if abs(percent_delta_vol) > volume_change_threshold: warning_msgs.append("Volume change > {}%" .format(volume_change_threshold * 100)) bv_struct = Structure.from_dict(d["output"]["crystal"]) try: bva = BVAnalyzer() bv_struct = bva.get_oxi_state_decorated_structure(bv_struct) except ValueError as e: logger.error("Valence cannot be determined due to {e}." .format(e=e)) except Exception as ex: logger.error("BVAnalyzer error {e}.".format(e=str(ex))) max_force = None if d["state"] == "successful" and \ d["calculations"][0]["input"]["parameters"].get("NSW", 0) > 0: # handle the max force and max force error max_force = max([np.linalg.norm(a) for a in d["calculations"][-1]["output"] ["ionic_steps"][-1]["forces"]]) if max_force > max_force_threshold: error_msgs.append("Final max force exceeds {} eV" .format(max_force_threshold)) d["state"] = "error" s = Structure.from_dict(d["output"]["crystal"]) if not s.is_valid(): error_msgs.append("Bad structure (atoms are too close!)") d["state"] = "error" return {"delta_volume": delta_vol, "max_force": max_force, "percent_delta_volume": percent_delta_vol, "warnings": warning_msgs, "errors": error_msgs, "coordination_numbers": coord_num, "bandgap": gap, "cbm": cbm, "vbm": vbm, "is_gap_direct": is_direct, "bv_structure": bv_struct.as_dict()}
def calc(self, item): s = Structure.from_dict(item["structure"]) d = { "pymatgen_version": pymatgen_version, "successful": False, "bond_valence": { "structure": item["structure"], "method": None }, } try: bva = BVAnalyzer() valences = bva.get_valences(s) possible_species = { str(Specie(s[idx].specie, oxidation_state=valence)) for idx, valence in enumerate(valences) } d["successful"] = True s.add_oxidation_state_by_site(valences) d["bond_valence"] = { "possible_species": list(possible_species), "possible_valences": valences, "method": "BVAnalyzer", "structure": s.as_dict(), } except Exception as e: self.logger.error("BVAnalyzer failed with: {}".format(e)) try: first_oxi_state_guess = s.composition.oxi_state_guesses( max_sites=-50)[0] valences = [ first_oxi_state_guess[site.species_string] for site in s ] possible_species = { str(Specie(el, oxidation_state=valence)) for el, valence in first_oxi_state_guess.items() } d["successful"] = True s.add_oxidation_state_by_site(valences) d["bond_valence"] = { "possible_species": list(possible_species), "possible_valences": valences, "method": "oxi_state_guesses", "structure": s.as_dict(), } except Exception as e: self.logger.error( "Oxidation state guess failed with: {}".format(e)) return d
def _get_valences(self): """ Computes ionic valences of elements for all sites in the structure. """ bv = BVAnalyzer() valences = bv.get_valences(self._structure) el = [site.species_string for site in self.structure.sites] valence_dict = dict(zip(el, valences)) return valence_dict
def setUp(self): mgo_latt = [[4.212, 0, 0], [0, 4.212, 0], [0, 0, 4.212]] mgo_specie = ["Mg", 'O'] * 4 mgo_frac_cord = [[0, 0, 0], [0.5, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0], [0.5, 0, 0.5], [0, 0, 0.5], [0, 0.5, 0.5], [0.5, 0.5, 0.5]] self.mgo_uc = Structure(mgo_latt, mgo_specie, mgo_frac_cord, True, True) bv = BVAnalyzer() val = bv.get_valences(self.mgo_uc) el = [site.species_string for site in self.mgo_uc.sites] self.val_dict = dict(zip(el, val))
def setUp(self): filepath = os.path.join(test_dir, 'POSCAR') p = Poscar.from_file(filepath) self.structure = p.structure bv = BVAnalyzer() valences = bv.get_valences(self.structure) el = [site.species_string for site in self.structure.sites] valence_dict = dict(zip(el, valences)) self.rad_dict = {} for k, v in valence_dict.items(): self.rad_dict[k] = Specie(k,v).ionic_radius assert len(self.rad_dict) == len(self.structure.composition)
def setUp(self): filepath1 = os.path.join(test_dir, 'Li2O.cif') p = CifParser(filepath1).get_structures(False)[0] bv = BVAnalyzer() valences = bv.get_valences(p) el = [site.species_string for site in p.sites] val_dict = dict(zip(el, valences)) self._radii = {} for k,v in val_dict.items(): k1 = re.sub('[1-9,+,\-]', '', k) self._radii[k] = Specie(k1, v).ionic_radius p.remove(0) self._vac_struct = p
def setUp(self): filepath1 = os.path.join(PymatgenTest.TEST_FILES_DIR, "Li2O.cif") p = CifParser(filepath1).get_structures(False)[0] bv = BVAnalyzer() valences = bv.get_valences(p) el = [site.species_string for site in p.sites] val_dict = dict(zip(el, valences)) self._radii = {} for k, v in val_dict.items(): k1 = re.sub(r"[1-9,+,\-]", "", k) self._radii[k1] = float(Species(k1, v).ionic_radius) p.remove(0) self._vac_struct = p
def setUp(self): filepath = os.path.join(test_dir, 'POSCAR') p = Poscar.from_file(filepath) self.structure = p.structure bv = BVAnalyzer() valences = bv.get_valences(self.structure) el = [site.species_string for site in self.structure.sites] valence_dict = dict(zip(el, valences)) self.rad_dict = {} for k, v in valence_dict.items(): self.rad_dict[k] = float(Specie(k, v).ionic_radius) assert len(self.rad_dict) == len(self.structure.composition)
def setUp(self): filepath1 = os.path.join(test_dir, 'Li2O.cif') p = CifParser(filepath1).get_structures(False)[0] bv = BVAnalyzer() valences = bv.get_valences(p) el = [site.species_string for site in p.sites] val_dict = dict(zip(el, valences)) self._radii = {} for k, v in val_dict.items(): k1 = re.sub(r'[1-9,+,\-]', '', k) self._radii[k1] = float(Specie(k1, v).ionic_radius) p.remove(0) self._vac_struct = p
def _get_valences(self): """ Computes ionic valences of elements for all sites in the structure. """ try: bv = BVAnalyzer() self._structure = bv.get_oxi_state_decorated_structure( self._structure) valences = bv.get_valences(self._structure) except: try: bv = BVAnalyzer(symm_tol=0.0) self._structure = bv.get_oxi_state_decorated_structure( self._structure) valences = bv.get_valences(self._structure) except: valences = [0] * self._structure.num_sites #raise #el = [site.specie.symbol for site in self._structure.sites] #el = [site.species_string for site in self._structure.sites] #el = [site.specie for site in self._structure.sites] #valence_dict = dict(zip(el, valences)) #print valence_dict return valences
def from_mp( species: List[Union[Tuple[str, int, int], Tuple[smact.Species, int]]], api_key: str, ): """Create a SmactStructure using the first Materials Project entry for a composition. Args: species: See :meth:`~.__init__`. api_key: A www.materialsproject.org API key. Returns: :class:`~.SmactStructure` """ sanit_species = SmactStructure._sanitise_species(species) with MPRester(api_key) as m: eles = SmactStructure._get_ele_stoics(sanit_species) formula = "".join(f"{ele}{stoic}" for ele, stoic in eles.items()) structs = m.query( criteria={"reduced_cell_formula": formula}, properties=["structure"], ) if len(structs) == 0: raise ValueError( "Could not find composition in Materials Project Database, " "please supply a structure.") struct = structs[0][ 'structure'] # Default to first found structure if 0 not in (spec[1] for spec in sanit_species): # If everything's charged bva = BVAnalyzer() struct = bva.get_oxi_state_decorated_structure(struct) lattice_mat = struct.lattice.matrix lattice_param = 1.0 # TODO Use actual lattice parameter sites, _ = SmactStructure.__parse_py_sites(struct) return SmactStructure( sanit_species, lattice_mat, sites, lattice_param, sanitise_species=False, )
def setUp(self): filepath = os.path.join(test_dir, 'POSCAR') p = Poscar.from_file(filepath) self.structure = p.structure bv = BVAnalyzer() self.structure = bv.get_oxi_state_decorated_structure(self.structure) valences = bv.get_valences(self.structure) radii = [] for i in range(len(valences)): el = self.structure.sites[i].specie.symbol radius = Specie(el, valences[i]).ionic_radius radii.append(radius) el = [site.species_string for site in self.structure.sites] self.rad_dict = dict(zip(el, radii)) for el in self.rad_dict.keys(): print((el, self.rad_dict[el].real))
def _get_valences(self): """ Computes ionic valences of elements for all sites in the structure. """ bv = BVAnalyzer() try: valences = bv.get_valences(self._structure) except: err_str = "BVAnalyzer failed. The defect effective charge, and" err_str += " volume and surface area may not work" print err_str raise LookupError() el = [site.species_string for site in self.structure.sites] valence_dict = dict(zip(el, valences)) return valence_dict
def setUp(self): with open("mp-7000.json", "r") as f: dict_lse = json.load(f) lse = LightStructureEnvironments.from_dict(dict_lse) struct = lse.structure bva = BVAnalyzer() valences = bva.get_valences(structure=struct) lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments(maximum_distance_factor=1.41, only_cations=False, valences=valences) strategy = MultiWeightsChemenvStrategy.stats_article_weights_parameters() self.lse = LightStructureEnvironments.from_structure_environments(strategy=strategy, structure_environments=se) with open("mp-5634.json", "r") as f: dict_lse2 = json.load(f) self.lse2 = LightStructureEnvironments.from_dict(dict_lse2)
def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): self.symm_tol = symm_tol self.max_radius = max_radius self.max_permutations = max_permutations self.distance_scale_factor = distance_scale_factor self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)
def setUp(self): """ Setup MgO rocksalt structure for testing Vacancy """ mgo_latt = [[4.212, 0, 0], [0, 4.212, 0], [0, 0, 4.212]] mgo_specie = ["Mg"] * 4 + ["O"] * 4 mgo_frac_cord = [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5], [0.5, 0, 0], [0, 0.5, 0], [0, 0, 0.5], [0.5, 0.5, 0.5]] self._mgo_uc = Structure(mgo_latt, mgo_specie, mgo_frac_cord, True, True) bv = BVAnalyzer() self._mgo_uc = bv.get_oxi_state_decorated_structure(self._mgo_uc) self._mgo_val_rad_eval = ValenceIonicRadiusEvaluator(self._mgo_uc) self._mgo_val = self._mgo_val_rad_eval.valences self._mgo_rad = self._mgo_val_rad_eval.radii self._mgo_vac = Vacancy(self._mgo_uc, self._mgo_val, self._mgo_rad)
def predict(self, structure, ref_structure, test_isostructural=True): """ Given a structure, returns back the predicted volume. Args: structure (Structure): structure w/unknown volume ref_structure (Structure): A reference structure with a similar structure but different species. test_isostructural (bool): Whether to test that the two structures are isostructural. This algo works best for isostructural compounds. Defaults to True. Returns: a float value of the predicted volume """ if not is_ox(structure): a = BVAnalyzer() structure = a.get_oxi_state_decorated_structure(structure) if not is_ox(ref_structure): a = BVAnalyzer() ref_structure = a.get_oxi_state_decorated_structure(ref_structure) if test_isostructural: m = StructureMatcher() mapping = m.get_best_electronegativity_anonymous_mapping(structure, ref_structure) if mapping is None: raise ValueError("Input structures do not match!") comp = structure.composition ref_comp = ref_structure.composition numerator = 0 denominator = 0 # Here, the 1/3 factor on the composition accounts for atomic # packing. We want the number per unit length. # TODO: AJ doesn't understand the (1/3). It would make sense to him # if you were doing atomic volume and not atomic radius for k, v in comp.items(): numerator += k.ionic_radius * v ** (1 / 3) for k, v in ref_comp.items(): denominator += k.ionic_radius * v ** (1 / 3) # The scaling factor is based on lengths. We apply a power of 3. return ref_structure.volume * (numerator / denominator) ** 3
class AutoOxiStateDecorationTransformation(AbstractTransformation): """ This transformation automatically decorates a structure with oxidation states using a bond valence approach. """ def __init__( self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015, ): """ Args: symm_tol (float): Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius (float): Maximum radius in Angstrom used to find nearest neighbors. max_permutations (int): Maximum number of permutations of oxidation states to test. distance_scale_factor (float): A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures, which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ self.symm_tol = symm_tol self.max_radius = max_radius self.max_permutations = max_permutations self.distance_scale_factor = distance_scale_factor self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor) def apply_transformation(self, structure): """ Apply the transformation. Args: structure (Structure): Input Structure Returns: Oxidation state decorated Structure. """ return self.analyzer.get_oxi_state_decorated_structure(structure) @property def inverse(self): """ Returns: None """ return None @property def is_one_to_many(self): """ Returns: False """ return False
def get_atomic_radii(self): if not self.pymatgen_radii: return None try: bv = BVAnalyzer() valences = bv.get_valences(self._structure) elements = [site.species_string for site in self._structure.sites] valence_dict = dict(zip(elements, valences)) radii = {} for k, v in valence_dict.items(): radii[k] = float(Specie(k, v).ionic_radius) except (ValueError, TypeError) as e: radii = None return radii
def __init__(self, structure, include_bv_charge=False): """ Initializes a Vacancy Generator Args: structure(Structure): pymatgen structure object """ self.structure = structure self.include_bv_charge = include_bv_charge # Find equivalent site list sga = SpacegroupAnalyzer(self.structure) self.symm_structure = sga.get_symmetrized_structure() self.equiv_site_seq = list(self.symm_structure.equivalent_sites) self.struct_valences = None if self.include_bv_charge: bv = BVAnalyzer() self.struct_valences = bv.get_valences(self.structure)
def get_valences(self): """ Uses Pymatgen to obtain likely valence states of every element in structure Returns vals: dictionary of average valence state for every element in composition """ struct = self.structure bv = BVAnalyzer() try: valences = bv.get_valences(struct) struct = bv.get_oxi_state_decorated_structure(struct) except: return None if isinstance(valences[0], list): valences = [item for sublist in valences for item in sublist] stoich = defaultdict(int) for site in struct.as_dict()['sites']: elem = site['species'][0]['element'] stoich[elem] += 1 vals = {} for spec in stoich.keys(): vals[spec] = 0.0 for atom in range(len(struct)): try: vals[struct.as_dict()['sites'][atom]['species'][0] ['element']] += valences[atom] except Exception as e: print("Trouble with {}".format(struct.formula)) print('Do you have partial occupancies?') return None for spec in vals: vals[spec] = vals[spec] / stoich[spec] vals[spec] = int(round(vals[spec])) return vals
def __init__(self, structure, valences, radii): """ Given a structure, generate symmetrically distinct interstitial sites. Args: structure: pymatgen.core.structure.Structure valences: Dictionary of oxidation states of elements in { El:valence} form radii: Radii of elemnts in the structure """ bv = BVAnalyzer() self._structure = bv.get_oxi_state_decorated_structure(structure) #self._structure = structure self._valence_dict = valences self._rad_dict = radii #Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction and #the symmetry reduced voronoi nodes #are possible candidates for interstitial sites #try: possible_interstitial_sites = symmetry_reduced_voronoi_nodes( self._structure, self._rad_dict) #except: # raise ValueError("Symmetry_reduced_voronoi_nodes failed") #Do futher processing on possibleInterstitialSites to obtain #interstitial sites self._defect_sites = possible_interstitial_sites self._defectsite_coord_no = [] self._defect_coord_sites = [] self._defect_coord_charge = [] self._radii = [] for site in self._defect_sites: coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site) self._defectsite_coord_no.append(coord_no) self._defect_coord_sites.append(coord_sites) self._defect_coord_charge.append(chrg) for site in self._defect_sites: self._radii.append(float(site.properties['voronoi_radius']))
class AutoOxiStateDecorationTransformation(AbstractTransformation): """ This transformation automatically decorates a structure with oxidation states using a bond valence approach. """ def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): """ Args: symm_tol: Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius: Maximum radius in Angstrom used to find nearest neighbors. max_permutations: The maximum number of permutations of oxidation states to test. distance_scale_factor: A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor) def apply_transformation(self, structure): return self.analyzer.get_oxi_state_decorated_structure(structure) @property def inverse(self): return None @property def is_one_to_many(self): return False @property def to_dict(self): return { "name": self.__class__.__name__, "version": __version__, "init_args": { "symm_tol": self.analyzer.symm_tol, "max_radius": self.analyzer.max_radius, "max_permutations": self.analyzer.max_permutations, "distance_scale_factor": self.analyzer.dist_scale_factor }, "@module": self.__class__.__module__, "@class": self.__class__.__name__ }
def _get_valences(self): """ Computes ionic valences of elements for all sites in the structure. """ bv = BVAnalyzer() self._structure = bv.get_oxi_state_decorated_structure(self._structure) try: valences = bv.get_valences(self._structure) except: try: valences = bv.get_valences(self._structure, symm_tol=0.0) except: raise #print valences #el = [site.specie.symbol for site in self._structure.sites] #el = [site.species_string for site in self._structure.sites] #el = [site.specie for site in self._structure.sites] #valence_dict = dict(zip(el, valences)) #print valence_dict return valences
class BVAnalyzerTest(PymatgenTest): def setUp(self): self.analyzer = BVAnalyzer() def test_get_valence(self): s = Structure.from_file(os.path.join(test_dir, "LiMn2O4.json")) ans = [1, 1, 3, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) s = self.get_structure("LiFePO4") ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) s = self.get_structure("Li3V2(PO4)3") ans = [1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) s = Structure.from_file(os.path.join(test_dir, "Li4Fe3Mn1(PO4)4.json")) ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) s = self.get_structure("NaFePO4") ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) def test_get_oxi_state_structure(self): s = Structure.from_file(os.path.join(test_dir, "LiMn2O4.json")) news = self.analyzer.get_oxi_state_decorated_structure(s) self.assertIn(Specie("Mn", 3), news.composition.elements) self.assertIn(Specie("Mn", 4), news.composition.elements)
def get_coordsites_min_max_charge(self, n): """ Minimum and maximum charge of sites surrounding the vacancy site. Args: n: Index of vacancy list """ bv = BVAnalyzer() struct_valences = bv.get_valences(self._structure) coordinated_site_valences = [] def _get_index(site): for i in range(len(self._structure.sites)): if site.is_periodic_image(self._structure.sites[i]): return i raise ValueError("Site not found") for site in self._defect_coord_sites[n]: ind = _get_index(site) coordinated_site_valences.append(struct_valences[ind]) coordinated_site_valences.sort() return coordinated_site_valences[0], coordinated_site_valences[-1]
class AutoOxiStateDecorationTransformation(AbstractTransformation): """ This transformation automatically decorates a structure with oxidation states using a bond valence approach. """ def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): """ Args: symm_tol: Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius: Maximum radius in Angstrom used to find nearest neighbors. max_permutations: The maximum number of permutations of oxidation states to test. distance_scale_factor: A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor) def apply_transformation(self, structure): return self.analyzer.get_oxi_state_decorated_structure(structure) @property def inverse(self): return None @property def is_one_to_many(self): return False @property def to_dict(self): return { "name": self.__class__.__name__, "version": __version__, "init_args": { "symm_tol": self.analyzer.symm_tol, "max_radius": self.analyzer.max_radius, "max_permutations": self.analyzer.max_permutations, "distance_scale_factor": self.analyzer.dist_scale_factor, }, "@module": self.__class__.__module__, "@class": self.__class__.__name__, }
def process_item(self, item): s = Structure.from_dict(item['structure']) try: bva = BVAnalyzer() valences = bva.get_valences(s) possible_species = { str(Specie(s[idx].specie, oxidation_state=valence)) for idx, valence in enumerate(valences) } method = "BVAnalyzer" except ValueError: try: first_oxi_state_guess = s.composition.oxi_state_guesses()[0] valences = [ first_oxi_state_guess[site.species_string] for site in s ] possible_species = { str(Specie(el, oxidation_state=valence)) for el, valence in first_oxi_state_guess.items() } method = "oxi_state_guesses" except: return { "task_id": item['task_id'], "pymatgen_version": pymatgen_version, "successful": False } return { "task_id": item['task_id'], "possible_species": list(possible_species), "possible_valences": valences, "method": method, "pymatgen_version": pymatgen_version, "successful": True }
def get_basic_analysis_and_error_checks(d): initial_vol = d["input"]["crystal"]["lattice"]["volume"] final_vol = d["output"]["crystal"]["lattice"]["volume"] delta_vol = final_vol - initial_vol percent_delta_vol = delta_vol / initial_vol coord_num = get_coordination_numbers(d) calc = d["calculations"][-1] gap = calc["output"]["bandgap"] cbm = calc["output"]["cbm"] vbm = calc["output"]["vbm"] is_direct = calc["output"]["is_gap_direct"] if abs(percent_delta_vol) > 0.20: warning_msgs = ["Volume change > 20%"] else: warning_msgs = [] bv_struct = Structure.from_dict(d["output"]["crystal"]) try: bva = BVAnalyzer() bv_struct = bva.get_oxi_state_decorated_structure(bv_struct) except ValueError as e: logger.error("Valence cannot be determined due to {e}.".format(e=e)) except Exception as ex: logger.error("BVAnalyzer error {e}.".format(e=str(ex))) return { "delta_volume": delta_vol, "percent_delta_volume": percent_delta_vol, "warnings": warning_msgs, "coordination_numbers": coord_num, "bandgap": gap, "cbm": cbm, "vbm": vbm, "is_gap_direct": is_direct, "bv_structure": bv_struct.to_dict }
def Calc_Ewald(pmg_struct, formal_val=[]): """ input: pmg_struct: pymatgen structure formal_val: list - list of valence for each atom TBD: use input formal valence list to decorate the structure """ # GET valence list if len(formal_val)==0: bv_analyzer=BVAnalyzer(max_radius=4) #max_radius default is 4 formal_val=bv_analyzer.get_valences(pmg_struct) # default for cutoff is set to None real_cutoff = None rec_cutoff = None # Oxidation states are decorated automatically. decorated_struct = bv_analyzer.get_oxi_state_decorated_structure(pmg_struct) NUM_sites=pmg_struct.num_sites # Per atom ewald_per_atom=1/NUM_sites*EwaldSummation(decorated_struct, real_space_cut=real_cutoff, recip_space_cut=rec_cutoff).total_energy return ewald_per_atom
def get_oxidation_states(stru): try: valences = BVAnalyzer().get_valences(stru) elems = [i for i in stru.species] oxids = set(zip(elems,valences)) reduced_elems = set(elems) out={i:[] for i in reduced_elems} for i in oxids: if i[1] not in out[i[0]]: out[i[0]].append(i[1]) out1 = {i:np.sign(out[i][0])*np.max(np.abs(out[i])) for i in out} return out1 except: oxi_states = {} oxi_range = {} reduc_comp = stru.composition.reduced_composition oxid_states = get_oxid_states_composition(reduc_comp) return oxid_states
def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): """ Args: symm_tol: Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius: Maximum radius in Angstrom used to find nearest neighbors. max_permutations: The maximum number of permutations of oxidation states to test. distance_scale_factor: A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)
def predict(self, structure, ref_structure, test_isostructural=True): """ Given a structure, returns back the predicted volume. Args: structure (Structure): structure w/unknown volume ref_structure (Structure): A reference structure with a similar structure but different species. test_isostructural (bool): Whether to test that the two structures are isostructural. This algo works best for isostructural compounds. Defaults to True. Returns: a float value of the predicted volume """ if not is_ox(structure): a = BVAnalyzer() structure = a.get_oxi_state_decorated_structure(structure) if not is_ox(ref_structure): a = BVAnalyzer() ref_structure = a.get_oxi_state_decorated_structure(ref_structure) if test_isostructural: m = StructureMatcher() mapping = m.get_best_electronegativity_anonymous_mapping( structure, ref_structure) if mapping is None: raise ValueError("Input structures do not match!") comp = structure.composition ref_comp = ref_structure.composition numerator = 0 denominator = 0 # Here, the 1/3 factor on the composition accounts for atomic # packing. We want the number per unit length. # TODO: AJ doesn't understand the (1/3). It would make sense to him # if you were doing atomic volume and not atomic radius for k, v in comp.items(): numerator += k.ionic_radius * v**(1 / 3) for k, v in ref_comp.items(): denominator += k.ionic_radius * v**(1 / 3) # The scaling factor is based on lengths. We apply a power of 3. return ref_structure.volume * (numerator / denominator)**3
class AutoOxiStateDecorationTransformation(AbstractTransformation): """ This transformation automatically decorates a structure with oxidation states using a bond valence approach. Args: symm_tol (float): Symmetry tolerance used to determine which sites are symmetrically equivalent. Set to 0 to turn off symmetry. max_radius (float): Maximum radius in Angstrom used to find nearest neighbors. max_permutations (int): Maximum number of permutations of oxidation states to test. distance_scale_factor (float): A scale factor to be applied. This is useful for scaling distances, esp in the case of calculation-relaxed structures, which may tend to under (GGA) or over bind (LDA). The default of 1.015 works for GGA. For experimental structure, set this to 1. """ def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015): self.symm_tol = symm_tol self.max_radius = max_radius self.max_permutations = max_permutations self.distance_scale_factor = distance_scale_factor self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor) def apply_transformation(self, structure): return self.analyzer.get_oxi_state_decorated_structure(structure) @property def inverse(self): return None @property def is_one_to_many(self): return False
class BVAnalyzerTest(unittest.TestCase): def setUp(self): self.analyzer = BVAnalyzer() def test_get_valence(self): parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif")) s = parser.get_structures()[0] ans = [1, 1, 3, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) parser = CifParser(os.path.join(test_dir, "LiFePO4.cif")) s = parser.get_structures()[0] ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) parser = CifParser(os.path.join(test_dir, "Li3V2(PO4)3.cif")) s = parser.get_structures()[0] ans = [1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) parser = CifParser(os.path.join(test_dir, "Li4Fe3Mn1(PO4)4.cif")) s = parser.get_structures()[0] ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) parser = CifParser(os.path.join(test_dir, "NaFePO4.cif")) s = parser.get_structures()[0] ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, - 2, -2, -2, -2, -2, -2, -2, -2, -2] self.assertEqual(self.analyzer.get_valences(s), ans) def test_get_oxi_state_structure(self): parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif")) s = parser.get_structures()[0] news = self.analyzer.get_oxi_state_decorated_structure(s) self.assertIn(Specie("Mn", 3), news.composition.elements) self.assertIn(Specie("Mn", 4), news.composition.elements)
def run_task(self, fw_spec): logging.basicConfig(filename='chemenv_structure_environments.log', format='%(levelname)s:%(module)s:%(funcName)s:%(message)s', level=logging.DEBUG) lgf = LocalGeometryFinder() lgf.setup_parameters(centering_type='centroid', include_central_site_in_centroid=True, structure_refinement=lgf.STRUCTURE_REFINEMENT_NONE) if 'chemenv_parameters' in fw_spec: for param, value in fw_spec['chemenv_parameters'].items(): lgf.setup_parameter(param, value) identifier = fw_spec['identifier'] if 'structure' in fw_spec: structure = fw_spec['structure'] else: if identifier['source'] == 'MaterialsProject' and 'material_id' in identifier: if not 'mapi_key' in fw_spec: raise ValueError('The mapi_key should be provided to get the structure from the Materials Project') # FIXME: Use MPRester from pymatgen from pymatgen.matproj.rest import MPRester a = MPRester(fw_spec['mapi_key']) structure = a.get_structure_by_material_id(identifier['material_id']) else: raise ValueError('Either structure or identifier with source = MaterialsProject and material_id ' 'should be provided') info = {} # Compute the structure environments lgf.setup_structure(structure) if 'valences' in fw_spec: valences = fw_spec['valences'] else: try: bva = BVAnalyzer() valences = bva.get_valences(structure=structure) info['valences'] = {'origin': 'BVAnalyzer'} except: valences = 'undefined' info['valences'] = {'origin': 'None'} excluded_atoms = None if 'excluded_atoms' in fw_spec: excluded_atoms = fw_spec['excluded_atoms'] se = lgf.compute_structure_environments(only_cations=False, valences=valences, excluded_atoms=excluded_atoms) # Write to json file if 'json_file' in fw_spec: json_file = fw_spec['json_file'] else: json_file = 'structure_environments.json' f = open(json_file, 'w') json.dump(se.as_dict(), f) f.close() # Save to database if 'mongo_database' in fw_spec: database = fw_spec['mongo_database'] entry = {'identifier': identifier, 'elements': [elmt.symbol for elmt in structure.composition.elements], 'nelements': len(structure.composition.elements), 'pretty_formula': structure.composition.reduced_formula, 'nsites': len(structure) } saving_option = fw_spec['saving_option'] if saving_option == 'gridfs': gridfs_msonables = {'structure': structure, 'structure_environments': se} elif saving_option == 'storefile': gridfs_msonables = None if 'se_prefix' in fw_spec: se_prefix = fw_spec['se_prefix'] if not se_prefix.isalpha(): raise ValueError('Prefix for structure_environments file is "{}" ' 'while it should be alphabetic'.format(se_prefix)) else: se_prefix = '' if se_prefix: se_rfilename = '{}_{}.json'.format(se_prefix, fw_spec['storefile_basename']) else: se_rfilename = '{}.json'.format(fw_spec['storefile_basename']) se_rfilepath = '{}/{}'.format(fw_spec['storefile_dirpath'], se_rfilename) storage_server = fw_spec['storage_server'] storage_server.put(localpath=json_file, remotepath=se_rfilepath, overwrite=True, makedirs=False) entry['structure_environments_file'] = se_rfilepath else: raise ValueError('Saving option is "{}" while it should be ' '"gridfs" or "storefile"'.format(saving_option)) criteria = {'identifier': identifier} if database.collection.find(criteria).count() == 1: database.update_entry(query=criteria, entry_update=entry, gridfs_msonables=gridfs_msonables) else: database.insert_entry(entry=entry, gridfs_msonables=gridfs_msonables)
def buckingham_potential(self, structure, val_dict=None): """ Generate species, buckingham, and spring options for an oxide structure using the parameters in default libraries. Ref: 1. G.V. Lewis and C.R.A. Catlow, J. Phys. C: Solid State Phys., 18, 1149-1161 (1985) 2. T.S.Bush, J.D.Gale, C.R.A.Catlow and P.D. Battle, J. Mater Chem., 4, 831-837 (1994) Args: structure: pymatgen.core.structure.Structure val_dict (Needed if structure is not charge neutral) El:valence dictionary, where El is element. """ if not val_dict: bv = BVAnalyzer() el = [site.species_string for site in structure.sites] valences = bv.get_valences(structure) val_dict = dict(zip(el, valences)) #Try bush library first bpb = BuckinghamPotBush() bpl = BuckinghamPotLewis() gin = "" for key in val_dict.keys(): use_bush = True el = re.sub('[1-9,+,\-]', '', key) if el not in bpb.species_dict.keys(): use_bush = False elif val_dict[key] != bpb.species_dict[el]['oxi']: use_bush = False if use_bush: gin += "species \n" gin += bpb.species_dict[el]['inp_str'] gin += "buckingham \n" gin += bpb.pot_dict[el] gin += "spring \n" gin += bpb.spring_dict[el] continue #Try lewis library next if element is not in bush #use_lewis = True if el != "O": # For metals the key is "Metal_OxiState+" k = el + '_' + str(int(val_dict[key])) + '+' if k not in bpl.species_dict.keys(): #use_lewis = False raise GulpError("Element {} not in library".format(k)) gin += "species\n" gin += bpl.species_dict[k] gin += "buckingham\n" gin += bpl.pot_dict[k] else: gin += "species\n" k = "O_core" gin += bpl.species_dict[k] k = "O_shel" gin += bpl.species_dict[k] gin += "buckingham\n" gin += bpl.pot_dict[key] gin += 'spring\n' gin += bpl.spring_dict[key] return gin
def __init__(self, structure, valences, radii, site_type='voronoi_vertex', accuracy='Normal', symmetry_flag=True): """ Given a structure, generate symmetrically distinct interstitial sites. Args: structure: pymatgen.core.structure.Structure valences: Dictionary of oxidation states of elements in {el:valence} form radii: Radii of elemnts in the structure site_type: "voronoi_vertex" uses voronoi nodes "voronoi_facecenter" uses voronoi polyhedra face centers Default is "voronoi_vertex" accuracy: Flag denoting whether to use high accuracy version of Zeo++. Options are "Normal" and "High". Default is normal. """ try: bv = BVAnalyzer() self._structure = bv.get_oxi_state_decorated_structure(structure) except: try: bv = BVAnalyzer(symm_tol=0.0) self._structure = bv.get_oxi_state_decorated_structure( structure ) except: raise self._valence_dict = valences self._rad_dict = radii """ Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction and the symmetry reduced voronoi nodes are possible candidates for interstitial sites. """ if accuracy == "Normal": high_accuracy_flag = False elif accuracy == "High": high_accuracy_flag = True else: raise ValueError("Accuracy setting not understood.") vor_node_sites, vor_facecenter_sites = symmetry_reduced_voronoi_nodes( self._structure, self._rad_dict, high_accuracy_flag, symmetry_flag ) if site_type == 'voronoi_vertex': possible_interstitial_sites = vor_node_sites elif site_type == 'voronoi_facecenter': possible_interstitial_sites = vor_facecenter_sites else: raise ValueError("Input site type not implemented") #Do futher processing on possibleInterstitialSites to obtain #interstitial sites self._defect_sites = possible_interstitial_sites self._defectsite_coord_no = [] self._defect_coord_sites = [] self._defect_coord_charge = [] self._radii = [] for site in self._defect_sites: coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site) self._defectsite_coord_no.append(coord_no) self._defect_coord_sites.append(coord_sites) self._defect_coord_charge.append(chrg) for site in self._defect_sites: self._radii.append(float(site.properties['voronoi_radius']))
def setUp(self): self.analyzer = BVAnalyzer()
def apply_transformation(self, structure, return_ranked_list=False): """ Args: structure (Structure): Input structure to dope Returns: [{"structure": Structure, "energy": float}] """ comp = structure.composition logger.info("Composition: %s" % comp) for sp in comp: try: sp.oxi_state except AttributeError: analyzer = BVAnalyzer() structure = analyzer.get_oxi_state_decorated_structure( structure) comp = structure.composition break ox = self.dopant.oxi_state radius = self.dopant.ionic_radius compatible_species = [ sp for sp in comp if sp.oxi_state == ox and abs(sp.ionic_radius / radius - 1) < self.ionic_radius_tol] if (not compatible_species) and self.alio_tol: # We only consider aliovalent doping if there are no compatible # isovalent species. compatible_species = [ sp for sp in comp if abs(sp.oxi_state - ox) <= self.alio_tol and abs(sp.ionic_radius / radius - 1) < self.ionic_radius_tol and sp.oxi_state * ox >= 0] if self.allowed_doping_species is not None: # Only keep allowed doping species. compatible_species = [ sp for sp in compatible_species if sp in [get_el_sp(s) for s in self.allowed_doping_species]] logger.info("Compatible species: %s" % compatible_species) lengths = structure.lattice.abc scaling = [max(1, int(round(math.ceil(self.min_length/x)))) for x in lengths] logger.info("Lengths are %s" % str(lengths)) logger.info("Scaling = %s" % str(scaling)) all_structures = [] t = EnumerateStructureTransformation(**self.kwargs) for sp in compatible_species: supercell = structure * scaling nsp = supercell.composition[sp] if sp.oxi_state == ox: supercell.replace_species({sp: {sp: (nsp - 1)/nsp, self.dopant: 1/nsp}}) logger.info("Doping %s for %s at level %.3f" % ( sp, self.dopant, 1 / nsp)) elif self.codopant: codopant = _find_codopant(sp, 2 * sp.oxi_state - ox) supercell.replace_species({sp: {sp: (nsp - 2) / nsp, self.dopant: 1 / nsp, codopant: 1 / nsp}}) logger.info("Doping %s for %s + %s at level %.3f" % ( sp, self.dopant, codopant, 1 / nsp)) elif abs(sp.oxi_state) < abs(ox): # Strategy: replace the target species with a # combination of dopant and vacancy. # We will choose the lowest oxidation state species as a # vacancy compensation species as it is likely to be lower in # energy sp_to_remove = min([s for s in comp if s.oxi_state * ox > 0], key=lambda ss: abs(ss.oxi_state)) if sp_to_remove == sp: common_charge = lcm(int(abs(sp.oxi_state)), int(abs(ox))) ndopant = common_charge / abs(ox) nsp_to_remove = common_charge / abs(sp.oxi_state) logger.info("Doping %d %s with %d %s." % (nsp_to_remove, sp, ndopant, self.dopant)) supercell.replace_species( {sp: {sp: (nsp - nsp_to_remove) / nsp, self.dopant: ndopant / nsp}}) else: ox_diff = int(abs(round(sp.oxi_state - ox))) vac_ox = int(abs(sp_to_remove.oxi_state)) common_charge = lcm(vac_ox, ox_diff) ndopant = common_charge / ox_diff nx_to_remove = common_charge / vac_ox nx = supercell.composition[sp_to_remove] logger.info("Doping %d %s with %s and removing %d %s." % (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove)) supercell.replace_species( {sp: {sp: (nsp - ndopant) / nsp, self.dopant: ndopant / nsp}, sp_to_remove: { sp_to_remove: (nx - nx_to_remove) / nx}}) elif abs(sp.oxi_state) > abs(ox): # Strategy: replace the target species with dopant and also # remove some opposite charged species for charge neutrality if ox > 0: sp_to_remove = max(supercell.composition.keys(), key=lambda el: el.X) else: sp_to_remove = min(supercell.composition.keys(), key=lambda el: el.X) # Confirm species are of opposite oxidation states. assert sp_to_remove.oxi_state * sp.oxi_state < 0 ox_diff = int(abs(round(sp.oxi_state - ox))) anion_ox = int(abs(sp_to_remove.oxi_state)) nx = supercell.composition[sp_to_remove] common_charge = lcm(anion_ox, ox_diff) ndopant = common_charge / ox_diff nx_to_remove = common_charge / anion_ox logger.info("Doping %d %s with %s and removing %d %s." % (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove)) supercell.replace_species( {sp: {sp: (nsp - ndopant) / nsp, self.dopant: ndopant / nsp}, sp_to_remove: {sp_to_remove: (nx - nx_to_remove)/nx}}) ss = t.apply_transformation( supercell, return_ranked_list=self.max_structures_per_enum) logger.info("%s distinct structures" % len(ss)) all_structures.extend(ss) logger.info("Total %s doped structures" % len(all_structures)) if return_ranked_list: return all_structures[:return_ranked_list] return all_structures[0]["structure"]
def get_analysis_and_structure(self, structure, calculate_valences=True, guesstimate_spin=False, op_threshold=0.1): """ Obtain an analysis of a given structure and if it may be Jahn-Teller active or not. This is a heuristic, and may give false positives and false negatives (false positives are preferred). :param structure: input structure :param calculate_valences (bool): whether to attempt to calculate valences or not, structure should have oxidation states to perform analysis :param guesstimate_spin (bool): whether to guesstimate spin state from magnetic moments or not, use with caution :param op_threshold (float): threshold for order parameter above which to consider site to match an octahedral or tetrahedral motif, since Jahn-Teller structures can often be quite distorted, this threshold is smaller than one might expect :return (dict): analysis of structure, with key 'strength' which may be 'none', 'strong', 'weak', or 'unknown' """ structure = structure.get_primitive_structure() if calculate_valences: bva = BVAnalyzer() structure = bva.get_oxi_state_decorated_structure(structure) # no point testing multiple equivalent sites, doesn't make any difference to analysis # but makes returned symmetrized_structure = SpacegroupAnalyzer(structure).get_symmetrized_structure() # to detect structural motifs of a given site op = LocalStructOrderParams(['oct', 'tet']) # dict of site index to the Jahn-Teller analysis of that site jt_sites = [] non_jt_sites = [] for indices in symmetrized_structure.equivalent_indices: idx = indices[0] site = symmetrized_structure[idx] # only interested in sites with oxidation states if isinstance(site.specie, Specie) and site.specie.element.is_transition_metal: # get motif around site order_params = op.get_order_parameters(symmetrized_structure, idx) if order_params[0] > order_params[1] and order_params[0] > op_threshold: motif = 'oct' motif_order_parameter = order_params[0] elif order_params[1] > op_threshold: motif = 'tet' motif_order_parameter = order_params[1] else: motif = 'unknown' motif_order_parameter = None if motif == "oct" or motif == "tet": # guess spin of metal ion if guesstimate_spin and 'magmom' in site.properties: # estimate if high spin or low spin magmom = site.properties['magmom'] spin_state = self._estimate_spin_state(site.specie, motif, magmom) else: spin_state = "unknown" magnitude = self.get_magnitude_of_effect_from_species(site.specie, spin_state, motif) if magnitude != "none": ligands = get_neighbors_of_site_with_index(structure, idx, approach="min_dist", delta=0.15) ligand_bond_lengths = [ligand.distance(structure[idx]) for ligand in ligands] ligands_species = list(set([str(ligand.specie) for ligand in ligands])) ligand_bond_length_spread = max(ligand_bond_lengths) - \ min(ligand_bond_lengths) def trim(f): # avoid storing to unreasonable precision, hurts readability return float("{:.4f}".format(f)) # to be Jahn-Teller active, all ligands have to be the same if len(ligands_species) == 1: jt_sites.append({'strength': magnitude, 'motif': motif, 'motif_order_parameter': trim(motif_order_parameter), 'spin_state': spin_state, 'species': str(site.specie), 'ligand': ligands_species[0], 'ligand_bond_lengths': [trim(length) for length in ligand_bond_lengths], 'ligand_bond_length_spread': trim(ligand_bond_length_spread), 'site_indices': indices}) # store reasons for not being J-T active else: non_jt_sites.append({'site_indices': indices, 'strength': "none", 'reason': "Not Jahn-Teller active for this " "electronic configuration."}) else: non_jt_sites.append({'site_indices': indices, 'strength': "none", 'reason': "motif is {}".format(motif)}) # perform aggregation of all sites if jt_sites: analysis = {'active': True} # if any site could exhibit 'strong' Jahn-Teller effect # then mark whole structure as strong strong_magnitudes = [site['strength'] == "strong" for site in jt_sites] if any(strong_magnitudes): analysis['strength'] = "strong" else: analysis['strength'] = "weak" analysis['sites'] = jt_sites return analysis, structure else: return {'active': False, 'sites': non_jt_sites}, structure
def __init__( self, structure, valences, radii, site_type="voronoi_vertex", accuracy="Normal", symmetry_flag=True, oxi_state=False, ): """ Given a structure, generate symmetrically distinct interstitial sites. Args: structure: pymatgen.core.structure.Structure valences: Dictionary of oxidation states of elements in {el:valence} form radii: Radii of elemnts in the structure site_type: "voronoi_vertex" uses voronoi nodes "voronoi_facecenter" uses voronoi polyhedra face centers Default is "voronoi_vertex" accuracy: Flag denoting whether to use high accuracy version of Zeo++. Options are "Normal" and "High". Default is normal. symmetry_flag: If True, only returns symmetrically distinct sites oxi_state: If False, input structure is considered devoid of oxidation-state decoration. And oxi-state for each site is determined. Use True, if input structure is oxi-state decorated. This option is useful when the structure is not electro-neutral after deleting/adding sites. In that case oxi-decorate the structure before deleting/adding the sites. """ if not oxi_state: try: bv = BVAnalyzer() self._structure = bv.get_oxi_state_decorated_structure(structure) except: try: bv = BVAnalyzer(symm_tol=0.0) self._structure = bv.get_oxi_state_decorated_structure(structure) except: raise else: self._structure = structure self._valence_dict = valences self._rad_dict = radii """ Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction and the symmetry reduced voronoi nodes are possible candidates for interstitial sites. """ if accuracy == "Normal": high_accuracy_flag = False elif accuracy == "High": high_accuracy_flag = True else: raise ValueError("Accuracy setting not understood.") vor_node_sites, vor_facecenter_sites = symmetry_reduced_voronoi_nodes( self._structure, self._rad_dict, high_accuracy_flag, symmetry_flag ) if site_type == "voronoi_vertex": possible_interstitial_sites = vor_node_sites elif site_type == "voronoi_facecenter": possible_interstitial_sites = vor_facecenter_sites else: raise ValueError("Input site type not implemented") # Do futher processing on possibleInterstitialSites to obtain # interstitial sites self._defect_sites = possible_interstitial_sites self._defectsite_coord_no = [] self._defect_coord_sites = [] self._defect_coord_charge = [] self._radii = [] for site in self._defect_sites: coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site) self._defectsite_coord_no.append(coord_no) self._defect_coord_sites.append(coord_sites) self._defect_coord_charge.append(chrg) for site in self._defect_sites: self._radii.append(float(site.properties["voronoi_radius"]))
def predict(self, structure, ref_structure): """ Given a structure, returns the predicted volume. Args: structure (Structure): structure w/unknown volume ref_structure (Structure): A reference structure with a similar structure but different species. Returns: a float value of the predicted volume """ if self.check_isostructural: m = StructureMatcher() mapping = m.get_best_electronegativity_anonymous_mapping( structure, ref_structure) if mapping is None: raise ValueError("Input structures do not match!") if "ionic" in self.radii_type: try: # Use BV analyzer to determine oxidation states only if the # oxidation states are not already specified in the structure # and use_bv is true. if (not is_ox(structure)) and self.use_bv: a = BVAnalyzer() structure = a.get_oxi_state_decorated_structure(structure) if (not is_ox(ref_structure)) and self.use_bv: a = BVAnalyzer() ref_structure = a.get_oxi_state_decorated_structure( ref_structure) comp = structure.composition ref_comp = ref_structure.composition # Check if all the associated ionic radii are available. if any([k.ionic_radius is None for k in list(comp.keys())]) or \ any([k.ionic_radius is None for k in list(ref_comp.keys())]): raise ValueError("Not all the ionic radii are available!") numerator = 0 denominator = 0 # Here, the 1/3 factor on the composition accounts for atomic # packing. We want the number per unit length. for k, v in comp.items(): numerator += k.ionic_radius * v ** (1 / 3) for k, v in ref_comp.items(): denominator += k.ionic_radius * v ** (1 / 3) return ref_structure.volume * (numerator / denominator) ** 3 except Exception as ex: warnings.warn("Exception occured. Will attempt atomic radii.") # If error occurs during use of ionic radii scheme, pass # and see if we can resolve it using atomic radii. pass if "atomic" in self.radii_type: comp = structure.composition ref_comp = ref_structure.composition # Here, the 1/3 factor on the composition accounts for atomic # packing. We want the number per unit length. numerator = 0 denominator = 0 for k, v in comp.items(): numerator += k.atomic_radius * v ** (1 / 3) for k, v in ref_comp.items(): denominator += k.atomic_radius * v ** (1 / 3) return ref_structure.volume * (numerator / denominator) ** 3 raise ValueError("Cannot find volume scaling based on radii choices " "specified!")