def setUp(self): self.comp = list() self.comp.append(Composition.from_formula("Li3Fe2(PO4)3")) self.comp.append(Composition.from_formula("Li3Fe(PO4)O")) self.comp.append(Composition.from_formula("LiMn2O4")) self.comp.append(Composition.from_formula("Li4O4")) self.comp.append(Composition.from_formula("Li3Fe2Mo3O12")) self.comp.append(Composition.from_formula("Li3Fe2((PO4)3(CO3)5)2")) self.comp.append(Composition.from_formula("Li1.5Si0.5")) self.comp.append(Composition.from_formula("ZnOH")) self.indeterminate_comp = [] self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "Co1", True)) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "Co1", False)) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "co2o3")) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "ncalu")) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "calun")) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "liCoo2n (pO4)2")) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "(co)2 (PO)4")) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("Fee3"))
def test_sub(self): self.assertEqual((self.comp[0] - Composition.from_formula("Li2O")).formula, "Li1 Fe2 P3 O11", "Incorrect composition after addition!") self.assertEqual((self.comp[0] - {"Fe": 2, "O": 3}).formula, "Li3 P3 O9")
def test_sub(self): self.assertEqual( (self.comp[0] - Composition.from_formula("Li2O")).formula, "Li1 Fe2 P3 O11", "Incorrect composition after addition!") self.assertEqual((self.comp[0] - { "Fe": 2, "O": 3 }).formula, "Li3 P3 O9")
def test_reduced_composition(self): correct_reduced_formulas = [ 'Li3Fe2(PO4)3', 'Li3FePO5', 'LiMn2O4', 'Li2O2', 'Li3Fe2(MoO4)3', 'Li3Fe2P6(C5O27)2', 'Li1.5Si0.5', 'ZnHO' ] for i in xrange(len(self.comp)): self.assertEqual( self.comp[i].get_reduced_composition_and_factor()[0], Composition.from_formula(correct_reduced_formulas[i]))
def test_getmu_vertices_stability_phase(self): results = self.analyzer.getmu_vertices_stability_phase(Composition.from_formula("LiFeO2"), Element("O")) self.assertAlmostEqual(len(results), 6) test_equality = False for c in results: if abs(c[Element("O")]+7.115) < 1e-2 and abs(c[Element("Fe")]+6.596) < 1e-2 and \ abs(c[Element("Li")]+3.931) < 1e-2: test_equality = True self.assertTrue(test_equality,"there is an expected vertex missing in the list")
def test_indeterminate_formula(self): correct_formulas = [["Co1"], ["Co1", "C1 O1"], ["Co2 O3", "C1 O5"], ["N1 Ca1 Lu1", "U1 Al1 C1 N1"], ["N1 Ca1 Lu1", "U1 Al1 C1 N1"], ["Li1 Co1 P2 N1 O10", "Li1 P2 C1 N1 O11", "Li1 Co1 Po8 N1 O2", "Li1 Po8 C1 N1 O3"], ["Co2 P4 O4", "Co2 Po4", "P4 C2 O6", "Po4 C2 O2"], []] for i, c in enumerate(correct_formulas): self.assertEqual([Composition.from_formula(comp) for comp in c], self.indeterminate_comp[i])
def test_getmu_vertices_stability_phase(self): results = self.analyzer.getmu_vertices_stability_phase( Composition.from_formula("LiFeO2"), Element("O")) self.assertAlmostEqual(len(results), 6) test_equality = False for c in results: if abs(c[Element("O")]+7.115) < 1e-2 and abs(c[Element("Fe")]+6.596) < 1e-2 and \ abs(c[Element("Li")]+3.931) < 1e-2: test_equality = True self.assertTrue(test_equality, "there is an expected vertex missing in the list")
def test_indeterminate_formula(self): correct_formulas = [["Co1"], ["Co1", "C1 O1"], ["Co2 O3", "C1 O5"], ["N1 Ca1 Lu1", "U1 Al1 C1 N1"], ["N1 Ca1 Lu1", "U1 Al1 C1 N1"], [ "Li1 Co1 P2 N1 O10", "Li1 P2 C1 N1 O11", "Li1 Co1 Po8 N1 O2", "Li1 Po8 C1 N1 O3" ], ["Co2 P4 O4", "Co2 Po4", "P4 C2 O6", "Po4 C2 O2"], []] for i, c in enumerate(correct_formulas): self.assertEqual([Composition.from_formula(comp) for comp in c], self.indeterminate_comp[i])
def __str__(self): reactant_str = [] product_str = [] for i in range(self._num_comp): comp = self._all_comp[i] coeff = self._coeffs[i] red_comp = Composition.from_formula(comp.reduced_formula) scale_factor = comp.num_atoms / red_comp.num_atoms scaled_coeff = coeff * scale_factor if scaled_coeff < 0: reactant_str.append("{:.3f} {}".format(-scaled_coeff, comp.reduced_formula)) elif scaled_coeff > 0: product_str.append("{:.3f} {}".format(scaled_coeff, comp.reduced_formula)) return " + ".join(reactant_str) + " -> " + " + ".join(product_str)
def setUp(self): self.comp = list() self.comp.append(Composition.from_formula("Li3Fe2(PO4)3")) self.comp.append(Composition.from_formula("Li3Fe(PO4)O")) self.comp.append(Composition.from_formula("LiMn2O4")) self.comp.append(Composition.from_formula("Li4O4")) self.comp.append(Composition.from_formula("Li3Fe2Mo3O12")) self.comp.append(Composition.from_formula("Li3Fe2((PO4)3(CO3)5)2")) self.comp.append(Composition.from_formula("Li1.5Si0.5")) self.comp.append(Composition.from_formula("ZnOH")) self.indeterminate_comp = [] self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("Co1", True) ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("Co1", False) ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("co2o3") ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("ncalu") ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("calun") ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "liCoo2n (pO4)2") ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula( "(co)2 (PO)4") ) self.indeterminate_comp.append( Composition.ranked_compositions_from_indeterminate_formula("Fee3"))
def test_getmu_vertices_stability_phase(self): results = self.analyzer.getmu_vertices_stability_phase(Composition.from_formula("LiFeO2"), Element("O")) self.assertAlmostEqual(results[5][Element("O")], -7.11535414) self.assertAlmostEqual(results[10][Element("Li")], -3.93161519) self.assertAlmostEqual(results[0][Element("Fe")], -10.45183356)
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") * AVOGADROS_CONST\ * 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, remove_spectator_species=True) 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 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)
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") * AVOGADROS_CONST\ * 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)
def __init__(self, struct): """ Args: struct: A pymatgen.core.structure.Structure object. """ block = CifFile.CifBlock() latt = struct.lattice comp = struct.composition no_oxi_comp = Composition(comp.formula) block["_symmetry_space_group_name_H-M"] = "P 1" for cell_attr in ['a', 'b', 'c']: block["_cell_length_" + cell_attr] = str(getattr(latt, cell_attr)) for cell_attr in ['alpha', 'beta', 'gamma']: block["_cell_angle_" + cell_attr] = str(getattr(latt, cell_attr)) block["_chemical_name_systematic"] = "Generated by pymatgen" block["_symmetry_Int_Tables_number"] = 1 block["_chemical_formula_structural"] = str(no_oxi_comp .reduced_formula) block["_chemical_formula_sum"] = str(no_oxi_comp.formula) block["_cell_volume"] = str(latt.volume) reduced_comp = Composition.from_formula(no_oxi_comp.reduced_formula) el = no_oxi_comp.elements[0] amt = comp[el] fu = int(amt / reduced_comp[Element(el.symbol)]) block["_cell_formula_units_Z"] = str(fu) block.AddCifItem(([["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"]], [[["1"], ["x, y, z"]]])) contains_oxidation = True try: symbol_to_oxinum = {str(el): float(el.oxi_state) for el in comp.elements} except AttributeError: symbol_to_oxinum = {el.symbol: 0 for el in comp.elements} contains_oxidation = False if contains_oxidation: block.AddCifItem(([["_atom_type_symbol", "_atom_type_oxidation_number"]], [[symbol_to_oxinum.keys(), symbol_to_oxinum.values()]])) atom_site_type_symbol = [] atom_site_symmetry_multiplicity = [] atom_site_fract_x = [] atom_site_fract_y = [] atom_site_fract_z = [] atom_site_attached_hydrogens = [] atom_site_B_iso_or_equiv = [] atom_site_label = [] atom_site_occupancy = [] count = 1 for site in struct: for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(str(sp)) atom_site_symmetry_multiplicity.append("1") atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_attached_hydrogens.append("0") atom_site_B_iso_or_equiv.append(".") atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(str(occu)) count += 1 block["_atom_site_type_symbol"] = atom_site_type_symbol block.AddToLoop("_atom_site_type_symbol", {"_atom_site_label": atom_site_label}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_symmetry_multiplicity": atom_site_symmetry_multiplicity}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_x": atom_site_fract_x}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_y": atom_site_fract_y}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_z": atom_site_fract_z}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_attached_hydrogens": atom_site_attached_hydrogens}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_B_iso_or_equiv": atom_site_B_iso_or_equiv}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_occupancy": atom_site_occupancy}) self._cf = CifFile.CifFile() # AJ says: CIF Block names cannot be more than 75 characters or you # get an Exception self._cf[comp.reduced_formula[0:74]] = block
def get_element_profile(self, element, comp, comp_tol=1e-5): """ Provides the element evolution data for a composition. For example, can be used to analyze Li conversion voltages by varying uLi and looking at the phases formed. Also can be used to analyze O2 evolution by varying uO2. Args: element: An element. Must be in the phase diagram. comp: A Composition comp_tol: The tolerance to use when calculating decompositions. Phases with amounts less than this tolerance are excluded. Defaults to 1e-5. Returns: Evolution data as a list of dictionaries of the following format: [ {'chempot': -10.487582010000001, 'evolution': -2.0, 'reaction': Reaction Object], ...] """ if element not in self._pd.elements: raise ValueError("get_transition_chempots can only be called with" " elements in the phase diagram.") chempots = self.get_transition_chempots(element) stable_entries = self._pd.stable_entries gccomp = Composition({el: amt for el, amt in comp.items() if el != element}) elref = self._pd.el_refs[element] elcomp = Composition.from_formula(element.symbol) prev_decomp = [] evolution = [] def are_same_decomp(decomp1, decomp2): for comp in decomp2: if comp not in decomp1: return False return True for c in chempots: gcpd = GrandPotentialPhaseDiagram( stable_entries, {element: c - 0.01}, self._pd.elements ) analyzer = PDAnalyzer(gcpd) gcdecomp = analyzer.get_decomposition(gccomp) decomp = [gcentry.original_entry.composition for gcentry, amt in gcdecomp.items() if amt > comp_tol] decomp_entries = [gcentry.original_entry for gcentry, amt in gcdecomp.items() if amt > comp_tol] if not are_same_decomp(prev_decomp, decomp): if elcomp not in decomp: decomp.insert(0, elcomp) rxn = Reaction([comp], decomp) rxn.normalize_to(comp) prev_decomp = decomp amt = -rxn.coeffs[rxn.all_comp.index(elcomp)] evolution.append({'chempot': c, 'evolution': amt, 'element_reference': elref, 'reaction': rxn, 'entries': decomp_entries}) return evolution
def test_getmu_range_stability_phase(self): results = self.analyzer.getmu_range_stability_phase(Composition.from_formula("LiFeO2"),Element("O")) self.assertAlmostEqual(results[Element("O")][1], -4.4501812249999997) self.assertAlmostEqual(results[Element("Fe")][0], -6.5961470999999996) self.assertAlmostEqual(results[Element("Li")][0], -3.6250022625000007)
def __init__(self, struct, find_spacegroup=False): block = CifFile.CifBlock() latt = struct.lattice comp = struct.composition no_oxi_comp = Composition(comp.formula) spacegroup = ("P 1", 1) if find_spacegroup: sf = SymmetryFinder(struct, 0.001) spacegroup = (sf.get_spacegroup_symbol(), sf.get_spacegroup_number()) block["_symmetry_space_group_name_H-M"] = spacegroup[0] for cell_attr in ['a', 'b', 'c']: block["_cell_length_" + cell_attr] = str(getattr(latt, cell_attr)) for cell_attr in ['alpha', 'beta', 'gamma']: block["_cell_angle_" + cell_attr] = float(getattr(latt, cell_attr)) block["_chemical_name_systematic"] = "Generated by pymatgen" block["_symmetry_Int_Tables_number"] = spacegroup[1] block["_chemical_formula_structural"] = str( no_oxi_comp.reduced_formula) block["_chemical_formula_sum"] = str(no_oxi_comp.formula) block["_cell_volume"] = str(latt.volume) reduced_comp = Composition.from_formula(no_oxi_comp.reduced_formula) el = no_oxi_comp.elements[0] amt = comp[el] fu = int(amt / reduced_comp[Element(el.symbol)]) block["_cell_formula_units_Z"] = str(fu) block.AddCifItem( ([["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"]], [[["1"], ["x, y, z"]]])) contains_oxidation = True try: symbol_to_oxinum = { str(el): float(el.oxi_state) for el in comp.elements } except AttributeError: symbol_to_oxinum = {el.symbol: 0 for el in comp.elements} contains_oxidation = False if contains_oxidation: block.AddCifItem( ([["_atom_type_symbol", "_atom_type_oxidation_number"]], [[symbol_to_oxinum.keys(), symbol_to_oxinum.values()]])) atom_site_type_symbol = [] atom_site_symmetry_multiplicity = [] atom_site_fract_x = [] atom_site_fract_y = [] atom_site_fract_z = [] atom_site_attached_hydrogens = [] atom_site_B_iso_or_equiv = [] atom_site_label = [] atom_site_occupancy = [] count = 1 for site in struct: for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(str(sp)) atom_site_symmetry_multiplicity.append("1") atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_attached_hydrogens.append("0") atom_site_B_iso_or_equiv.append(".") atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(str(occu)) count += 1 block["_atom_site_type_symbol"] = atom_site_type_symbol block.AddToLoop("_atom_site_type_symbol", {"_atom_site_label": atom_site_label}) block.AddToLoop("_atom_site_type_symbol", { "_atom_site_symmetry_multiplicity": atom_site_symmetry_multiplicity }) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_x": atom_site_fract_x}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_y": atom_site_fract_y}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_fract_z": atom_site_fract_z}) block.AddToLoop( "_atom_site_type_symbol", {"_atom_site_attached_hydrogens": atom_site_attached_hydrogens}) block.AddToLoop( "_atom_site_type_symbol", {"_atom_site_B_iso_or_equiv": atom_site_B_iso_or_equiv}) block.AddToLoop("_atom_site_type_symbol", {"_atom_site_occupancy": atom_site_occupancy}) self._cf = CifFile.CifFile() # AJ says: CIF Block names cannot be more than 75 characters or you # get an Exception self._cf[comp.reduced_formula[0:74]] = block
def get_element_profile(self, element, comp, comp_tol=1e-5): """ Provides the element evolution data for a composition. For example, can be used to analyze Li conversion voltages by varying uLi and looking at the phases formed. Also can be used to analyze O2 evolution by varying uO2. Args: element: An element. Must be in the phase diagram. comp: A Composition comp_tol: The tolerance to use when calculating decompositions. Phases with amounts less than this tolerance are excluded. Defaults to 1e-5. Returns: Evolution data as a list of dictionaries of the following format: [ {'chempot': -10.487582010000001, 'evolution': -2.0, 'reaction': Reaction Object], ...] """ if element not in self._pd.elements: raise ValueError("get_transition_chempots can only be called with" " elements in the phase diagram.") chempots = self.get_transition_chempots(element) stable_entries = self._pd.stable_entries gccomp = Composition( {el: amt for el, amt in comp.items() if el != element}) elref = self._pd.el_refs[element] elcomp = Composition.from_formula(element.symbol) prev_decomp = [] evolution = [] def are_same_decomp(decomp1, decomp2): for comp in decomp2: if comp not in decomp1: return False return True for c in chempots: gcpd = GrandPotentialPhaseDiagram(stable_entries, {element: c - 0.01}, self._pd.elements) analyzer = PDAnalyzer(gcpd) gcdecomp = analyzer.get_decomposition(gccomp) decomp = [ gcentry.original_entry.composition for gcentry, amt in gcdecomp.items() if amt > comp_tol ] decomp_entries = [ gcentry.original_entry for gcentry, amt in gcdecomp.items() if amt > comp_tol ] if not are_same_decomp(prev_decomp, decomp): if elcomp not in decomp: decomp.insert(0, elcomp) rxn = Reaction([comp], decomp) rxn.normalize_to(comp) prev_decomp = decomp amt = -rxn.coeffs[rxn.all_comp.index(elcomp)] evolution.append({ 'chempot': c, 'evolution': amt, 'element_reference': elref, 'reaction': rxn, 'entries': decomp_entries }) return evolution