def _get_reaction(self, x): """ Generates balanced reaction at mixing ratio x : (1-x) for self.comp1 : self.comp2. Args: x (float): Mixing ratio x of reactants, a float between 0 and 1. Returns: Reaction object. """ mix_comp = self.comp1 * x + self.comp2 * (1 - x) decomp = self.pd.get_decomposition(mix_comp) # Uses original composition for reactants. if np.isclose(x, 0): reactant = [self.c2_original] elif np.isclose(x, 1): reactant = [self.c1_original] else: reactant = list(set([self.c1_original, self.c2_original])) if self.grand: reactant += [Composition(e.symbol) for e, v in self.pd.chempots.items()] product = [Composition(k.name) for k, v in decomp.items()] reaction = Reaction(reactant, product) x_original = self._get_original_composition_ratio(reaction) if np.isclose(x_original, 1): reaction.normalize_to(self.c1_original, x_original) else: reaction.normalize_to(self.c2_original, 1 - x_original) return reaction
def test_normalize_to(self): products = [Composition("Fe"), Composition("O2")] reactants = [Composition("Fe2O3")] rxn = Reaction(reactants, products) rxn.normalize_to(Composition("Fe"), 3) self.assertEqual(str(rxn), "1.500 Fe2O3 -> 3.000 Fe + 2.250 O2", "Wrong reaction obtained!")
def _get_reaction(self, x): """ Generates balanced reaction at mixing ratio x : (1-x) for self.comp1 : self.comp2. Args: x (float): Mixing ratio x of reactants, a float between 0 and 1. Returns: Reaction object. """ mix_comp = self.comp1 * x + self.comp2 * (1-x) decomp = self.pd.get_decomposition(mix_comp) # Uses original composition for reactants. reactant = list(set([self.c1_original, self.c2_original])) if self.grand: reactant += [Composition(e.symbol) for e, v in self.pd.chempots.items()] product = [Composition(k.name) for k, v in decomp.items()] reaction = Reaction(reactant, product) if np.isclose(x, 1): reaction.normalize_to(self.c1_original, 1) else: reaction.normalize_to(self.c2_original, 1) return reaction
def _get_reaction(self, x: float) -> Reaction: """ Generates balanced reaction at mixing ratio x : (1-x) for self.comp1 : self.comp2. Args: x (float): Mixing ratio x of reactants, a float between 0 and 1. Returns: Reaction object. """ mix_comp = self.comp1 * x + self.comp2 * (1 - x) decomp = self.pd.get_decomposition(mix_comp) reactants = self._get_reactants(x) product = [Composition(k.name) for k, v in decomp.items()] reaction = Reaction(reactants, product) x_original = self._get_original_composition_ratio(reaction) if np.isclose(x_original, 1): reaction.normalize_to(self.c1_original, x_original) else: reaction.normalize_to(self.c2_original, 1 - x_original) return reaction
def test_normalize_to(self): products = [Composition("Fe"), Composition("O2")] reactants = [Composition("Fe2O3")] rxn = Reaction(reactants, products) rxn.normalize_to(Composition("Fe"), 3) self.assertEqual(str(rxn), "1.500 Fe2O3 -> 3.000 Fe + 2.250 O2", "Wrong reaction obtained!")
def transform_entries(self, entries, terminal_compositions): """ Method to transform all entries to the composition coordinate in the terminal compositions. If the entry does not fall within the space defined by the terminal compositions, they are excluded. For example, Li3PO4 is mapped into a Li2O:1.5, P2O5:0.5 composition. The terminal compositions are represented by DummySpecies. Args: entries: Sequence of all input entries terminal_compositions: Terminal compositions of phase space. Returns: Sequence of TransformedPDEntries falling within the phase space. """ new_entries = [] if self.normalize_terminals: fractional_comp = [ c.get_fractional_composition() for c in terminal_compositions ] else: fractional_comp = terminal_compositions #Map terminal compositions to unique dummy species. sp_mapping = collections.OrderedDict() for i, comp in enumerate(fractional_comp): sp_mapping[comp] = DummySpecie("X" + chr(102 + i)) for entry in entries: try: rxn = Reaction(fractional_comp, [entry.composition]) rxn.normalize_to(entry.composition) #We only allow reactions that have positive amounts of #reactants. if all([ rxn.get_coeff(comp) <= CompoundPhaseDiagram.amount_tol for comp in fractional_comp ]): newcomp = { sp_mapping[comp]: -rxn.get_coeff(comp) for comp in fractional_comp } newcomp = { k: v for k, v in newcomp.items() if v > CompoundPhaseDiagram.amount_tol } transformed_entry = \ TransformedPDEntry(Composition(newcomp), entry) new_entries.append(transformed_entry) except ReactionError: #If the reaction can't be balanced, the entry does not fall #into the phase space. We ignore them. pass return new_entries, sp_mapping
def transform_entries(self, entries, terminal_compositions): """ Method to transform all entries to the composition coordinate in the terminal compositions. If the entry does not fall within the space defined by the terminal compositions, they are excluded. For example, Li3PO4 is mapped into a Li2O:1.5, P2O5:0.5 composition. The terminal compositions are represented by DummySpecies. Args: entries: Sequence of all input entries terminal_compositions: Terminal compositions of phase space. Returns: Sequence of TransformedPDEntries falling within the phase space. """ new_entries = [] if self.normalize_terminals: fractional_comp = [c.get_fractional_composition() for c in terminal_compositions] else: fractional_comp = terminal_compositions #Map terminal compositions to unique dummy species. sp_mapping = collections.OrderedDict() for i, comp in enumerate(fractional_comp): sp_mapping[comp] = DummySpecie("X" + chr(102 + i)) for entry in entries: try: rxn = Reaction(fractional_comp, [entry.composition]) rxn.normalize_to(entry.composition) #We only allow reactions that have positive amounts of #reactants. if all([rxn.get_coeff(comp) <= CompoundPhaseDiagram.amount_tol for comp in fractional_comp]): newcomp = {sp_mapping[comp]: -rxn.get_coeff(comp) for comp in fractional_comp} newcomp = {k: v for k, v in newcomp.items() if v > CompoundPhaseDiagram.amount_tol} transformed_entry = \ TransformedPDEntry(Composition(newcomp), entry) new_entries.append(transformed_entry) except ReactionError: #If the reaction can't be balanced, the entry does not fall #into the phase space. We ignore them. pass return new_entries, sp_mapping
def get_element_profile(self, element, comp): """ 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 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) decomp = [gcentry.original_entry.composition for gcentry, amt in analyzer.get_decomposition(gccomp).items() if amt > 1e-5] decomp_entries = [gcentry.original_entry for gcentry, amt in analyzer.get_decomposition(gccomp).items() if amt > 1e-5] 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 evolution.append({'chempot':c, 'evolution' :-rxn.coeffs[rxn.all_comp.index(elcomp)], 'element_reference': elref, 'reaction':rxn, 'entries':decomp_entries}) return evolution
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.") gccomp = Composition( {el: amt for el, amt in comp.items() if el != element}) elref = self._pd.el_refs[element] elcomp = Composition(element.symbol) evolution = [] for cc in self.get_critical_compositions(elcomp, gccomp)[1:]: decomp_entries = self.get_decomposition(cc).keys() decomp = [k.composition for k in decomp_entries] rxn = Reaction([comp], decomp + [elcomp]) rxn.normalize_to(comp) c = self.get_composition_chempots(cc + elcomp * 1e-5)[element] 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 _get_reaction(self, x, normalize=False): """ Generates balanced reaction at mixing ratio x : (1-x) for self.comp1 : self.comp2. Args: x (float): Mixing ratio x of reactants, a float between 0 and 1. normalize (bool): Whether or not to normalize the sum of coefficients of reactants to 1. For not normalized case, use original reactant compositions in reaction for clarity. Returns: Reaction object. """ mix_comp = self.comp1 * x + self.comp2 * (1 - x) decomp = self.pd.get_decomposition(mix_comp) if normalize: reactant = list(set([self.c1, self.c2])) else: # Uses original composition for reactants. reactant = list(set([self.c1_original, self.c2_original])) if self.grand: reactant += [ Composition(e.symbol) for e, v in self.pd.chempots.items() ] product = [Composition(k.name) for k, v in decomp.items()] reaction = Reaction(reactant, product) if normalize: x = self._convert(x, self.factor1, self.factor2) if x == 1: reaction.normalize_to(self.c1, x) else: reaction.normalize_to(self.c2, 1 - x) return reaction
def _get_reaction(self, x, normalize=False): """ Generates balanced reaction at mixing ratio x : (1-x) for self.comp1 : self.comp2. Args: x (float): Mixing ratio x of reactants, a float between 0 and 1. normalize (bool): Whether or not to normalize the sum of coefficients of reactants to 1. For not normalized case, use original reactant compositions in reaction for clarity. Returns: Reaction object. """ mix_comp = self.comp1 * x + self.comp2 * (1-x) decomp = self.pd.get_decomposition(mix_comp) if normalize: reactant = list(set([self.c1, self.c2])) else: # Uses original composition for reactants. reactant = list(set([self.c1_original, self.c2_original])) if self.grand: reactant += [Composition(e.symbol) for e, v in self.pd.chempots.items()] product = [Composition(k.name) for k, v in decomp.items()] reaction = Reaction(reactant, product) if normalize: x = self._convert(x, self.factor1, self.factor2) if x == 1: reaction.normalize_to(self.c1, x) else: reaction.normalize_to(self.c2, 1-x) return reaction
def test_normalize_to(self): products = [Composition("Fe"), Composition("O2")] reactants = [Composition("Fe2O3")] rxn = Reaction(reactants, products) rxn.normalize_to(Composition("Fe"), 3) self.assertEqual(str(rxn), "1.5 Fe2O3 -> 3 Fe + 2.25 O2")
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(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 - 1e-5}, 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_normalize_to(self): products = [Composition("Fe"), Composition("O2")] reactants = [Composition("Fe2O3")] rxn = Reaction(reactants, products) rxn.normalize_to(Composition("Fe"), 3) self.assertEqual(str(rxn), "1.5 Fe2O3 -> 3 Fe + 2.25 O2")