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!")
Ejemplo n.º 2
0
    def test_products_reactants(self):
        reactants = [
            Composition("Li3Fe2(PO4)3"),
            Composition("Fe2O3"),
            Composition("O2"),
        ]
        products = [Composition("LiFePO4")]
        energies = {
            Composition("Li3Fe2(PO4)3"): -0.1,
            Composition("Fe2O3"): -0.2,
            Composition("O2"): -0.2,
            Composition("LiFePO4"): -0.5,
        }
        rxn = Reaction(reactants, products)

        self.assertIn(Composition("O2"), rxn.products, "O not in products!")
        self.assertIn(
            Composition("Li3Fe2(PO4)3"), rxn.reactants, "Li3Fe2(PO4)4 not in reactants!"
        )
        self.assertEqual(
            str(rxn), "0.3333 Li3Fe2(PO4)3 + 0.1667 Fe2O3 -> 0.25 O2 + LiFePO4"
        )
        self.assertEqual(
            rxn.normalized_repr, "4 Li3Fe2(PO4)3 + 2 Fe2O3 -> 3 O2 + 12 LiFePO4"
        )
        self.assertAlmostEqual(rxn.calculate_energy(energies), -0.48333333, 5)
Ejemplo n.º 3
0
    def test_get_get_elmt_amt_in_rxt(self):
        rxt1 = Reaction(
            [Composition('Mn'), Composition('O2'), Composition('Li')],
            [Composition('LiMnO2')])
        test1 = np.isclose(self.ir[2]._get_elmt_amt_in_rxt(rxt1), 3)
        self.assertTrue(test1,
                        '_get_get_elmt_amt_in_rxt: '
                        'gpd elements amounts gets error!')

        rxt2 = rxt1
        rxt2.normalize_to(Composition('Li'), 0.5)
        test2 = np.isclose(self.ir[2]._get_elmt_amt_in_rxt(rxt2), 1.5)
        self.assertTrue(test2,
                        '_get_get_elmt_amt_in_rxt: '
                        'gpd elements amounts gets error!')

        rxt3 = Reaction([Composition('O2'), Composition('Li')],
                        [Composition('Li2O')])
        # Li is not counted
        test3 = np.isclose(self.ir[2]._get_elmt_amt_in_rxt(rxt3), 1)
        self.assertTrue(test3,
                        '_get_get_elmt_amt_in_rxt: '
                        'gpd elements amounts gets error!')

        # Li is counted
        test4 = np.isclose(self.ir[6]._get_elmt_amt_in_rxt(rxt3), 3)
        self.assertTrue(test4,
                        '_get_get_elmt_amt_in_rxt: '
                        'pd elements amounts gets error!')
