Module stoich.balance

Stoich balance method

Expand source code
# SPDX-FileCopyrightText: © 2021 Antonio López Rivera <antonlopezr99@gmail.com>
# SPDX-License-Identifier: GPL-3.0-only

"""
Stoich balance method
---------------------
"""


import sys
import numpy as np

from stoich.helpers import array


class Balance:

    def __init__(self, r, p):
        self.r = r
        self.p = p

        self.molecules   = array([r.components, p.components])

        self.elements    = np.unique(array([molecule.elements.flatten()[0] for molecule in self.molecules]))

        self.n_elements  = self.elements.size
        self.m_molecules = self.molecules.size

        self.results     = self.balance()

        print(self)

    def __repr__(self):
        return self.latex().replace("\longrightarrow", "->").replace("{", "").replace("}", "")

    def latex(self):
        equilibrium = ""

        # Reactants
        for i in range(self.r.components.size):
            equilibrium += f"{self.results[i]}*{self.r.components[i].__repr__()}" \
            + (" + " if self.r.components.size-1 > i else "")

        equilibrium += " \longrightarrow "

        # Products
        print(self.results)
        for j in range(self.p.components.size):
            equilibrium += f"{self.results[self.r.components.size + j]}*{self.p.components[i].__repr__()}" \
            + (" + " if self.r.components.size-1 > j else "")

        return equilibrium

    def balance(self):
        """
        :return: Solution to the stoichiometric equilibrium in the form Ma = b

        If the system is ill determined (there are more intervening molecules than atoms in the reaction),
        the following happens:

            - If there is 1 more molecule than there are atoms, the quantity of the first reactant is
              set to 1. The system is then solved.
            - If there are more than 1 molecules than there are atoms, the system is ill determined
              and cannot be solved.

        """

        # Balance matrix
        M = np.zeros((self.n_elements, self.m_molecules))
        i = 0
        for mixture in [self.r, self.p]:
            for j in range(mixture.components.size):
                # Molecule
                m = mixture.components[j]
                # Element quantities per molecule in the mixture
                e_qts = array([m.extract_quantity(element) for element in self.elements])\
                        * (1 if mixture == self.r else -1)  # Account for difference between reactants and products
                # Set matrix column
                M[:, i] = array([e_qts[i] for i in range(e_qts.size)])
                i += 1

        # Solution of the linear system
        b       = np.zeros(M.shape[1])
        results = np.zeros(M.shape[1])

        if M.shape[1] - M.shape[0] == 1:
            print("The system is ill determined: there is 1 too many degree of freedom.\n"
                  "The quantity of the first reactant will be automatically set to 1 to remove the inconsistency.")
            b          = -M[:, 0]
            M          = np.delete(M, 0, 1)
            results[0] = 1

        elif M.shape[1] - M.shape[0] > 1:
            sys.exit(f"The system is ill determined: there are {M.shape[1]-M.shape[0]} too many degrees of freedom.")

        results[np.where(results == 0)[0][0]:] = np.linalg.solve(M, b)

        return results

Classes

