Source code for chemics.chemical_equation

"""
Class for chemical equation properties.
"""

import chemics as cm
import pandas as pd
import re
from collections import Counter


[docs] class ChemicalEquation: """ Properties of the reactants and products in a given chemical equation. Parameters ---------- eq : str Chemical equation such as A + B -> C + D names : dict, optional Names of unique species and their corresponding chemical formula. Attributes ---------- eq : str Chemical equation such as A + B -> C + D names : dict, optional Names of unique species and their corresponding chemical formula. rct_properties : dataframe Number of moles, chemical species, molecular weight, mass, mole fraction, and mass fraction for each reactant. rct_elements : dict Total number of atomic elements on reactant side of the equation. rct_moles : float Total number of moles on reactant side of the equation. rct_mass : float Total mass of the reactants. prod_properties : dataframe Number of moles, chemical species, molecular weight, mass, mole fraction, and mass fraction for each product. prod_elements : dict Total number of atomic elements on product side of the equation. prod_moles : float Total number of moles on product side of the equation. prod_mass : float Total mass of the products. Examples -------- >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.is_balanced() True >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.rct_properties HCl Na moles 2.0 2.0 species HCl Na molwt 36.458 22.99 mass 72.916 45.98 molfrac 0.5 0.5 massfrac 0.613275 0.386725 >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.rct_properties.loc['massfrac'] HCl 0.613275 Na 0.386725 ... >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.rct_elements {'H': 2.0, 'Cl': 2.0, 'Na': 2.0} >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.rct_moles 4.0... >>> ce = cm.ChemicalEquation('2 HCl + 2 Na -> 2 NaCl + H2') >>> ce.rct_mass 118.896... """ def __init__(self, eq, names=None): self.eq = eq self.names = names self._parse_equation() self._assign_rct_attrs() self._assign_prod_attrs() def _parse_equation(self): """ Split chemical equation into reactants and products. """ rct_split = self.eq.split(" -> ")[0].split(" + ") self._rct_items = rct_split prod_split = self.eq.split(" -> ")[1].split(" + ") self._prod_items = prod_split def _eq_properties(self, eq_items): """ Determine properties for each item in reactants or products. """ eq_names = [] eq_moles = [] eq_species = [] eq_molwts = [] eq_masses = [] eq_elements = Counter() for item in eq_items: # number of moles and name for each item if item[0].isdigit(): item = item.split() mol = float(item[0]) name = item[1] eq_names.append(name) eq_moles.append(mol) else: mol = 1.0 name = item eq_names.append(name) eq_moles.append(mol) # species for each item is chemical formula specified in names # dictonary or as chemical formula (name) in the chemical equation # note - species is used for the molecular weight and elements if (self.names is not None) and (name in self.names.keys()): sp = self.names[name] eq_species.append(sp) else: sp = name eq_species.append(sp) # masses and molecular weights for each item mw = cm.molecular_weight(sp) mass = mol * mw eq_molwts.append(cm.molecular_weight(sp)) eq_masses.append(mass) # count elements from each chemical species rex = re.findall("([A-Z][a-z]?)([0-9]*)", sp) for r in rex: element = r[0] if r[1] == "": atoms = 1.0 else: atoms = float(r[1]) # dict where key is element and value is number of atoms such as {'C': 6.0} d = {element: atoms * mol} eq_elements.update(d) # sum of moles and masses eq_sum_moles = sum(eq_moles) eq_sum_masses = sum(eq_masses) # mole fractions and mass fractions eq_mol_fracs = [m / eq_sum_moles for m in eq_moles] eq_mass_fracs = [m / eq_sum_masses for m in eq_masses] # dataframe for properties cols = eq_names idx = ["moles", "species", "molwt", "mass", "molfrac", "massfrac"] df = pd.DataFrame(columns=cols, index=idx) df.loc["moles"] = eq_moles df.loc["species"] = eq_species df.loc["molwt"] = eq_molwts df.loc["mass"] = eq_masses df.loc["molfrac"] = eq_mol_fracs df.loc["massfrac"] = eq_mass_fracs return df, dict(eq_elements), eq_sum_moles, eq_sum_masses def _assign_rct_attrs(self): """ Assign results for reactants to attributes. """ df, elements, sum_moles, sum_masses = self._eq_properties(self._rct_items) self.rct_properties = df self.rct_elements = elements self.rct_moles = sum_moles self.rct_mass = sum_masses def _assign_prod_attrs(self): """ Assign results for products to attributes. """ df, elements, sum_moles, sum_masses = self._eq_properties(self._prod_items) self.prod_properties = df self.prod_elements = elements self.prod_moles = sum_moles self.prod_mass = sum_masses
[docs] def is_balanced(self) -> bool: """ Check balance of atomic elements in reactants or products. """ tol = 1e-4 if self.rct_elements == self.prod_elements: return True for x, y in zip(self.rct_elements.values(), self.prod_elements.values()): if abs(x - y) > tol: return False return True