def test_sulfide_type(self): # NaS2 -> polysulfide latt = Lattice.tetragonal(9.59650, 11.78850) species = ["Na"] * 2 + ["S"] * 2 coords = [[0.00000, 0.00000, 0.17000], [0.27600, 0.25000, 0.12500], [0.03400, 0.25000, 0.29600], [0.14700, 0.11600, 0.40000]] struct = Structure.from_spacegroup(122, latt, species, coords) self.assertEqual(sulfide_type(struct), "polysulfide") # NaCl type NaS -> sulfide latt = Lattice.cubic(5.75) species = ["Na", "S"] coords = [[0.00000, 0.00000, 0.00000], [0.50000, 0.50000, 0.50000]] struct = Structure.from_spacegroup(225, latt, species, coords) self.assertEqual(sulfide_type(struct), "sulfide") # Na2S2O3 -> None (sulfate) latt = Lattice.monoclinic(6.40100, 8.10000, 8.47400, 96.8800) species = ["Na"] * 2 + ["S"] * 2 + ["O"] * 3 coords = [[0.29706, 0.62396, 0.08575], [0.37673, 0.30411, 0.45416], [0.52324, 0.10651, 0.21126], [0.29660, -0.04671, 0.26607], [0.17577, 0.03720, 0.38049], [0.38604, -0.20144, 0.33624], [0.16248, -0.08546, 0.11608]] struct = Structure.from_spacegroup(14, latt, species, coords) self.assertEqual(sulfide_type(struct), None) # Na3PS3O -> sulfide latt = Lattice.orthorhombic(9.51050, 11.54630, 5.93230) species = ["Na"] * 2 + ["S"] * 2 + ["P", "O"] coords = [[0.19920, 0.11580, 0.24950], [0.00000, 0.36840, 0.29380], [0.32210, 0.36730, 0.22530], [0.50000, 0.11910, 0.27210], [0.50000, 0.29400, 0.35500], [0.50000, 0.30300, 0.61140]] struct = Structure.from_spacegroup(36, latt, species, coords) self.assertEqual(sulfide_type(struct), "sulfide")
def test_sulfide_type(self): # NaS2 -> polysulfide latt = Lattice.tetragonal(9.59650, 11.78850) species = ["Na"] * 2 + ["S"] * 2 coords = [[0.00000, 0.00000, 0.17000], [0.27600, 0.25000, 0.12500], [0.03400, 0.25000, 0.29600], [0.14700, 0.11600, 0.40000]] struct = Structure.from_spacegroup(122, latt, species, coords) self.assertEqual(sulfide_type(struct), "polysulfide") # NaCl type NaS -> sulfide latt = Lattice.cubic(5.75) species = ["Na", "S"] coords = [[0.00000, 0.00000, 0.00000], [0.50000, 0.50000, 0.50000]] struct = Structure.from_spacegroup(225, latt, species, coords) self.assertEqual(sulfide_type(struct), "sulfide") # Na2S2O3 -> None (sulfate) latt = Lattice.monoclinic(6.40100, 8.10000, 8.47400, 96.8800) species = ["Na"] * 2 + ["S"] * 2 + ["O"] * 3 coords = [[0.29706, 0.62396, 0.08575], [0.37673, 0.30411, 0.45416], [0.52324, 0.10651, 0.21126], [0.29660, -0.04671, 0.26607], [0.17577, 0.03720, 0.38049], [0.38604, -0.20144, 0.33624], [0.16248, -0.08546, 0.11608]] struct = Structure.from_spacegroup(14, latt, species, coords) self.assertEqual(sulfide_type(struct), None) # Na3PS3O -> sulfide latt = Lattice.orthorhombic(9.51050, 11.54630, 5.93230) species = ["Na"] * 2 + ["S"] * 2 + ["P", "O"] coords = [[0.19920, 0.11580, 0.24950], [0.00000, 0.36840, 0.29380], [0.32210, 0.36730, 0.22530], [0.50000, 0.11910, 0.27210], [0.50000, 0.29400, 0.35500], [0.50000, 0.30300, 0.61140]] struct = Structure.from_spacegroup(36, latt, species, coords) self.assertEqual(sulfide_type(struct), "sulfide") # test for unphysical cells struct.scale_lattice(struct.volume*10) self.assertEqual(sulfide_type(struct), "sulfide")
def get_correction(self, entry): comp = entry.composition if len(comp) == 1: # Skip element entry return 0 correction = 0 # Check for sulfide corrections if Element("S") in comp: sf_type = "sulfide" if entry.data.get("sulfide_type"): sf_type = entry.data["sulfide_type"] elif hasattr(entry, "structure"): sf_type = sulfide_type(entry.structure) if sf_type in self.sulfide_correction: correction += self.sulfide_correction[sf_type] * comp["S"] # Check for oxide, peroxide, superoxide, and ozonide corrections. if Element("O") in comp: if self.correct_peroxide: if entry.data.get("oxide_type"): if entry.data["oxide_type"] in self.oxide_correction: ox_corr = self.oxide_correction[ entry.data["oxide_type"]] correction += ox_corr * comp["O"] if entry.data["oxide_type"] == "hydroxide": ox_corr = self.oxide_correction["oxide"] correction += ox_corr * comp["O"] elif hasattr(entry, "structure"): ox_type, nbonds = oxide_type(entry.structure, 1.05, return_nbonds=True) if ox_type in self.oxide_correction: correction += self.oxide_correction[ox_type] * \ nbonds elif ox_type == "hydroxide": correction += self.oxide_correction["oxide"] * \ comp["O"] else: rform = entry.composition.reduced_formula if rform in UCorrection.common_peroxides: correction += self.oxide_correction["peroxide"] * \ comp["O"] elif rform in UCorrection.common_superoxides: correction += self.oxide_correction["superoxide"] * \ comp["O"] elif rform in UCorrection.ozonides: correction += self.oxide_correction["ozonide"] * \ comp["O"] elif Element("O") in comp.elements and len(comp.elements)\ > 1: correction += self.oxide_correction['oxide'] * \ comp["O"] else: correction += self.oxide_correction['oxide'] * comp["O"] return correction
def list_sulfide_type(list_struc): get_list = [] for structure in list_struc: try: get_list.append(sulfide_type(structure)) except: get_list.append("N/A") continue return get_list
def compute_corrections(self, exp_entries: list, calc_entries: dict) -> dict: """ Computes the corrections and fills in correction, corrections_std_error, and corrections_dict. Args: exp_entries: list of dictionary objects with the following keys/values: {"formula": chemical formula, "exp energy": formation energy in eV/formula unit, "uncertainty": uncertainty in formation energy} calc_entries: dictionary of computed entries, of the form {chemical formula: ComputedEntry} Raises: ValueError: calc_compounds is missing an entry """ self.exp_compounds = exp_entries self.calc_compounds = calc_entries self.names: List[str] = [] self.diffs: List[float] = [] self.coeff_mat: List[List[float]] = [] self.exp_uncer: List[float] = [] # remove any corrections in calc_compounds for entry in self.calc_compounds.values(): entry.correction = 0 for cmpd_info in self.exp_compounds: # to get consistent element ordering in formula name = Composition(cmpd_info["formula"]).reduced_formula allow = True compound = self.calc_compounds.get(name, None) if not compound: warnings.warn( "Compound {} is not found in provided computed entries and is excluded from the fit" .format(name)) continue # filter out compounds with large uncertainties relative_uncertainty = abs(cmpd_info["uncertainty"] / cmpd_info["exp energy"]) if relative_uncertainty > self.max_error: allow = False warnings.warn( "Compound {} is excluded from the fit due to high experimental uncertainty ({}%)" .format(name, relative_uncertainty)) # filter out compounds containing certain polyanions for anion in self.exclude_polyanions: if anion in name or anion in cmpd_info["formula"]: allow = False warnings.warn( "Compound {} contains the polyanion {} and is excluded from the fit" .format(name, anion)) break # filter out compounds that are unstable if isinstance(self.allow_unstable, float): try: eah = compound.data["e_above_hull"] except KeyError: raise ValueError("Missing e above hull data") if eah > self.allow_unstable: allow = False warnings.warn( "Compound {} is unstable and excluded from the fit (e_above_hull = {})" .format(name, eah)) if allow: comp = Composition(name) elems = list(comp.as_dict()) reactants = [] for elem in elems: try: elem_name = Composition(elem).reduced_formula reactants.append(self.calc_compounds[elem_name]) except KeyError: raise ValueError("Computed entries missing " + elem) rxn = ComputedReaction(reactants, [compound]) rxn.normalize_to(comp) energy = rxn.calculated_reaction_energy coeff = [] for specie in self.species: if specie == "oxide": if compound.data["oxide_type"] == "oxide": coeff.append(comp["O"]) self.oxides.append(name) else: coeff.append(0) elif specie == "peroxide": if compound.data["oxide_type"] == "peroxide": coeff.append(comp["O"]) self.peroxides.append(name) else: coeff.append(0) elif specie == "superoxide": if compound.data["oxide_type"] == "superoxide": coeff.append(comp["O"]) self.superoxides.append(name) else: coeff.append(0) elif specie == "S": if Element("S") in comp: sf_type = "sulfide" if compound.data.get("sulfide_type"): sf_type = compound.data["sulfide_type"] elif hasattr(compound, "structure"): sf_type = sulfide_type(compound.structure) if sf_type == "sulfide": coeff.append(comp["S"]) self.sulfides.append(name) else: coeff.append(0) else: coeff.append(0) else: try: coeff.append(comp[specie]) except ValueError: raise ValueError( "We can't detect this specie: {}".format( specie)) self.names.append(name) self.diffs.append( (cmpd_info["exp energy"] - energy) / comp.num_atoms) self.coeff_mat.append([i / comp.num_atoms for i in coeff]) self.exp_uncer.append( (cmpd_info["uncertainty"]) / comp.num_atoms) # for any exp entries with no uncertainty value, assign average uncertainty value sigma = np.array(self.exp_uncer) sigma[sigma == 0] = np.nan with warnings.catch_warnings(): warnings.simplefilter( "ignore", category=RuntimeWarning ) # numpy raises warning if the entire array is nan values mean_uncer = np.nanmean(sigma) sigma = np.where(np.isnan(sigma), mean_uncer, sigma) if np.isnan(mean_uncer): # no uncertainty values for any compounds, don't try to weight popt, self.pcov = curve_fit(_func, self.coeff_mat, self.diffs, p0=np.ones(len(self.species))) else: popt, self.pcov = curve_fit( _func, self.coeff_mat, self.diffs, p0=np.ones(len(self.species)), sigma=sigma, absolute_sigma=True, ) self.corrections = popt.tolist() self.corrections_std_error = np.sqrt(np.diag(self.pcov)).tolist() for i in range(len(self.species)): self.corrections_dict[self.species[i]] = ( round(self.corrections[i], 3), round(self.corrections_std_error[i], 4), ) return self.corrections_dict
def get_correction(self, entry): comp = entry.composition if len(comp) == 1: # Skip element entry return 0 correction = 0 # Check for sulfide corrections if Element("S") in comp: sf_type = "sulfide" if entry.data.get("sulfide_type"): sf_type = entry.data["sulfide_type"] elif hasattr(entry, "structure"): sf_type = sulfide_type(entry.structure) if sf_type in self.sulfide_correction: correction += self.sulfide_correction[sf_type] * comp["S"] # Check for oxide, peroxide, superoxide, and ozonide corrections. if Element("O") in comp: if self.correct_peroxide: if entry.data.get("oxide_type"): if entry.data["oxide_type"] in self.oxide_correction: ox_corr = self.oxide_correction[ entry.data["oxide_type"]] correction += ox_corr * comp["O"] if entry.data["oxide_type"] == "hydroxide": ox_corr = self.oxide_correction["oxide"] correction += ox_corr * comp["O"] elif hasattr(entry, "structure"): ox_type, nbonds = oxide_type(entry.structure, 1.05, return_nbonds=True) if ox_type in self.oxide_correction: correction += self.oxide_correction[ox_type] * \ nbonds elif ox_type == "hydroxide": correction += self.oxide_correction["oxide"] * \ comp["O"] else: warnings.warn( "No structure or oxide_type parameter present. Note " "that peroxide/superoxide corrections are not as " "reliable and relies only on detection of special" "formulas, e.g., Li2O2.") rform = entry.composition.reduced_formula if rform in UCorrection.common_peroxides: correction += self.oxide_correction["peroxide"] * \ comp["O"] elif rform in UCorrection.common_superoxides: correction += self.oxide_correction["superoxide"] * \ comp["O"] elif rform in UCorrection.ozonides: correction += self.oxide_correction["ozonide"] * \ comp["O"] elif Element("O") in comp.elements and len(comp.elements) \ > 1: correction += self.oxide_correction['oxide'] * \ comp["O"] else: correction += self.oxide_correction['oxide'] * comp["O"] return correction
def get_correction(self, entry): comp = entry.composition if len(comp) == 1: # Skip element entry return 0 correction = 0 # Check for sulfide corrections if Element("S") in comp: sf_type = "sulfide" if entry.data.get("sulfide_type"): sf_type = entry.data["sulfide_type"] elif hasattr(entry, "structure"): sf_type = sulfide_type(entry.structure) if sf_type in self.sulfide_correction: correction += self.sulfide_correction[sf_type] * comp["S"] # Check for oxide, peroxide, superoxide, and ozonide corrections. if Element("O") in comp: if self.correct_peroxide: if entry.data.get("oxide_type"): if entry.data["oxide_type"] in self.oxide_correction: ox_corr = self.oxide_correction[ entry.data["oxide_type"]] correction += ox_corr * comp["O"] if entry.data["oxide_type"] == "hydroxide": ox_corr = self.oxide_correction["oxide"] correction += ox_corr * comp["O"] elif hasattr(entry, "structure"): ox_type, nbonds = oxide_type(entry.structure, 1.05, return_nbonds=True) if ox_type in self.oxide_correction: correction += self.oxide_correction[ox_type] * \ nbonds elif ox_type == "hydroxide": correction += self.oxide_correction["oxide"] * \ comp["O"] else: warnings.warn( "No structure or oxide_type parameter present. Note" "that peroxide/superoxide corrections are not as " "reliable and relies only on detection of special" "formulas, e.g., Li2O2.") rform = entry.composition.reduced_formula if rform in UCorrection.common_peroxides: correction += self.oxide_correction["peroxide"] * \ comp["O"] elif rform in UCorrection.common_superoxides: correction += self.oxide_correction["superoxide"] * \ comp["O"] elif rform in UCorrection.ozonides: correction += self.oxide_correction["ozonide"] * \ comp["O"] elif Element("O") in comp.elements and len(comp.elements)\ > 1: correction += self.oxide_correction['oxide'] * \ comp["O"] else: correction += self.oxide_correction['oxide'] * comp["O"] return correction