class Balance (r, p)
Expand source code
class Balance:

    def __init__(self, r, p):
        self.r = r
        self.p = p

        self.molecules   = array([r.components, p.components])

        self.elements    = np.unique(array([molecule.elements.flatten()[0] for molecule in self.molecules]))

        self.n_elements  = self.elements.size
        self.m_molecules = self.molecules.size

        self.results     = self.balance()

        print(self)

    def __repr__(self):
        return self.latex().replace("\longrightarrow", "->").replace("{", "").replace("}", "")

    def latex(self):
        equilibrium = ""

        # Reactants
        for i in range(self.r.components.size):
            equilibrium += f"{self.results[i]}*{self.r.components[i].__repr__()}" \
            + (" + " if self.r.components.size-1 > i else "")

        equilibrium += " \longrightarrow "

        # Products
        print(self.results)
        for j in range(self.p.components.size):
            equilibrium += f"{self.results[self.r.components.size + j]}*{self.p.components[i].__repr__()}" \
            + (" + " if self.r.components.size-1 > j else "")

        return equilibrium

    def balance(self):
        """
        :return: Solution to the stoichiometric equilibrium in the form Ma = b

        If the system is ill determined (there are more intervening molecules than atoms in the reaction),
        the following happens:

            - If there is 1 more molecule than there are atoms, the quantity of the first reactant is
              set to 1. The system is then solved.
            - If there are more than 1 molecules than there are atoms, the system is ill determined
              and cannot be solved.

        """

        # Balance matrix
        M = np.zeros((self.n_elements, self.m_molecules))
        i = 0
        for mixture in [self.r, self.p]:
            for j in range(mixture.components.size):
                # Molecule
                m = mixture.components[j]
                # Element quantities per molecule in the mixture
                e_qts = array([m.extract_quantity(element) for element in self.elements])\
                        * (1 if mixture == self.r else -1)  # Account for difference between reactants and products
                # Set matrix column
                M[:, i] = array([e_qts[i] for i in range(e_qts.size)])
                i += 1

        # Solution of the linear system
        b       = np.zeros(M.shape[1])
        results = np.zeros(M.shape[1])

        if M.shape[1] - M.shape[0] == 1:
            print("The system is ill determined: there is 1 too many degree of freedom.\n"
                  "The quantity of the first reactant will be automatically set to 1 to remove the inconsistency.")
            b          = -M[:, 0]
            M          = np.delete(M, 0, 1)
            results[0] = 1

        elif M.shape[1] - M.shape[0] > 1:
            sys.exit(f"The system is ill determined: there are {M.shape[1]-M.shape[0]} too many degrees of freedom.")

        results[np.where(results == 0)[0][0]:] = np.linalg.solve(M, b)

        return results

Methods

def balance(self)

:return: Solution to the stoichiometric equilibrium in the form Ma = b

If the system is ill determined (there are more intervening molecules than atoms in the reaction), the following happens:

- If there is 1 more molecule than there are atoms, the quantity of the first reactant is
  set to 1. The system is then solved.
- If there are more than 1 molecules than there are atoms, the system is ill determined
  and cannot be solved.
Expand source code
def balance(self):
    """
    :return: Solution to the stoichiometric equilibrium in the form Ma = b

    If the system is ill determined (there are more intervening molecules than atoms in the reaction),
    the following happens:

        - If there is 1 more molecule than there are atoms, the quantity of the first reactant is
          set to 1. The system is then solved.
        - If there are more than 1 molecules than there are atoms, the system is ill determined
          and cannot be solved.

    """

    # Balance matrix
    M = np.zeros((self.n_elements, self.m_molecules))
    i = 0
    for mixture in [self.r, self.p]:
        for j in range(mixture.components.size):
            # Molecule
            m = mixture.components[j]
            # Element quantities per molecule in the mixture
            e_qts = array([m.extract_quantity(element) for element in self.elements])\
                    * (1 if mixture == self.r else -1)  # Account for difference between reactants and products
            # Set matrix column
            M[:, i] = array([e_qts[i] for i in range(e_qts.size)])
            i += 1

    # Solution of the linear system
    b       = np.zeros(M.shape[1])
    results = np.zeros(M.shape[1])

    if M.shape[1] - M.shape[0] == 1:
        print("The system is ill determined: there is 1 too many degree of freedom.\n"
              "The quantity of the first reactant will be automatically set to 1 to remove the inconsistency.")
        b          = -M[:, 0]
        M          = np.delete(M, 0, 1)
        results[0] = 1

    elif M.shape[1] - M.shape[0] > 1:
        sys.exit(f"The system is ill determined: there are {M.shape[1]-M.shape[0]} too many degrees of freedom.")

    results[np.where(results == 0)[0][0]:] = np.linalg.solve(M, b)

    return results
def latex(self)
Expand source code
def latex(self):
    equilibrium = ""

    # Reactants
    for i in range(self.r.components.size):
        equilibrium += f"{self.results[i]}*{self.r.components[i].__repr__()}" \
        + (" + " if self.r.components.size-1 > i else "")

    equilibrium += " \longrightarrow "

    # Products
    print(self.results)
    for j in range(self.p.components.size):
        equilibrium += f"{self.results[self.r.components.size + j]}*{self.p.components[i].__repr__()}" \
        + (" + " if self.r.components.size-1 > j else "")

    return equilibrium