Ejemplo n.º 4
0
    def test_underdetermined(self):
        reactants = [Composition("Fe"), Composition("O2")]
        products = [Composition("Fe"), Composition("O2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "Fe + O2 -> Fe + O2")

        reactants = [
            Composition("Fe"),
            Composition("O2"),
            Composition("Na"),
            Composition("Li"),
            Composition("Cl"),
        ]
        products = [Composition("FeO2"), Composition("NaCl"), Composition("Li2Cl2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(
            str(rxn), "Fe + O2 + Na + 2 Li + 1.5 Cl2 -> FeO2 + NaCl + 2 LiCl"
        )

        reactants = [
            Composition("Fe"),
            Composition("Na"),
            Composition("Li2O"),
            Composition("Cl"),
        ]
        products = [
            Composition("LiCl"),
            Composition("Na2O"),
            Composition("Xe"),
            Composition("FeCl"),
            Composition("Mn"),
        ]
        rxn = Reaction(reactants, products)
        # this cant normalize to 1 LiCl + 1 Na2O (not enough O), so chooses LiCl and FeCl
        self.assertEqual(str(rxn), "Fe + Na + 0.5 Li2O + Cl2 -> LiCl + 0.5 Na2O + FeCl")
Ejemplo n.º 5
0
    def process_multientry(entry_list, prod_comp):
        """
        Static method for finding a multientry based on
        a list of entries and a product composition.
        Essentially checks to see if a valid aqueous
        reaction exists between the entries and the 
        product composition and returns a MultiEntry
        with weights according to the coefficients if so.

        Args:
            entry_list ([Entry]): list of entries from which to
                create a MultiEntry
            comp (Composition): composition constraint for setting
                weights of MultiEntry
        """
        dummy_oh = [Composition("H"), Composition("O")]
        try:
            # Get balanced reaction coeffs, ensuring all < 0 or conc thresh
            # Note that we get reduced compositions for solids and non-reduced 
            # compositions for ions because ions aren't normalized due to
            # their charge state.
            entry_comps = [e.composition if e.phase_type=='Ion'
                           else e.composition.reduced_composition 
                           for e in entry_list]
            rxn = Reaction(entry_comps + dummy_oh, [prod_comp])
            thresh = np.array([pe.conc if pe.phase_type == "Ion"
                               else 1e-3 for pe in entry_list])
            coeffs = -np.array([rxn.get_coeff(comp) for comp in entry_comps])
            if (coeffs > thresh).all():
                weights = coeffs / coeffs[0]
                return MultiEntry(entry_list, weights=weights.tolist())
            else:
                return None
        except ReactionError:
            return None
 def test_to_from_dict(self):
     reactants = [Composition("Fe"), Composition("O2")]
     products = [Composition("Fe2O3")]
     rxn = Reaction(reactants, products)
     d = rxn.as_dict()
     rxn = Reaction.from_dict(d)
     self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3")
Ejemplo n.º 7
0
    def process_multientry(entry_list, prod_comp):
        """
        Static method for finding a multientry based on
        a list of entries and a product composition.
        Essentially checks to see if a valid aqueous
        reaction exists between the entries and the 
        product composition and returns a MultiEntry
        with weights according to the coefficients if so.

        Args:
            entry_list ([Entry]): list of entries from which to
                create a MultiEntry
            comp (Composition): composition constraint for setting
                weights of MultiEntry
        """
        dummy_oh = [Composition("H"), Composition("O")]
        try:
            # Get balanced reaction coeffs, ensuring all < 0 or conc thresh
            # Note that we get reduced compositions for solids and non-reduced 
            # compositions for ions because ions aren't normalized due to
            # their charge state.
            entry_comps = [e.composition if e.phase_type=='Ion'
                           else e.composition.reduced_composition 
                           for e in entry_list]
            rxn = Reaction(entry_comps + dummy_oh, [prod_comp])
            thresh = np.array([pe.conc if pe.phase_type == "Ion"
                               else 1e-3 for pe in entry_list])
            coeffs = -np.array([rxn.get_coeff(comp) for comp in entry_comps])
            if (coeffs > thresh).all():
                weights = coeffs / coeffs[0]
                return MultiEntry(entry_list, weights=weights.tolist())
            else:
                return None
        except ReactionError:
            return None
Ejemplo n.º 8
0
 def test_to_from_dict(self):
     reactants = [Composition("Fe"), Composition("O2")]
     products = [Composition("Fe2O3")]
     rxn = Reaction(reactants, products)
     d = rxn.as_dict()
     rxn = Reaction.from_dict(d)
     self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3")
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
 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!")
Ejemplo n.º 11
0
    def process_multientry(entry_list, prod_comp, coeff_threshold=1e-4):
        """
        Static method for finding a multientry based on
        a list of entries and a product composition.
        Essentially checks to see if a valid aqueous
        reaction exists between the entries and the
        product composition and returns a MultiEntry
        with weights according to the coefficients if so.

        Args:
            entry_list ([Entry]): list of entries from which to
                create a MultiEntry
            prod_comp (Composition): composition constraint for setting
                weights of MultiEntry
            coeff_threshold (float): threshold of stoichiometric
                coefficients to filter, if weights are lower than
                this value, the entry is not returned
        """
        dummy_oh = [Composition("H"), Composition("O")]
        try:
            # Get balanced reaction coeffs, ensuring all < 0 or conc thresh
            # Note that we get reduced compositions for solids and non-reduced
            # compositions for ions because ions aren't normalized due to
            # their charge state.
            entry_comps = [e.composition for e in entry_list]
            rxn = Reaction(entry_comps + dummy_oh, [prod_comp])
            coeffs = -np.array([rxn.get_coeff(comp) for comp in entry_comps])
            # Return None if reaction coeff threshold is not met
            # TODO: this filtration step might be put somewhere else
            if (coeffs > coeff_threshold).all():
                return MultiEntry(entry_list, weights=coeffs.tolist())
            else:
                return None
        except ReactionError:
            return None
Ejemplo n.º 12
0
    def test_get_get_elmt_amt_in_rxt(self):
        rxt1 = Reaction(
            [Composition("Mn"),
             Composition("O2"),
             Composition("Li")],
            [Composition("LiMnO2")],
        )
        test1 = np.isclose(self.ir[2]._get_elmt_amt_in_rxn(rxt1), 3)
        self.assertTrue(
            test1,
            "_get_get_elmt_amt_in_rxt: gpd elements amounts gets error!")

        rxt2 = rxt1
        rxt2.normalize_to(Composition("Li"), 0.5)
        test2 = np.isclose(self.ir[2]._get_elmt_amt_in_rxn(rxt2), 1.5)
        self.assertTrue(
            test2,
            "_get_get_elmt_amt_in_rxt: gpd elements amounts gets error!")

        rxt3 = Reaction(
            [Composition("O2"), Composition("Li")], [Composition("Li2O")])
        # Li is not counted
        test3 = np.isclose(self.ir[2]._get_elmt_amt_in_rxn(rxt3), 1)
        self.assertTrue(
            test3,
            "_get_get_elmt_amt_in_rxt: gpd elements amounts gets error!")

        # Li is counted
        test4 = np.isclose(self.ir[6]._get_elmt_amt_in_rxn(rxt3), 3)
        self.assertTrue(
            test4, "_get_get_elmt_amt_in_rxt: pd elements amounts gets error!")
Ejemplo n.º 13
0
 def test_as_entry(self):
     reactants = [Composition("MgO"), Composition("Al2O3")]
     products = [Composition("MgAl2O4")]
     energies = {Composition("MgO"): -0.1, Composition("Al2O3"): -0.2, Composition("MgAl2O4"): -0.5}
     rxn = Reaction(reactants, products)
     entry = rxn.as_entry(energies)
     self.assertEqual(entry.name, "1.000 MgO + 1.000 Al2O3 -> 1.000 MgAl2O4")
     self.assertAlmostEquals(entry.energy, -0.2, 5)
Ejemplo n.º 14
0
 def test_equals(self):
     reactants = [Composition("Fe"), Composition("O2")]
     products = [Composition("Fe2O3")]
     rxn = Reaction(reactants, products)
     reactants = [Composition("O2"), Composition("Fe")]
     products = [Composition("Fe2O3")]
     rxn2 = Reaction(reactants, products)
     self.assertTrue(rxn == rxn2)
Ejemplo n.º 15
0
 def test_calculate_energy(self):
     reactants = [Composition("MgO"), Composition("Al2O3")]
     products = [Composition("MgAl2O4")]
     energies = {Composition("MgO"): -0.1, Composition("Al2O3"): -0.2, Composition("MgAl2O4"): -0.5}
     rxn = Reaction(reactants, products)
     self.assertEqual(str(rxn), "1.000 MgO + 1.000 Al2O3 -> 1.000 MgAl2O4")
     self.assertEqual(rxn.normalized_repr, "MgO + Al2O3 -> MgAl2O4")
     self.assertAlmostEquals(rxn.calculate_energy(energies), -0.2, 5)
Ejemplo n.º 16
0
    def test_scientific_notation(self):
        products = [Composition("FePO3.9999"), Composition("O2")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 -> Fe1P1O3.9999 + 5e-05 O2")
        self.assertEqual(rxn, Reaction.from_string(str(rxn)))

        rxn2 = Reaction.from_string("FePO4 + 20 CO -> 1e1 O2 + Fe1P1O4 + 20 C")
        self.assertEqual(str(rxn2), "20 CO -> 10 O2 + 20 C")
Ejemplo n.º 17
0
    def test_scientific_notation(self):
        products = [Composition("FePO3.9999"), Composition("O2")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 -> Fe1P1O3.9999 + 5e-05 O2")
        self.assertEqual(rxn, Reaction.from_string(str(rxn)))

        rxn2 = Reaction.from_string("FePO4 + 20 CO -> 1e1 O2 + Fe1P1O4 + 20 C")
        self.assertEqual(str(rxn2), "20 CO -> 10 O2 + 20 C")
Ejemplo n.º 18
0
    def test_underdetermined_reactants(self):
        reactants = [Composition("Li"), Composition("Cl"), Composition("Cl")]
        products = [Composition("LiCl")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "Li + 0.25 Cl2 + 0.25 Cl2 -> LiCl")

        reactants = [Composition("LiMnCl3"), Composition("LiCl"), Composition("MnCl2")]
        products = [Composition("Li2MnCl4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "LiMnCl3 + 3 LiCl + MnCl2 -> 2 Li2MnCl4")
Ejemplo n.º 19
0
    def test_products_reactants(self):
        reactants = [Composition.from_formula("Li3Fe2(PO4)3"), Composition.from_formula("Fe2O3"), Composition.from_formula("O2")]
        products = [Composition.from_formula("LiFePO4")]
        energies = {Composition.from_formula("Li3Fe2(PO4)3"):-0.1, Composition.from_formula("Fe2O3"):-0.2, Composition.from_formula("O2"):-0.2, Composition.from_formula("LiFePO4"):-0.5}
        rxn = Reaction(reactants, products)

        self.assertIn(Composition.from_formula("O2"), rxn.products, "O not in products!")
        self.assertIn(Composition.from_formula("Li3Fe2(PO4)3"), rxn.reactants, "Li3Fe2(PO4)4 not in reactants!")
        self.assertEquals(str(rxn), "0.333 Li3Fe2(PO4)3 + 0.167 Fe2O3 -> 0.250 O2 + 1.000 LiFePO4", "Wrong reaction obtained!")
        self.assertEquals(rxn.normalized_repr, "4 Li3Fe2(PO4)3 + 2 Fe2O3 -> 3 O2 + 12 LiFePO4", "Wrong normalized reaction obtained!")
        self.assertAlmostEquals(rxn.calculate_energy(energies), -0.48333333, 5)
Ejemplo n.º 20
0
 def test_calculate_energy(self):
     reactants = [Composition("MgO"), Composition("Al2O3")]
     products = [Composition("MgAl2O4")]
     energies = {
         Composition("MgO"): -0.1,
         Composition("Al2O3"): -0.2,
         Composition("MgAl2O4"): -0.5
     }
     rxn = Reaction(reactants, products)
     self.assertEqual(str(rxn), "MgO + Al2O3 -> MgAl2O4")
     self.assertEqual(rxn.normalized_repr, "MgO + Al2O3 -> MgAl2O4")
     self.assertAlmostEqual(rxn.calculate_energy(energies), -0.2, 5)
Ejemplo n.º 21
0
    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
Ejemplo n.º 22
0
 def test_singular_case(self):
     rxn = Reaction(
         [Composition('XeMn'), Composition("Li")],
         [Composition("S"),
          Composition("LiS2"),
          Composition('FeCl')])
     self.assertEqual(str(rxn), '0.5 LiS2 -> 0.5 Li + S')
Ejemplo n.º 23
0
 def test_singular_case(self):
     rxn = Reaction(
         [Composition("XeMn"), Composition("Li")],
         [Composition("S"),
          Composition("LiS2"),
          Composition("FeCl")],
     )
     self.assertEqual(str(rxn), "Li + 2 S -> LiS2")
Ejemplo n.º 24
0
    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
Ejemplo n.º 25
0
    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
Ejemplo n.º 26
0
    def test_as_entry(self):
        reactants = [Composition("MgO"), Composition("Al2O3")]
        products = [Composition("MgAl2O4")]
        energies = {Composition("MgO"): -0.1, Composition("Al2O3"): -0.2,
                    Composition("MgAl2O4"): -0.5}
        rxn = Reaction(reactants, products)
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.name,
                         "MgO + Al2O3 -> MgAl2O4")
        self.assertAlmostEqual(entry.energy, -0.2, 5)

        products = [Composition("Fe"), Composition("O2")]
        reactants = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        energies = {Composition("Fe"): 0, Composition("O2"): 0,
                    Composition("Fe2O3"): 0.5}
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.composition, Composition("Fe1.0 O1.5"))
        self.assertAlmostEqual(entry.energy, -0.25, 5)
    def test_as_entry(self):
        reactants = [Composition("MgO"), Composition("Al2O3")]
        products = [Composition("MgAl2O4")]
        energies = {Composition("MgO"): -0.1, Composition("Al2O3"): -0.2,
                    Composition("MgAl2O4"): -0.5}
        rxn = Reaction(reactants, products)
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.name,
                         "1.000 MgO + 1.000 Al2O3 -> 1.000 MgAl2O4")
        self.assertAlmostEqual(entry.energy, -0.2, 5)

        products = [Composition("Fe"), Composition("O2")]
        reactants = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        energies = {Composition("Fe"): 0, Composition("O2"): 0,
                    Composition("Fe2O3"): 0.5}
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.composition.formula, "Fe1.33333333 O2")
        self.assertAlmostEqual(entry.energy, -0.333333, 5)
Ejemplo n.º 28
0
    def test_as_entry(self):
        reactants = [Composition("MgO"), Composition("Al2O3")]
        products = [Composition("MgAl2O4")]
        energies = {
            Composition("MgO"): -0.1,
            Composition("Al2O3"): -0.2,
            Composition("MgAl2O4"): -0.5
        }
        rxn = Reaction(reactants, products)
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.name,
                         "1.000 MgO + 1.000 Al2O3 -> 1.000 MgAl2O4")
        self.assertAlmostEqual(entry.energy, -0.2, 5)

        products = [Composition("Fe"), Composition("O2")]
        reactants = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        energies = {
            Composition("Fe"): 0,
            Composition("O2"): 0,
            Composition("Fe2O3"): 0.5
        }
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.composition.formula, "Fe1.33333333 O2")
        self.assertAlmostEqual(entry.energy, -0.333333, 5)
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
    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
Ejemplo n.º 31
0
    def _get_elmt_amt_in_rxn(self, rxn: Reaction) -> int:
        """
        Computes total number of atoms in a reaction formula for elements
        not in external reservoir. This method is used in the calculation
        of reaction energy per mol of reaction formula.

        Args:
            rxn: a Reaction object.

        Returns:
            Total number of atoms for non_reservoir elements.
        """
        return sum(rxn.get_el_amount(e) for e in self.pd.elements)
Ejemplo n.º 32
0
    def __init__(self, equation, mass_production):
        """[summary]

        Args:
            equation ([type]): [description]
            mass_production ([type]): [description]
        """
        self.equation = equation
        self.mass_production = mass_production

        # fazendo a separação da reagente do produto
        self.separate_rp = self.equation.split('->')

        # separando os reagente para passar para Classe Composition
        self.reagent = self.separate_rp[0].split('+')

        # separando os produtos para passar para Classe Composition
        self.producer = self.separate_rp[1].split('+')

        reactants = [Composition(i) for i in self.reagent]
        producers = [Composition(i) for i in self.producer]

        self.reaction = Reaction(reactants, producers)
        print(self.reaction)

        self.mol_reactant = []
        self.mol_production = []
        self.mass_reactant = []
        self.massa_production = []

        tam = int(len(producers))

        j = 0

        for i in range(len(self.reaction.coeffs)):
            if i < tam:
                mol = (self.reaction.coeffs[i] / self.reaction.coeffs[-tam]
                       ) * (mass_production /
                            Composition(self.producer[-tam]).weight)
                self.mol_reactant.append(mol)
                mass = mol * Composition(self.reagent[i]).weight
                self.mass_reactant.append(mass)
            else:
                mol_ = (self.reaction.coeffs[i] / self.reaction.coeffs[-tam]
                        ) * (mass_production /
                             Composition(self.producer[-tam]).weight)
                self.mol_production.append(mol_)
                mass_ = mol_ * Composition(self.producer[j]).weight
                j = j + 1
                self.massa_production.append(mass_)
Ejemplo n.º 33
0
    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
Ejemplo n.º 34
0
    def process_multientry(entry_list, prod_comp, coeff_threshold=1e-4):
        """
        Static method for finding a multientry based on
        a list of entries and a product composition.
        Essentially checks to see if a valid aqueous
        reaction exists between the entries and the
        product composition and returns a MultiEntry
        with weights according to the coefficients if so.

        Args:
            entry_list ([Entry]): list of entries from which to
                create a MultiEntry
            prod_comp (Composition): composition constraint for setting
                weights of MultiEntry
            coeff_threshold (float): threshold of stoichiometric
                coefficients to filter, if weights are lower than
                this value, the entry is not returned
        """
        dummy_oh = [Composition("H"), Composition("O")]
        try:
            # Get balanced reaction coeffs, ensuring all < 0 or conc thresh
            # Note that we get reduced compositions for solids and non-reduced
            # compositions for ions because ions aren't normalized due to
            # their charge state.
            entry_comps = [e.composition for e in entry_list]
            rxn = Reaction(entry_comps + dummy_oh, [prod_comp])
            react_coeffs = [-rxn.get_coeff(comp) for comp in entry_comps]
            all_coeffs = react_coeffs + [rxn.get_coeff(prod_comp)]

            # Check if reaction coeff threshold met for pourbaix compounds
            # All reactant/product coefficients must be positive nonzero
            if all([coeff > coeff_threshold for coeff in all_coeffs]):
                return MultiEntry(entry_list, weights=react_coeffs)

            return None
        except ReactionError:
            return None
Ejemplo n.º 35
0
    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
Ejemplo n.º 36
0
    def generate_reaction(cls, rct_mols, pro_mols):
        """
        Generate a pymatgen Reaction object, given lists of reactant and product Molecule objects.

        Args:
            rct_mols (list): list of Molecule objects representing reactants
            pro_mols (list): list of Molecule objects representing products

        Note: if reaction cannot be balanced, this method will raise a ReactionError

        Returns:
            reaction (Reaction): pymatgen Reaction object representing the reaction between the
                reactants and products.
        """

        rct_comps = [r.composition for r in rct_mols]
        pro_comps = [p.composition for p in pro_mols]

        return Reaction(rct_comps, pro_comps)
Ejemplo n.º 37
0
    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
Ejemplo n.º 38
0
    def test_as_entry(self):
        reactants = [Composition("MgO"), Composition("Al2O3")]
        products = [Composition("MgAl2O4")]
        energies = {
            Composition("MgO"): -0.1,
            Composition("Al2O3"): -0.2,
            Composition("MgAl2O4"): -0.5
        }
        rxn = Reaction(reactants, products)
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.name, "MgO + Al2O3 -> MgAl2O4")
        self.assertAlmostEqual(entry.energy, -0.2, 5)

        products = [Composition("Fe"), Composition("O2")]
        reactants = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        energies = {
            Composition("Fe"): 0,
            Composition("O2"): 0,
            Composition("Fe2O3"): 0.5
        }
        entry = rxn.as_entry(energies)
        self.assertEqual(entry.composition, Composition("Fe1.0 O1.5"))
        self.assertAlmostEqual(entry.energy, -0.25, 5)
