def test_negative_compositions(self): self.assertEqual(Composition("Li-1(PO-1)4", allow_negative=True).formula, "Li-1 P4 O-4") self.assertEqual(Composition("Li-1(PO-1)4", allow_negative=True).reduced_formula, "Li-1(PO-1)4") self.assertEqual( Composition("Li-2Mg4", allow_negative=True).reduced_composition, Composition("Li-1Mg2", allow_negative=True) ) self.assertEqual( Composition("Li-2.5Mg4", allow_negative=True).reduced_composition, Composition("Li-2.5Mg4", allow_negative=True), ) # test math c1 = Composition("LiCl", allow_negative=True) c2 = Composition("Li") self.assertEqual(c1 - 2 * c2, Composition({"Li": -1, "Cl": 1}, allow_negative=True)) self.assertEqual((c1 + c2).allow_negative, True) self.assertEqual(c1 / -1, Composition("Li-1Cl-1", allow_negative=True)) # test num_atoms c1 = Composition("Mg-1Li", allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.get_atomic_fraction("Mg"), 0.5) self.assertEqual(c1.get_atomic_fraction("Li"), 0.5) self.assertEqual(c1.fractional_composition, Composition("Mg-0.5Li0.5", allow_negative=True)) # test copy self.assertEqual(c1.copy(), c1) # test species c1 = Composition({"Mg": 1, "Mg2+": -1}, allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.element_composition, Composition()) self.assertEqual(c1.average_electroneg, 1.31)
def test_negative_compositions(self): self.assertEqual(Composition('Li-1(PO-1)4', allow_negative=True).formula, 'Li-1 P4 O-4') self.assertEqual(Composition('Li-1(PO-1)4', allow_negative=True).reduced_formula, 'Li-1(PO-1)4') self.assertEqual(Composition('Li-2Mg4', allow_negative=True).reduced_composition, Composition('Li-1Mg2', allow_negative=True)) self.assertEqual(Composition('Li-2.5Mg4', allow_negative=True).reduced_composition, Composition('Li-2.5Mg4', allow_negative=True)) #test math c1 = Composition('LiCl', allow_negative=True) c2 = Composition('Li') self.assertEqual(c1 - 2 * c2, Composition({'Li': -1, 'Cl': 1}, allow_negative=True)) self.assertEqual((c1 + c2).allow_negative, True) self.assertEqual(c1 / -1, Composition('Li-1Cl-1', allow_negative=True)) #test num_atoms c1 = Composition('Mg-1Li', allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.get_atomic_fraction('Mg'), 0.5) self.assertEqual(c1.get_atomic_fraction('Li'), 0.5) self.assertEqual(c1.fractional_composition, Composition('Mg-0.5Li0.5', allow_negative=True)) #test copy self.assertEqual(c1.copy(), c1) #test species c1 = Composition({'Mg':1, 'Mg2+':-1}, allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.element_composition, Composition()) self.assertEqual(c1.average_electroneg, 1.31)
def parse_sites(row): comp = Composition(row) # get elements in the order of the compound // pymatgen's comp.formula gives elements sorted by electronegativity, # we don't want that. We want to preserve A(A')B(B')O3 order elements = [''.join([i for i in elem if not i.isdigit()]).replace(".", "") for elem in comp.formula.split()] indices = {e: row.find(e) for e in elements} A1, A2, B1, B2 = '_', '_', '_', '_' A1_frac, A2_frac, B1_frac, B2_frac, O_frac = 0, 0, 0, 0, 0 if (len(row.split("(")[0])==0 or len(row.split("(")[0])>8): # type =1 ### value '8' is hard coded and it works. But check A1, A2, B1, O = sorted(elements, key=indices.get) elem_fracs = [comp.get_atomic_fraction(Element(i))*comp.num_atoms for i in [A1, A2, B1, O]] # now formulate A(A')B(B')O3 format A1_frac, A2_frac, B1_frac, O_frac = [round(i*3.0/elem_fracs[-1], 3) for i in elem_fracs] else: # type = 2 A1, B1, B2, O = sorted(elements, key=indices.get) elem_fracs = [comp.get_atomic_fraction(Element(i))*comp.num_atoms for i in [A1, B1, B2, O]] A1_frac, B1_frac, B2_frac, O_frac = [round(i*3.0/elem_fracs[-1], 3) for i in elem_fracs] return A1, A1_frac, A2, A2_frac, B1, B1_frac, B2, B2_frac, O, O_frac
def test_negative_compositions(self): self.assertEqual( Composition("Li-1(PO-1)4", allow_negative=True).formula, "Li-1 P4 O-4") self.assertEqual( Composition("Li-1(PO-1)4", allow_negative=True).reduced_formula, "Li-1(PO-1)4", ) self.assertEqual( Composition("Li-2Mg4", allow_negative=True).reduced_composition, Composition("Li-1Mg2", allow_negative=True), ) self.assertEqual( Composition("Li-2.5Mg4", allow_negative=True).reduced_composition, Composition("Li-2.5Mg4", allow_negative=True), ) # test math c1 = Composition("LiCl", allow_negative=True) c2 = Composition("Li") self.assertEqual(c1 - 2 * c2, Composition({ "Li": -1, "Cl": 1 }, allow_negative=True)) self.assertEqual((c1 + c2).allow_negative, True) self.assertEqual(c1 / -1, Composition("Li-1Cl-1", allow_negative=True)) # test num_atoms c1 = Composition("Mg-1Li", allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.get_atomic_fraction("Mg"), 0.5) self.assertEqual(c1.get_atomic_fraction("Li"), 0.5) self.assertEqual(c1.fractional_composition, Composition("Mg-0.5Li0.5", allow_negative=True)) # test copy self.assertEqual(c1.copy(), c1) # test species c1 = Composition({"Mg": 1, "Mg2+": -1}, allow_negative=True) self.assertEqual(c1.num_atoms, 2) self.assertEqual(c1.element_composition, Composition()) self.assertEqual(c1.average_electroneg, 1.31)
def from_steps(step1, step2, normalization_els): """ Creates a ConversionVoltagePair from two steps in the element profile from a PD analysis. Args: step1: Starting step step2: Ending step normalization_els: Elements to normalize the reaction by. To ensure correct capacities. """ working_ion_entry = step1["element_reference"] working_ion = working_ion_entry.composition.elements[0].symbol voltage = -step1["chempot"] + working_ion_entry.energy_per_atom mAh = (step2["evolution"] - step1["evolution"]) \ * Charge(1, "e").to("C") * Time(1, "s").to("h") * N_A * 1000 licomp = Composition(working_ion) prev_rxn = step1["reaction"] reactants = {comp: abs(prev_rxn.get_coeff(comp)) for comp in prev_rxn.products if comp != licomp} curr_rxn = step2["reaction"] products = {comp: abs(curr_rxn.get_coeff(comp)) for comp in curr_rxn.products if comp != licomp} reactants[licomp] = (step2["evolution"] - step1["evolution"]) rxn = BalancedReaction(reactants, products) for el, amt in normalization_els.items(): if rxn.get_el_amount(el) > 1e-6: rxn.normalize_to_element(el, amt) break prev_mass_dischg = sum([prev_rxn.all_comp[i].weight * abs(prev_rxn.coeffs[i]) for i in range(len(prev_rxn.all_comp))]) / 2 vol_charge = sum([abs(prev_rxn.get_coeff(e.composition)) * e.structure.volume for e in step1["entries"] if e.composition.reduced_formula != working_ion]) mass_discharge = sum([curr_rxn.all_comp[i].weight * abs(curr_rxn.coeffs[i]) for i in range(len(curr_rxn.all_comp))]) / 2 mass_charge = prev_mass_dischg mass_discharge = mass_discharge vol_discharge = sum([abs(curr_rxn.get_coeff(e.composition)) * e.structure.volume for e in step2["entries"] if e.composition.reduced_formula != working_ion]) totalcomp = Composition({}) for comp in prev_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(prev_rxn.get_coeff(comp)) frac_charge = totalcomp.get_atomic_fraction(Element(working_ion)) totalcomp = Composition({}) for comp in curr_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(curr_rxn.get_coeff(comp)) frac_discharge = totalcomp.get_atomic_fraction(Element(working_ion)) rxn = rxn entries_charge = step2["entries"] entries_discharge = step1["entries"] return ConversionVoltagePair(rxn, voltage, mAh, vol_charge, vol_discharge, mass_charge, mass_discharge, frac_charge, frac_discharge, entries_charge, entries_discharge, working_ion_entry)
def from_steps(cls, step1, step2, normalization_els, framework_formula=None): """ Creates a ConversionVoltagePair from two steps in the element profile from a PD analysis. Args: step1: Starting step step2: Ending step normalization_els: Elements to normalize the reaction by. To ensure correct capacities. """ working_ion_entry = step1["element_reference"] working_ion = working_ion_entry.composition.elements[0].symbol working_ion_valence = max(Element(working_ion).oxidation_states) voltage = (-step1["chempot"] + working_ion_entry.energy_per_atom) / working_ion_valence mAh = ((step2["evolution"] - step1["evolution"]) * Charge(1, "e").to("C") * Time(1, "s").to("h") * N_A * 1000 * working_ion_valence) licomp = Composition(working_ion) prev_rxn = step1["reaction"] reactants = { comp: abs(prev_rxn.get_coeff(comp)) for comp in prev_rxn.products if comp != licomp } curr_rxn = step2["reaction"] products = { comp: abs(curr_rxn.get_coeff(comp)) for comp in curr_rxn.products if comp != licomp } reactants[licomp] = step2["evolution"] - step1["evolution"] rxn = BalancedReaction(reactants, products) for el, amt in normalization_els.items(): if rxn.get_el_amount(el) > 1e-6: rxn.normalize_to_element(el, amt) break prev_mass_dischg = (sum([ prev_rxn.all_comp[i].weight * abs(prev_rxn.coeffs[i]) for i in range(len(prev_rxn.all_comp)) ]) / 2) vol_charge = sum([ abs(prev_rxn.get_coeff(e.composition)) * e.structure.volume for e in step1["entries"] if e.composition.reduced_formula != working_ion ]) mass_discharge = (sum([ curr_rxn.all_comp[i].weight * abs(curr_rxn.coeffs[i]) for i in range(len(curr_rxn.all_comp)) ]) / 2) mass_charge = prev_mass_dischg mass_discharge = mass_discharge vol_discharge = sum([ abs(curr_rxn.get_coeff(e.composition)) * e.structure.volume for e in step2["entries"] if e.composition.reduced_formula != working_ion ]) totalcomp = Composition({}) for comp in prev_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(prev_rxn.get_coeff(comp)) frac_charge = totalcomp.get_atomic_fraction(Element(working_ion)) totalcomp = Composition({}) for comp in curr_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(curr_rxn.get_coeff(comp)) frac_discharge = totalcomp.get_atomic_fraction(Element(working_ion)) rxn = rxn entries_charge = step2["entries"] entries_discharge = step1["entries"] return cls( rxn=rxn, voltage=voltage, mAh=mAh, vol_charge=vol_charge, vol_discharge=vol_discharge, mass_charge=mass_charge, mass_discharge=mass_discharge, frac_charge=frac_charge, frac_discharge=frac_discharge, entries_charge=entries_charge, entries_discharge=entries_discharge, working_ion_entry=working_ion_entry, _framework_formula=framework_formula, )
def predict_k_g_list_of_entries(entries): """ Predict bulk (K) and shear (G) moduli from a list of entries in the same format as retrieved from the Materials Project API. """ lvpa_list = [] cepa_list = [] rowH1A_list = [] rowHn3A_list = [] xH4A_list = [] xHn4A_list = [] matid_list = [] k_list = [] g_list = [] caveats_list = [] aiab_problem_list = [] # TODO: figure out if closing the query engine (using 'with' ctx mgr) is an issue # If it is a problem then try manually doing a session.close() for MPRester, but ignore for qe for entry in entries: caveats_str = '' aiab_flag = False f_block_flag = False weight_list = [] energy_list = [] row_list = [] x_list = [] # Construct per-element lists for this material composition = Composition(str(entry["pretty_formula"])) for element_key, amount in composition.get_el_amt_dict().items(): element = Element(element_key) weight_list.append(composition.get_atomic_fraction(element)) aiab_energy = get_element_aiab_energy( element_key) # aiab = atom-in-a-box if aiab_energy is None: aiab_flag = True break energy_list.append(aiab_energy) if element.block == 'f': f_block_flag = True row_list.append(element.row) x_list.append(element.X) # On error, add material to aiab_problem_list and continue with next material if aiab_flag: aiab_problem_list.append(str(entry["material_id"])) continue # Check caveats if bool(entry["is_hubbard"]): if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_HUBBARD if f_block_flag: if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_F_BLOCK # Calculate intermediate weighted averages (WA) for this material ewa = np.average(energy_list, weights=weight_list) # atom-in-a-box energy WA print(str(entry["material_id"])) # Append descriptors for this material to descriptor lists lvpa_list.append( math.log10(float(entry["volume"]) / float(entry["nsites"]))) cepa_list.append(float(entry["energy_per_atom"]) - ewa) rowH1A_list.append(holder_mean(row_list, 1.0, weights=weight_list)) rowHn3A_list.append(holder_mean(row_list, -3.0, weights=weight_list)) xH4A_list.append(holder_mean(x_list, 4.0, weights=weight_list)) xHn4A_list.append(holder_mean(x_list, -4.0, weights=weight_list)) matid_list.append(str(entry["material_id"])) caveats_list.append(caveats_str) # Check that at least one valid material was provided num_predictions = len(matid_list) if num_predictions > 0: # Construct descriptor arrays if (len(lvpa_list) != num_predictions or len(cepa_list) != num_predictions or len(rowH1A_list) != num_predictions or len(rowHn3A_list) != num_predictions or len(xH4A_list) != num_predictions or len(xHn4A_list) != num_predictions): return (None, None, None, None) k_descriptors = np.ascontiguousarray( [lvpa_list, rowH1A_list, cepa_list, xHn4A_list], dtype=float) g_descriptors = np.ascontiguousarray( [cepa_list, lvpa_list, rowHn3A_list, xH4A_list], dtype=float) # Allocate prediction arrays k_predictions = np.empty(num_predictions) g_predictions = np.empty(num_predictions) # Make predictions k_filename = os.path.join(os.path.dirname(__file__), DATAFILE_K) g_filename = os.path.join(os.path.dirname(__file__), DATAFILE_G) gbml.core.predict(k_filename, num_predictions, k_descriptors, k_predictions) gbml.core.predict(g_filename, num_predictions, g_descriptors, g_predictions) k_list = np.power(10.0, k_predictions).tolist() g_list = np.power(10.0, g_predictions).tolist() # Append aiab problem cases for entry in aiab_problem_list: matid_list.append(entry) k_list.append(None) g_list.append(None) caveats_list.append(CAVEAT_AIAB) if len(matid_list) == 0: return (None, None, None, None) else: return (matid_list, k_list, g_list, caveats_list)
def predict_k_g_list_of_entries(entries): """ Predict bulk (K) and shear (G) moduli from a list of entries in the same format as retrieved from the Materials Project API. """ lvpa_list = [] cepa_list = [] rowH1A_list = [] rowHn3A_list = [] xH4A_list = [] xHn4A_list = [] matid_list = [] k_list = [] g_list = [] caveats_list = [] aiab_problem_list = [] # TODO: figure out if closing the query engine (using 'with' ctx mgr) is an issue # If it is a problem then try manually doing a session.close() for MPRester, but ignore for qe for entry in entries: caveats_str = '' aiab_flag = False f_block_flag = False weight_list = [] energy_list = [] row_list = [] x_list = [] # Construct per-element lists for this material composition = Composition(str(entry["pretty_formula"])) for element_key, amount in composition.get_el_amt_dict().items(): element = Element(element_key) weight_list.append(composition.get_atomic_fraction(element)) aiab_energy = get_element_aiab_energy(element_key) # aiab = atom-in-a-box if aiab_energy is None: aiab_flag = True break energy_list.append(aiab_energy) if element.block == 'f': f_block_flag = True row_list.append(element.row) x_list.append(element.X) # On error, add material to aiab_problem_list and continue with next material if aiab_flag: aiab_problem_list.append(str(entry["material_id"])) continue # Check caveats if bool(entry["is_hubbard"]): if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_HUBBARD if f_block_flag: if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_F_BLOCK # Calculate intermediate weighted averages (WA) for this material ewa = np.average(energy_list, weights=weight_list) # atom-in-a-box energy WA print(str(entry["material_id"])) # Append descriptors for this material to descriptor lists lvpa_list.append(math.log10(float(entry["volume"]) / float(entry["nsites"]))) cepa_list.append(float(entry["energy_per_atom"]) - ewa) rowH1A_list.append(holder_mean(row_list, 1.0, weights=weight_list)) rowHn3A_list.append(holder_mean(row_list, -3.0, weights=weight_list)) xH4A_list.append(holder_mean(x_list, 4.0, weights=weight_list)) xHn4A_list.append(holder_mean(x_list, -4.0, weights=weight_list)) matid_list.append(str(entry["material_id"])) caveats_list.append(caveats_str) # Check that at least one valid material was provided num_predictions = len(matid_list) if num_predictions > 0: # Construct descriptor arrays if (len(lvpa_list) != num_predictions or len(cepa_list) != num_predictions or len(rowH1A_list) != num_predictions or len(rowHn3A_list) != num_predictions or len(xH4A_list) != num_predictions or len(xHn4A_list) != num_predictions): return (None, None, None, None) k_descriptors = np.ascontiguousarray([lvpa_list, rowH1A_list, cepa_list, xHn4A_list], dtype=float) g_descriptors = np.ascontiguousarray([cepa_list, lvpa_list, rowHn3A_list, xH4A_list], dtype=float) # Allocate prediction arrays k_predictions = np.empty(num_predictions) g_predictions = np.empty(num_predictions) # Make predictions k_filename = os.path.join(os.path.dirname(__file__),DATAFILE_K) g_filename = os.path.join(os.path.dirname(__file__),DATAFILE_G) gbml.core.predict(k_filename, num_predictions, k_descriptors, k_predictions) gbml.core.predict(g_filename, num_predictions, g_descriptors, g_predictions) k_list = np.power(10.0, k_predictions).tolist() g_list = np.power(10.0, g_predictions).tolist() # Append aiab problem cases for entry in aiab_problem_list: matid_list.append(entry) k_list.append(None) g_list.append(None) caveats_list.append(CAVEAT_AIAB) if len(matid_list) == 0: return (None, None, None, None) else: return (matid_list, k_list, g_list, caveats_list)
def predict_k_g_list(material_id_list, api_key=API_KEY, query_engine=None): """ Predict bulk (K) and shear (G) moduli for a list of materials. :param material_id_list: list of material-ID strings :param api_key: The API key used by pymatgen.matproj.rest.MPRester to connect to Materials Project :param query_engine: (Optional) QueryEngine object used to query materials instead of MPRester :return: (matid_list, predicted_k_list, predicted_g_list, caveats_list) Note that len(matid_list) may be less than len(material_id_list), if any requested material-IDs are not found. """ if len(material_id_list) == 0 or not isinstance(material_id_list, list): return (None, None, None, None ) # material_id_list not properly specified lvpa_list = [] cepa_list = [] rowH1A_list = [] rowHn3A_list = [] xH4A_list = [] xHn4A_list = [] matid_list = [] k_list = [] g_list = [] caveats_list = [] aiab_problem_list = [] # TODO: figure out if closing the query engine (using 'with' ctx mgr) is an issue # If it is a problem then try manually doing a session.close() for MPRester, but ignore for qe mpr = _get_mp_query(api_key, query_engine) for entry in mpr.query(criteria={"task_id": { "$in": material_id_list }}, properties=[ "material_id", "pretty_formula", "nsites", "volume", "energy_per_atom", "is_hubbard" ]): caveats_str = '' aiab_flag = False f_block_flag = False weight_list = [] energy_list = [] row_list = [] x_list = [] # Construct per-element lists for this material composition = Composition(str(entry["pretty_formula"])) for element_key, amount in composition.get_el_amt_dict().iteritems(): element = Element(element_key) weight_list.append(composition.get_atomic_fraction(element)) aiab_energy = get_element_aiab_energy( element_key) # aiab = atom-in-a-box if aiab_energy is None: aiab_flag = True break energy_list.append(aiab_energy) if element.block == 'f': f_block_flag = True row_list.append(element.row) x_list.append(element.X) # On error, add material to aiab_problem_list and continue with next material if aiab_flag: aiab_problem_list.append(str(entry["material_id"])) continue # Check caveats if bool(entry["is_hubbard"]): if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_HUBBARD if f_block_flag: if len(caveats_str) > 0: caveats_str += " " caveats_str += CAVEAT_F_BLOCK # Calculate intermediate weighted averages (WA) for this material ewa = np.average(energy_list, weights=weight_list) # atom-in-a-box energy WA print str(entry["material_id"]) # Append descriptors for this material to descriptor lists lvpa_list.append( math.log10(float(entry["volume"]) / float(entry["nsites"]))) cepa_list.append(float(entry["energy_per_atom"]) - ewa) rowH1A_list.append(holder_mean(row_list, 1.0, weights=weight_list)) rowHn3A_list.append(holder_mean(row_list, -3.0, weights=weight_list)) xH4A_list.append(holder_mean(x_list, 4.0, weights=weight_list)) xHn4A_list.append(holder_mean(x_list, -4.0, weights=weight_list)) matid_list.append(str(entry["material_id"])) caveats_list.append(caveats_str) if isinstance(mpr, MPRester): mpr.session.close() # Check that at least one valid material was provided num_predictions = len(matid_list) if num_predictions > 0: # Construct descriptor arrays if (len(lvpa_list) != num_predictions or len(cepa_list) != num_predictions or len(rowH1A_list) != num_predictions or len(rowHn3A_list) != num_predictions or len(xH4A_list) != num_predictions or len(xHn4A_list) != num_predictions): return (None, None, None, None) k_descriptors = np.ascontiguousarray( [lvpa_list, rowH1A_list, cepa_list, xHn4A_list], dtype=float) g_descriptors = np.ascontiguousarray( [cepa_list, lvpa_list, rowHn3A_list, xH4A_list], dtype=float) # Allocate prediction arrays k_predictions = np.empty(num_predictions) g_predictions = np.empty(num_predictions) # Make predictions k_filename = os.path.join(os.path.dirname(__file__), DATAFILE_K) g_filename = os.path.join(os.path.dirname(__file__), DATAFILE_G) gbml.core.predict(k_filename, num_predictions, k_descriptors, k_predictions) gbml.core.predict(g_filename, num_predictions, g_descriptors, g_predictions) k_list = np.power(10.0, k_predictions).tolist() g_list = np.power(10.0, g_predictions).tolist() # Append aiab problem cases for entry in aiab_problem_list: matid_list.append(entry) k_list.append(None) g_list.append(None) caveats_list.append(CAVEAT_AIAB) if len(matid_list) == 0: return (None, None, None, None) else: return (matid_list, k_list, g_list, caveats_list)
def from_steps(step1, step2, normalization_els): """ Creates a ConversionVoltagePair from two steps in the element profile from a PD analysis. Args: step1: Starting step step2: Ending step normalization_els: Elements to normalize the reaction by. To ensure correct capacities. """ working_ion_entry = step1["element_reference"] working_ion = working_ion_entry.composition.elements[0].symbol voltage = -step1["chempot"] + working_ion_entry.energy_per_atom mAh = (step2["evolution"] - step1["evolution"]) \ * ELECTRON_TO_AMPERE_HOURS * 1000 licomp = Composition.from_formula(working_ion) prev_rxn = step1["reaction"] reactants = { comp: abs(prev_rxn.get_coeff(comp)) for comp in prev_rxn.products if comp != licomp } curr_rxn = step2["reaction"] products = { comp: abs(curr_rxn.get_coeff(comp)) for comp in curr_rxn.products if comp != licomp } reactants[licomp] = (step2["evolution"] - step1["evolution"]) rxn = BalancedReaction(reactants, products) for el, amt in normalization_els.items(): if rxn.get_el_amount(el) != 0: rxn.normalize_to_element(el, amt) break prev_mass_dischg = sum([ prev_rxn.all_comp[i].weight * abs(prev_rxn.coeffs[i]) for i in xrange(len(prev_rxn.all_comp)) ]) / 2 vol_charge = sum([ abs(prev_rxn.get_coeff(e.composition)) * e.structure.volume for e in step1["entries"] if e.composition.reduced_formula != working_ion ]) mass_discharge = sum([ curr_rxn.all_comp[i].weight * abs(curr_rxn.coeffs[i]) for i in xrange(len(curr_rxn.all_comp)) ]) / 2 mass_charge = prev_mass_dischg mass_discharge = mass_discharge vol_discharge = sum([ abs(curr_rxn.get_coeff(e.composition)) * e.structure.volume for e in step2["entries"] if e.composition.reduced_formula != working_ion ]) totalcomp = Composition({}) for comp in prev_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(prev_rxn.get_coeff(comp)) frac_charge = totalcomp.get_atomic_fraction(Element(working_ion)) totalcomp = Composition({}) for comp in curr_rxn.products: if comp.reduced_formula != working_ion: totalcomp += comp * abs(curr_rxn.get_coeff(comp)) frac_discharge = totalcomp.get_atomic_fraction(Element(working_ion)) rxn = rxn entries_charge = step2["entries"] entries_discharge = step1["entries"] return ConversionVoltagePair(rxn, voltage, mAh, vol_charge, vol_discharge, mass_charge, mass_discharge, frac_charge, frac_discharge, entries_charge, entries_discharge, working_ion_entry)