def test_time(self): a = Time(20, "h") self.assertAlmostEqual(float(a.to("s")), 3600 * 20) #Test left and right multiplication. b = a * 3 self.assertAlmostEqual(float(b), 60.0) self.assertEqual(str(b.unit), "h") self.assertEqual(float(3 * a), 60.0)
def slurm_parse_timestr(s): """ A slurm time parser. Accepts a string in one the following forms: # "days-hours", # "days-hours:minutes", # "days-hours:minutes:seconds". # "minutes", # "minutes:seconds", # "hours:minutes:seconds", Returns: Time in seconds. Raises: `ValueError` if string is not valid. """ days, hours, minutes, seconds = 0, 0, 0, 0 if type(s) == type(1): return Time(s, "s") if '-' in s: # "days-hours", # "days-hours:minutes", # "days-hours:minutes:seconds". days, s = s.split("-") days = int(days) if ':' not in s: hours = int(float(s)) elif s.count(':') == 1: hours, minutes = map(int, s.split(':')) elif s.count(':') == 2: hours, minutes, seconds = map(int, s.split(':')) else: raise ValueError("More that 2 ':' in string!") else: # "minutes", # "minutes:seconds", # "hours:minutes:seconds", if ':' not in s: minutes = int(float(s)) elif s.count(':') == 1: minutes, seconds = map(int, s.split(':')) elif s.count(':') == 2: hours, minutes, seconds = map(int, s.split(':')) else: raise ValueError("More than 2 ':' in string!") return Time((days * 24 + hours) * 3600 + minutes * 60 + seconds, "s")
def test_compound_operations(self): g = 10 * Length(1, "m") / (Time(1, "s") ** 2) e = Mass(1, "kg") * g * Length(1, "m") self.assertEqual(str(e), "10.0 N m") form_e = FloatWithUnit(10, unit="kJ mol^-1") self.assertEqual(str(form_e.to("eV atom^-1")), "0.103642691905 eV atom^-1") self.assertRaises(UnitError, form_e.to, "m s^-1") a = FloatWithUnit(1.0, "Ha^3") self.assertEqual(str(a.to("J^3")), "8.28672661615e-53 J^3") a = FloatWithUnit(1.0, "Ha bohr^-2") self.assertEqual(str(a.to("J m^-2")), "1556.89291457 J m^-2")
def time2pbspro(timeval, unit="s"): """ Convert a number representing a time value in the given unit (Default: seconds) to a string following the PbsPro convention: "hours:minutes:seconds". >>> assert time2pbspro(2, unit="d") == '48:0:0' """ h, m, s = 3600, 60, 1 timeval = Time(timeval, unit).to("s") hours, minutes = divmod(timeval, h) minutes, secs = divmod(minutes, m) return "%d:%d:%d" % (hours, minutes, secs)
def time2loadlever(timeval, unit="s"): """ Convert a number representing a time value in the given unit (Default: seconds) to a string following the LoadLever convention. format hh:mm:ss (hours:minutes:seconds) >>> assert time2loadlever(2, unit="d") == '48:00:00' """ h, m, s = 3600, 60, 1 timeval = Time(timeval, unit).to("s") hours, minutes = divmod(timeval, h) minutes, secs = divmod(minutes, m) return "%d:%02d:%02d" % (hours, minutes, secs)
def test_compound_operations(self): g = 10 * Length(1, "m") / (Time(1, "s")**2) e = Mass(1, "kg") * g * Length(1, "m") self.assertEqual(str(e), "10.0 N m") form_e = FloatWithUnit(10, unit="kJ mol^-1").to("eV atom^-1") self.assertAlmostEqual(float(form_e), 0.103642691905) self.assertEqual(str(form_e.unit), "eV atom^-1") self.assertRaises(UnitError, form_e.to, "m s^-1") a = FloatWithUnit(1.0, "Ha^3") b = a.to("J^3") self.assertAlmostEqual(b, 8.28672661615e-53) self.assertEqual(str(b.unit), "J^3") a = FloatWithUnit(1.0, "Ha bohr^-2") b = a.to("J m^-2") self.assertAlmostEqual(b, 1556.893078472351) self.assertEqual(str(b.unit), "J m^-2")
def time2slurm(timeval, unit="s"): """ Convert a number representing a time value in the given unit (Default: seconds) to a string following the slurm convention: "days-hours:minutes:seconds". >>> assert time2slurm(61) == '0-0:1:1' and time2slurm(60*60+1) == '0-1:0:1' >>> assert time2slurm(0.5, unit="h") == '0-0:30:0' """ d, h, m, s = 24 * 3600, 3600, 60, 1 timeval = Time(timeval, unit).to("s") days, hours = divmod(timeval, d) hours, minutes = divmod(hours, h) minutes, secs = divmod(minutes, m) return "%d-%d:%d:%d" % (days, hours, minutes, secs)
def __init__(self, entry1, entry2, working_ion_entry): #initialize some internal variables working_element = working_ion_entry.composition.elements[0] entry_charge = entry1 entry_discharge = entry2 if entry_charge.composition.get_atomic_fraction(working_element) \ > entry2.composition.get_atomic_fraction(working_element): (entry_charge, entry_discharge) = (entry_discharge, entry_charge) comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition ion_sym = working_element.symbol frame_charge_comp = Composition({ el: comp_charge[el] for el in comp_charge if el.symbol != ion_sym }) frame_discharge_comp = Composition({ el: comp_discharge[el] for el in comp_discharge if el.symbol != ion_sym }) #Data validation #check that the ion is just a single element if not working_ion_entry.composition.is_element: raise ValueError("VoltagePair: The working ion specified must be " "an element") #check that at least one of the entries contains the working element if not comp_charge.get_atomic_fraction(working_element) > 0 and \ not comp_discharge.get_atomic_fraction(working_element) > 0: raise ValueError("VoltagePair: The working ion must be present in " "one of the entries") #check that the entries do not contain the same amount of the workin #element if comp_charge.get_atomic_fraction(working_element) == \ comp_discharge.get_atomic_fraction(working_element): raise ValueError("VoltagePair: The working ion atomic percentage " "cannot be the same in both the entries") #check that the frameworks of the entries are equivalent if not frame_charge_comp.reduced_formula == \ frame_discharge_comp.reduced_formula: raise ValueError("VoltagePair: the specified entries must have the" " same compositional framework") #Initialize normalization factors, charged and discharged entries valence_list = Element(ion_sym).oxidation_states working_ion_valence = max(valence_list) (self.framework, norm_charge) = frame_charge_comp.get_reduced_composition_and_factor() norm_discharge = \ frame_discharge_comp.get_reduced_composition_and_factor()[1] self._working_ion_entry = working_ion_entry #Initialize normalized properties self._vol_charge = entry_charge.structure.volume / norm_charge self._vol_discharge = entry_discharge.structure.volume / norm_discharge comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition self._mass_charge = comp_charge.weight / norm_charge self._mass_discharge = comp_discharge.weight / norm_discharge self._num_ions_transferred = \ (comp_discharge[working_element] / norm_discharge) \ - (comp_charge[working_element] / norm_charge) self._voltage = \ (((entry_charge.energy / norm_charge) - (entry_discharge.energy / norm_discharge)) / \ self._num_ions_transferred + working_ion_entry.energy_per_atom) / working_ion_valence self._mAh = self._num_ions_transferred * Charge(1, "e").to("C") * \ Time(1, "s").to("h") * AVOGADROS_CONST * 1000 * working_ion_valence #Step 4: add (optional) hull and muO2 data self.decomp_e_charge = \ entry_charge.data.get("decomposition_energy", None) self.decomp_e_discharge = \ entry_discharge.data.get("decomposition_energy", None) self.muO2_charge = entry_charge.data.get("muO2", None) self.muO2_discharge = entry_discharge.data.get("muO2", None) self.entry_charge = entry_charge self.entry_discharge = entry_discharge self.normalization_charge = norm_charge self.normalization_discharge = norm_discharge self._frac_charge = comp_charge.get_atomic_fraction(working_element) self._frac_discharge = \ comp_discharge.get_atomic_fraction(working_element)
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 from_entries(cls, entry1, entry2, working_ion_entry): """ Args: entry1: Entry corresponding to one of the entries in the voltage step. entry2: Entry corresponding to the other entry in the voltage step. working_ion_entry: A single ComputedEntry or PDEntry representing the element that carries charge across the battery, e.g. Li. """ # initialize some internal variables working_element = working_ion_entry.composition.elements[0] entry_charge = entry1 entry_discharge = entry2 if entry_charge.composition.get_atomic_fraction( working_element) > entry2.composition.get_atomic_fraction( working_element): (entry_charge, entry_discharge) = (entry_discharge, entry_charge) comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition ion_sym = working_element.symbol frame_charge_comp = Composition({ el: comp_charge[el] for el in comp_charge if el.symbol != ion_sym }) frame_discharge_comp = Composition({ el: comp_discharge[el] for el in comp_discharge if el.symbol != ion_sym }) # Data validation # check that the ion is just a single element if not working_ion_entry.composition.is_element: raise ValueError( "VoltagePair: The working ion specified must be an element") # check that at least one of the entries contains the working element if (not comp_charge.get_atomic_fraction(working_element) > 0 and not comp_discharge.get_atomic_fraction(working_element) > 0): raise ValueError( "VoltagePair: The working ion must be present in one of the entries" ) # check that the entries do not contain the same amount of the workin # element if comp_charge.get_atomic_fraction( working_element) == comp_discharge.get_atomic_fraction( working_element): raise ValueError( "VoltagePair: The working ion atomic percentage cannot be the same in both the entries" ) # check that the frameworks of the entries are equivalent if not frame_charge_comp.reduced_formula == frame_discharge_comp.reduced_formula: raise ValueError( "VoltagePair: the specified entries must have the same compositional framework" ) # Initialize normalization factors, charged and discharged entries valence_list = Element(ion_sym).oxidation_states working_ion_valence = abs(max(valence_list)) ( framework, norm_charge, ) = frame_charge_comp.get_reduced_composition_and_factor() norm_discharge = frame_discharge_comp.get_reduced_composition_and_factor( )[1] # Initialize normalized properties if hasattr(entry_charge, "structure"): _vol_charge = entry_charge.structure.volume / norm_charge else: _vol_charge = entry_charge.data.get("volume") if hasattr(entry_discharge, "structure"): _vol_discharge = entry_discharge.structure.volume / norm_discharge else: _vol_discharge = entry_discharge.data.get("volume") comp_charge = entry_charge.composition comp_discharge = entry_discharge.composition _mass_charge = comp_charge.weight / norm_charge _mass_discharge = comp_discharge.weight / norm_discharge _num_ions_transferred = ( comp_discharge[working_element] / norm_discharge) - (comp_charge[working_element] / norm_charge) _voltage = ( ((entry_charge.energy / norm_charge) - (entry_discharge.energy / norm_discharge)) / _num_ions_transferred + working_ion_entry.energy_per_atom) / working_ion_valence _mAh = _num_ions_transferred * Charge(1, "e").to("C") * Time( 1, "s").to("h") * N_A * 1000 * working_ion_valence _frac_charge = comp_charge.get_atomic_fraction(working_element) _frac_discharge = comp_discharge.get_atomic_fraction(working_element) vpair = InsertionVoltagePair( # pylint: disable=E1123 voltage=_voltage, mAh=_mAh, mass_charge=_mass_charge, mass_discharge=_mass_discharge, vol_charge=_vol_charge, vol_discharge=_vol_discharge, frac_charge=_frac_charge, frac_discharge=_frac_discharge, working_ion_entry=working_ion_entry, entry_charge=entry_charge, entry_discharge=entry_discharge, framework_formula=framework.reduced_formula, ) # Step 4: add (optional) hull and muO2 data vpair.decomp_e_charge = entry_charge.data.get("decomposition_energy", None) vpair.decomp_e_discharge = entry_discharge.data.get( "decomposition_energy", None) vpair.muO2_charge = entry_charge.data.get("muO2", None) vpair.muO2_discharge = entry_discharge.data.get("muO2", None) return vpair
def test_time(self): a = Time(20, "h") self.assertAlmostEqual(a.to("s"), 3600 * 20) #Test left and right multiplication. self.assertEqual(str(a * 3), "60.0 h") self.assertEqual(str(3 * a), "60.0 h")
def timelimit_parser(s): """Convert a float or a string into time in seconds.""" try: return Time(float(s), "s") except ValueError: return slurm_parse_timestr(s)