Ejemplo n.º 39
0
    def test_rank(self):
        reactants = [Composition("La2Zr2O7"), Composition("LiCoO2")]
        products = [
            Composition("La2O3"),
            Composition("Co2O3"),
            Composition("Li2ZrO3"),
            Composition("Li2O")
        ]

        self.assertEqual(
            str(Reaction(reactants, products)),
            "La2Zr2O7 + 2 LiCoO2 + Li2O -> "
            "La2O3 + Co2O3 + 2 Li2ZrO3")

        reactants = [
            Composition("La2O3"),
            Composition("Co2O3"),
            Composition("Li2ZrO3")
        ]
        products = [
            Composition("Li2O"),
            Composition("La2Zr2O7"),
            Composition("Li3CoO3")
        ]
        self.assertEqual(
            str(Reaction(reactants, products)),
            "La2O3 + 0.3333 Co2O3 + 2 Li2ZrO3 -> "
            "Li2O + La2Zr2O7 + 0.6667 Li3CoO3")

        reactants = [
            Composition("La2O3"),
            Composition("Co2O3"),
            Composition("Li2ZrO3")
        ]
        products = [
            Composition("Xe"),
            Composition("Li2O"),
            Composition("La2Zr2O7"),
            Composition("Li3CoO3")
        ]
        self.assertEqual(
            str(Reaction(reactants, products)),
            "La2O3 + 0.3333 Co2O3 + 2 Li2ZrO3 -> "
            "Li2O + La2Zr2O7 + 0.6667 Li3CoO3")

        reactants = [
            Composition("La2O3"),
            Composition("Co2O3"),
            Composition("Li2ZrO3")
        ]
        products = [
            Composition("Xe"),
            Composition("Li2O"),
            Composition("La2Zr2O7"),
            Composition("Li3CoO3"),
            Composition("XeNe")
        ]
        self.assertEqual(
            str(Reaction(reactants, products)),
            "La2O3 + 0.3333 Co2O3 + 2 Li2ZrO3 -> "
            "Li2O + La2Zr2O7 + 0.6667 Li3CoO3")

        reactants = [Composition("LiCoO2")]
        products = [
            Composition("La2O3"),
            Composition("Co2O3"),
            Composition("Li2O1"),
            Composition("Li1F1"),
            Composition("Co1F3")
        ]
        self.assertEqual(str(Reaction(reactants, products)),
                         "2 LiCoO2 -> Co2O3 + Li2O")

        # this test can fail because of numerical rank calculation issues
        reactants = [Composition("LiCoO2"), Composition("Li2O1")]
        products = [Composition("ZrF4"), Composition("Co2O3")]
        self.assertEqual(str(Reaction(reactants, products)),
                         '2 LiCoO2 -> Li2O + Co2O3')
