ChemicalFun is a C++ library (Python and C++ API) for generating balanced chemical reactions and for parsing and calculating properties of chemical formulas.
ThermoFun can be easily installed using Conda package manager. If you have Conda installed, first add the conda-forge channel by executing
#!bash
conda config --add channels conda-forge
install ChemicalFun by executing the following command:
#!bash
conda install chemicalfun
Conda can be installed from Miniconda.
The thermofun library uses nlohmann/json.hpp as thirdparty dependency to parse database files in json format. To install the header only json library in a terminal ~/thermofun$ execute the following:
#!bash
sudo ./install-dependencies.sh
On Windows run the following batch file in cmd:
install-dependencies.bat
Create the thermofun environnement by executing in ~/chemicalfun$:
conda devenv
then activate the environnement
conda activate chemicalfun
In the terminal (chemicalfun)~/chemicalfun$, execute the following commands:
mkdir build && \
cd build && \
cmake .. && \
make install
For a local installation, you can specify a directory path for the installed files as follows:
#!bash
cmake .. -DCMAKE_INSTALL_PREFIX=/home/username/local/
The chemical reactions generator generates all independent balanced chemical reactions for a given list of substances. The library uses the method described by Smith and Missen (1997) to generate reactions of dependent substances from master substances.
import chemicalfun as cf
formulas = ['Ca+2', 'CO3-2', 'H+', 'OH-', 'HCO3-', 'CaCO3', 'H2O']
chemicalReactions = cf.ChemicalReactions(formulas)
reactions = chemicalReactions.generateReactions() # returns the reactions list as a list of tuples ('substance', coefficient)
# can be transformed to a list of dictionaries, with reaction substances as keys and the reaction coefficients as values
reactions_dic = [{el[0]: el[1] for el in r} for r in reactions]
print(chemicalReactions.printReactions())
print(reactions_dic)Output
HCO3- = H+ + CO3-2
CaCO3 = CO3-2 + Ca+2
H2O = H+ + OH-
[{'H2O': 1.0, 'OH-': -1.0, 'H+': -1.0},
{'CO3-2': 1.0, 'H2O': 1.0, 'OH-': -1.0, 'HCO3-': -1.0},
{'Ca+2': 1.0, 'CO3-2': 1.0, 'CaCO3': -1.0}]
The master substances in the reactions are decided based on the order in the given list, with master substances being the first. The number of master substances is decided based on the given substance list and thier elemental composition. To swap the master substances exchange the position in the list.
formulas = ['Ca+2', 'CO3-2', 'H2O', 'OH-', 'H+', 'HCO3-', 'CaCO3' ] # 'H2O' and 'OH-' are in front of 'H+' and will be set as master
chemicalReactions = cf.ChemicalReactions(formulas)
reactions = chemicalReactions.generateReactions()
print(chemicalReactions.printReactions())
print(f'master: {chemicalReactions.masterSubstances()}')Output
H+ + OH- = H2O
HCO3- + OH- = H2O + CO3-2
CaCO3 = CO3-2 + Ca+2
master: ['Ca+2', 'CO3-2', 'H2O', 'OH-']
By default the algorithm produces dissociation reactions (dissociation of dependent substances). To generate formation reactions, where the dependent substances are on the product side of the reaction:
reactions = chemicalReactions.generateReactions(formation=True) # formation=True to generate formation reactions
print(chemicalReactions.printReactions())
print(f'master: {chemicalReactions.masterSubstances()}')Output
H2O = H+ + OH-
H2O + CO3-2 = HCO3- + OH-
CO3-2 + Ca+2 = CaCO3
master: ['Ca+2', 'CO3-2', 'H2O', 'OH-']
dependent: ['H+', 'HCO3-', 'CaCO3']
For more complex cases where element valcences are involved this can be defined using |3| after the element symbol e.g. FeFe|3|2O4. To generate reactions where each element valence has its own master species use valence=True when creating a ChemicalReactions object.
formulas = [ 'OH-', 'H+', 'Fe+2', 'Fe+3', 'FeO@', 'FeFe|3|2O4', 'O2', 'H2O']
chemicalReactions = cf.ChemicalReactions(formulas, valence=True)
reactions = chemicalReactions.generateReactions(formation=True)
print(chemicalReactions.printReactions())
print(f'master: {chemicalReactions.masterSubstances()}')
print(f'dependent: {chemicalReactions.dependentSubstances()}')Output
Fe+2 + OH- = FeO@ + H+
4Fe+3 + 2OH- = O2 + 4Fe+2 + 2H+
H+ + OH- = H2O
master: ['OH-', 'H+', 'Fe+2', 'Fe+3', 'FeFe|3|2O4']
dependent: ['FeO@', 'O2', 'H2O']
There are 5 master substances, one for each element with valence: O|0|, O|-2|, H|1|, Fe|2|, and Fe|3|. Without using the valence option we get
chemicalReactions = cf.ChemicalReactions(formulas)
reactions = chemicalReactions.generateReactions(formation=True)
print(chemicalReactions.printReactions())
print(f'master: {chemicalReactions.masterSubstances()}')
print(f'dependent: {chemicalReactions.dependentSubstances()}')Output
Fe+2 + OH- = FeO@ + H+
2Fe+3 + Fe+2 + 4OH- = FeFe|3|2O4 + 4H+
4Fe+3 + 2OH- = O2 + 4Fe+2 + 2H+
H+ + OH- = H2O
master: ['OH-', 'H+', 'Fe+2', 'Fe+3']
dependent: ['FeO@', 'FeFe|3|2O4', 'O2', 'H2O']
To have reactions using substance symbols, provide the symbols list in addition to their formulas when creating a ChemicalReactions object. Pay attention to the order in the two lists and that each formula has a corresponding symbol.
formulas = ["Ca+2", "Fe+2", "Fe|3|+3", "H+", "OH-", "SO4-2", "CaSO4@", "CaOH+", "FeO@", "HFe|3|O2@", "FeOH+", "Fe|3|OH+2", "H2O@", "FeS|-2|", "FeS|0|S|-2|", "S|4|O2"]
symbols = ["Ca+2", "Fe+2", "Fe+3", "H+", "OH-", "SO4-2", "CaSO4(aq)", "CaOH+", "FeO(aq)", "HFeO2(aq)", "FeOH+", "FeOH+2", "H2O(aq)", "Pyrrhotite", "Pyrite", "SO2(gas)"]
chemicalReactions = cf.ChemicalReactions(formulas, symbols)
reactions = chemicalReactions.generateReactions()
print(chemicalReactions.printReactions())
print(f'master: {chemicalReactions.masterSubstances()}')
print(f'dependent: {chemicalReactions.dependentSubstances()}')Output
CaSO4(aq) = SO4-2 + Ca+2
CaOH+ = OH- + Ca+2
FeO(aq) + H+ = OH- + Fe+2
HFeO2(aq) + H+ = 2OH- + Fe+3
FeOH+ = OH- + Fe+2
FeOH+2 = OH- + Fe+3
H2O(aq) = OH- + H+
Pyrrhotite + 4OH- + 8Fe+3 = SO4-2 + 4H+ + 9Fe+2
Pyrite + 8OH- + 14Fe+3 = 2SO4-2 + 8H+ + 15Fe+2
SO2(gas) + 2OH- + 2Fe+3 = SO4-2 + 2H+ + 2Fe+2
master: ['Ca+2', 'Fe+2', 'Fe+3', 'H+', 'OH-', 'SO4-2']
dependent: ['CaSO4(aq)', 'CaOH+', 'FeO(aq)', 'HFeO2(aq)', 'FeOH+', 'FeOH+2', 'H2O(aq)', 'Pyrrhotite', 'Pyrite', 'SO2(gas)']
The class that holds formula information is called FormulaToken. It is constructed from a given formula string. The parser relies on a default database of elements, which can be overwritten with custom element properties.
ChemicalFun uses the oxidation state (valence) of each element to calculate formula properties such as charge. By default, the oxidation state is taken from the elements database unless explicitly specified in the formula.
For example:
Fe|3|(OH)4-→ The oxidation state of Fe is explicitly set to +3 using the bars|3|.Fe(OH)4-→ The parser uses the default oxidation state of Fe from the database (+2). This leads to a mismatch between the calculated charge and the charge indicated in the formula.
import chemicalfun as cf
# Enable logging to file (info and warnings)
cf.update_loggers(True, "chemicalfun.log", 1)
# Initialize default elements database
dbelements = cf.DBElements()
# Example 1: Formula without explicit valence, default 4 for Si and -2 for O
token = cf.FormulaToken("SiO3-2")
print("formula properties:", token.properties(dbelements.elements()))
# Charge is calculated from default valences or explicit valences (via bars ||)
print("charge:", token.charge())
# Stoichiometry output (Zz represents the charge)
print("stoichiometry:", token.stoichCoefficients())
# Example 2: Formula with explicit Fe valence
token = cf.FormulaToken("Fe|3|(OH)4-")
print("formula properties:", token.properties(dbelements.elements()))
print("charge:", token.charge())
# Example 3: Formula without explicit valence
token.setFormula("Fe(OH)4-")
print("default valence of Fe in DB:", dbelements.defaultValence("Fe"))
# Charge mismatch warning: default valence (+2) gives -2, while formula indicates -1
print("charge from valences:", token.charge())
print("charge from formula:", token.charge(use_charge_from_formula=True))
print("formula properties:", token.properties(dbelements.elements(), use_charge_from_formula=True))For more examples, see the /examples directory.