Ejemplo n.º 40
0
    def test_init(self):
        reactants = [Composition("Fe"),
                     Composition("O2")]
        products = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2 Fe + 1.5 O2 -> Fe2O3")
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3")

        d = rxn.as_dict()
        rxn = Reaction.from_dict(d)
        repr, factor = rxn.normalized_repr_and_factor()
        self.assertEqual(repr, "4 Fe + 3 O2 -> 2 Fe2O3")
        self.assertAlmostEqual(factor, 2)

        reactants = [Composition("FePO4"), Composition('Mn')]
        products = [Composition("FePO4"), Composition('Xe')]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 -> FePO4")

        products = [Composition("Ti2 O4"), Composition("O1")]
        reactants = [Composition("Ti1 O2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2 TiO2 -> 2 TiO2")

        reactants = [Composition("FePO4"), Composition("Li")]
        products = [Composition("LiFePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 + Li -> LiFePO4")

        reactants = [Composition("MgO")]
        products = [Composition("MgO")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "MgO -> MgO")

        reactants = [Composition("Mg")]
        products = [Composition("Mg")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "Mg -> Mg")

        reactants = [Composition("FePO4"), Composition("LiPO3")]
        products = [Composition("LiFeP2O7")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "FePO4 + LiPO3 -> LiFeP2O7")

        reactants = [Composition("Na"), Composition("K2O")]
        products = [Composition("Na2O"), Composition("K")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "2 Na + K2O -> Na2O + 2 K")

        # Test for an old bug which has a problem when excess product is
        # defined.
        products = [Composition("FePO4"), Composition("O")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)

        self.assertEqual(str(rxn), "FePO4 -> FePO4")

        products = list(map(Composition, ['LiCrO2', 'La8Ti8O12', 'O2']))
        reactants = [Composition('LiLa3Ti3CrO12')]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "LiLa3Ti3CrO12 -> LiCrO2 + 1.5 La2Ti2O3 + 2.75 O2")
Ejemplo n.º 41
0
    def test_init(self):
        reactants = [Composition("Fe"), Composition("O2")]
        products = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2 Fe + 1.5 O2 -> Fe2O3")
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3")

        d = rxn.as_dict()
        rxn = Reaction.from_dict(d)
        repr, factor = rxn.normalized_repr_and_factor()
        self.assertEqual(repr, "4 Fe + 3 O2 -> 2 Fe2O3")
        self.assertAlmostEqual(factor, 2)

        reactants = [Composition("FePO4"), Composition('Mn')]
        products = [Composition("FePO4"), Composition('Xe')]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 -> FePO4")

        products = [Composition("Ti2 O4"), Composition("O1")]
        reactants = [Composition("Ti1 O2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2 TiO2 -> 2 TiO2")

        reactants = [Composition("FePO4"), Composition("Li")]
        products = [Composition("LiFePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 + Li -> LiFePO4")

        reactants = [Composition("MgO")]
        products = [Composition("MgO")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "MgO -> MgO")

        reactants = [Composition("Mg")]
        products = [Composition("Mg")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "Mg -> Mg")

        reactants = [Composition("FePO4"), Composition("LiPO3")]
        products = [Composition("LiFeP2O7")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "FePO4 + LiPO3 -> LiFeP2O7")

        reactants = [Composition("Na"), Composition("K2O")]
        products = [Composition("Na2O"), Composition("K")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2 Na + K2O -> Na2O + 2 K")

        # Test for an old bug which has a problem when excess product is
        # defined.
        products = [Composition("FePO4"), Composition("O")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)

        self.assertEqual(str(rxn), "FePO4 -> FePO4")

        products = list(map(Composition, ['LiCrO2', 'La8Ti8O12', 'O2']))
        reactants = [Composition('LiLa3Ti3CrO12')]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "LiLa3Ti3CrO12 -> LiCrO2 + 1.5 La2Ti2O3 + 2.75 O2")
Ejemplo n.º 42
0
 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")
Ejemplo n.º 43
0
 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")
Ejemplo n.º 44
0
    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
Ejemplo n.º 45
0
    def test_init(self):
        reactants = [Composition("Fe"), Composition("O2")]
        products = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2.000 Fe + 1.500 O2 -> 1.000 Fe2O3",
                         "Wrong reaction obtained!")
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3",
                         "Wrong normalized reaction obtained!")

        d = rxn.as_dict()
        rxn = Reaction.from_dict(d)
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3",
                         "Wrong normalized reaction obtained!")

        reactants = [
            Composition("Fe"),
            Composition("O"),
            Composition("Mn"),
            Composition("P")
        ]
        products = [Composition("FeP"), Composition("MnO")]
        rxn = Reaction(reactants, products)
        self.assertEqual(
            str(rxn),
            "1.000 Fe + 0.500 O2 + 1.000 Mn + 1.000 P -> 1.000 FeP + 1.000 MnO",
            "Wrong reaction obtained!")
        self.assertEqual(rxn.normalized_repr,
                         "2 Fe + O2 + 2 Mn + 2 P -> 2 FeP + 2 MnO",
                         "Wrong normalized reaction obtained!")
        reactants = [Composition("FePO4")]
        products = [Composition("FePO4")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 FePO4 -> 1.000 FePO4",
                         "Wrong reaction obtained!")

        products = [Composition("Ti2 O4"), Composition("O1")]
        reactants = [Composition("Ti1 O2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2.000 TiO2 -> 2.000 TiO2",
                         "Wrong reaction obtained!")

        reactants = [Composition("FePO4"), Composition("Li")]
        products = [Composition("LiFePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 FePO4 + 1.000 Li -> 1.000 LiFePO4",
                         "Wrong reaction obtained!")

        reactants = [Composition("MgO")]
        products = [Composition("MgO")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 MgO -> 1.000 MgO",
                         "Wrong reaction obtained!")

        reactants = [Composition("Mg")]
        products = [Composition("Mg")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 Mg -> 1.000 Mg",
                         "Wrong reaction obtained!")

        reactants = [Composition("FePO4"), Composition("LiPO3")]
        products = [Composition("LiFeP2O7")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 FePO4 + 1.000 LiPO3 -> 1.000 LiFeP2O7",
                         "Wrong reaction obtained!")

        reactants = [Composition("Na"), Composition("K2O")]
        products = [Composition("Na2O"), Composition("K")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 Na + 0.500 K2O -> 0.500 Na2O + 1.000 K",
                         "Wrong reaction obtained!")

        # Test for an old bug which has a problem when excess product is
        # defined.
        products = [Composition("FePO4"), Composition("O")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)

        self.assertEqual(str(rxn), "1.000 FePO4 -> 1.000 FePO4",
                         "Wrong reaction obtained!")

        products = list(map(Composition, ['La8Ti8O12', 'O2', 'LiCrO2']))
        reactants = [Composition('LiLa3Ti3CrO12')]
        rxn = Reaction(reactants, products)
        self.assertEqual(
            str(rxn),
            "1.000 LiLa3Ti3CrO12 -> 1.500 La2Ti2O3 + 2.750 O2 + 1.000 LiCrO2",
            "Wrong reaction obtained!")
    def test_init(self):
        reactants = [Composition("Fe"),
                     Composition("O2")]
        products = [Composition("Fe2O3")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2.000 Fe + 1.500 O2 -> 1.000 Fe2O3",
                         "Wrong reaction obtained!")
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3",
                         "Wrong normalized reaction obtained!")

        d = rxn.as_dict()
        rxn = Reaction.from_dict(d)
        self.assertEqual(rxn.normalized_repr, "4 Fe + 3 O2 -> 2 Fe2O3",
                         "Wrong normalized reaction obtained!")

        reactants = [Composition("Fe"), Composition("O"), Composition("Mn"),
                     Composition("P")]
        products = [Composition("FeP"), Composition("MnO")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 Fe + 0.500 O2 + 1.000 Mn + 1.000 P -> 1.000 FeP + 1.000 MnO",
                         "Wrong reaction obtained!")
        self.assertEqual(rxn.normalized_repr,
                         "2 Fe + O2 + 2 Mn + 2 P -> 2 FeP + 2 MnO",
                         "Wrong normalized reaction obtained!")
        reactants = [Composition("FePO4")]
        products = [Composition("FePO4")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 FePO4 -> 1.000 FePO4",
                         "Wrong reaction obtained!")

        products = [Composition("Ti2 O4"), Composition("O1")]
        reactants = [Composition("Ti1 O2")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "2.000 TiO2 -> 2.000 TiO2",
                         "Wrong reaction obtained!")

        reactants = [Composition("FePO4"), Composition("Li")]
        products = [Composition("LiFePO4")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 FePO4 + 1.000 Li -> 1.000 LiFePO4",
                         "Wrong reaction obtained!")

        reactants = [Composition("MgO")]
        products = [Composition("MgO")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 MgO -> 1.000 MgO",
                         "Wrong reaction obtained!")

        reactants = [Composition("Mg")]
        products = [Composition("Mg")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn), "1.000 Mg -> 1.000 Mg",
                         "Wrong reaction obtained!")

        reactants = [Composition("FePO4"), Composition("LiPO3")]
        products = [Composition("LiFeP2O7")]

        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 FePO4 + 1.000 LiPO3 -> 1.000 LiFeP2O7",
                         "Wrong reaction obtained!")

        reactants = [Composition("Na"), Composition("K2O")]
        products = [Composition("Na2O"), Composition("K")]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 Na + 0.500 K2O -> 0.500 Na2O + 1.000 K",
                         "Wrong reaction obtained!")

        # Test for an old bug which has a problem when excess product is
        # defined.
        products = [Composition("FePO4"), Composition("O")]
        reactants = [Composition("FePO4")]
        rxn = Reaction(reactants, products)

        self.assertEqual(str(rxn), "1.000 FePO4 -> 1.000 FePO4",
                         "Wrong reaction obtained!")

        products = list(map(Composition, ['La8Ti8O12', 'O2', 'LiCrO2']))
        reactants = [Composition('LiLa3Ti3CrO12')]
        rxn = Reaction(reactants, products)
        self.assertEqual(str(rxn),
                         "1.000 LiLa3Ti3CrO12 -> 1.500 La2Ti2O3 + 2.750 O2 + 1.000 LiCrO2",
                         "Wrong reaction obtained!")