ChemicalReaction
A class to calculate end results of chemical reaction from the reaction string.
API docs: ChemicalReaction
Reaction object initialization
To create a reaction object from a string:
from chemsynthcalc import ChemicalReaction
reaction_string = "Fe2O3+C=Fe3O4+FeO+Fe+Fe3C+CO+CO2"
reaction = ChemicalReaction(reaction_string)
Important
The symbols allowed for the ChemicalReaction input string are: a-z A-Z 0-9 . () [] {} * · • = < - > → ⇄ + whitespace
Whitespaces will be ignored. If there are any other symbols in the string, they will not be ignored, instead InvalidCharacter error will be raised.
Important
Other important conditions for calculations are:
There must be one reactant-product separator (listed in possible_reaction_separators)
The sets of atoms in the reactant and product (left-right) parts of equation should be equal (in other words, all atoms in the left must appear on the right). If not, ReactantProductDifference error will be raised.
There are other important arguments for reaction object creation. The most important concept is calculation mode.
Modes
Force mode
Danger
This mode does not imply any automatic checking of calculation results. Use it at your own risk!
Force mode can be used when user enters coefficients in the reaction string and want masses to be calculated whether the reaction is balanced or not. For example, consider the following reaction for iodine synthesis:
from chemsynthcalc import ChemicalReaction
reaction_string = "KIO3+KI+H2SO4=I2+K2SO4+H2O"
If we know beforehand that coefficient list will be [1,5,3,3,3,3], we can input:
from chemsynthcalc import ChemicalReaction
reaction_string = "KIO3+5KI+3H2SO4=3I2+3K2SO4+3H2O"
reaction = ChemicalReaction(reaction_string, mode="force")
>>> reaction.is_balanced
True
>>> reaction.masses
[0.2810506, 1.09007501, 0.38640089, 1.0, 0.68654792, 0.07097859]
But what if we decided to add a 3-fold excess of KIO3? In the force mode, we can do that and still get output masses, even though the reaction is not balanced:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "3KIO3+5KI+3H2SO4=3I2+3K2SO4+3H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="force")
>>> reaction.is_balanced
False
>>> reaction.masses
[0.84315182, 1.09007501, 0.38640089, 1.0, 0.68654792, 0.07097859]
As we can see, the mass of KIO3 has increased, while all other masses have not been changed.
Check mode
Check mode is almost the same as force mode, but with mandatory reaction balance checks. Therefore, an unbalanced reaction cannot be calculated:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "3KIO3+5KI+3H2SO4=3I2+3K2SO4+3H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="check")
>>> reaction.is_balanced
chemsynthcalc.chem_errors.ReactionNotBalanced: This reaction is not balanced!
Whereas if we input the right list of coefficients:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "3KIO3+5KI+3H2SO4=3I2+3K2SO4+3H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="check")
>>> reaction.is_balanced
True
>>> reaction.masses
[0.2810506, 1.09007501, 0.38640089, 1.0, 0.68654792, 0.07097859]
Balance mode
The last mode is designed for the automatic reaction balancing by means of Balancer class. It is the default mode. For most reactions, the auto balancing option is enough:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "KIO3+KI+H2SO4=I2+K2SO4+H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="balance")
>>> reaction.coefficients
[1, 5, 3, 3, 3, 3]
>>> reaction.is_balanced
True
>>> reaction.algorithm # we can also check which algorithm solved the reaction
inverse
>>> reaction.masses
[0.2810506, 1.09007501, 0.38640089, 1.0, 0.68654792, 0.07097859]
In some cases, however, the auto-balancing is not enough, or one would want to calculate coefficients strictly with a specific algorithm. To address these issues, the following is implemented in ChemicalReaction class logic:
Coefficients property calculation
The Balancer class is designed to give high-level interface to every coefficient calculation algorithm implemented in chemsynthcalc. These can be chosen by the specific method name, and they are:
inv or matrix inverse Thorne algorithm
See inv_algorithm for details.
gpinv or general pseudoinverse Risteski algorithm
See gpinv_algorithm for details.
ppinv or partial pseudoinverse Risteski algorithm
See ppinv_algorithm for details.
comb or combinatorial algorithm
See comb_algorithm for details.
Note
Although _intify_coefficients tries to intify coefficients, it won't always succeed. In this case, coefficients will stay float without any exception raise.
Some examples of the same reaction balanced by these four different methods:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "Fe2O3+C=Fe3O4+FeO+Fe+Fe3C+CO+CO2"
>>> reaction = ChemicalReaction(reaction_string, mode="balance")
>>> reaction.coefficients = reaction.balancer.inv()
"chemsynthcalc.chem_errors.BalancingError: Can't balance reaction by inv method"
>>> reaction.coefficients = reaction.balancer.gpinv()
[1954, 1854, 518, 1093, 1096, 55, 901, 898]
>>> reaction.coefficients = reaction.balancer.ppinv()
[39, 39, 13, 13, 11, 5, 16, 18]
>>> reaction.coefficients = reaction.balancer.comb()
[4, 5, 1, 1, 1, 1, 1, 3]
As we can see, we have got four different results (including 3 right ones) using four different algorithms. This is why the :meth:.ChemicalReaction.balancer.x() methods were implemented in the first place. We can, of course, get gpinv or ppinv data without intification:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "Fe2O3+C=Fe3O4+FeO+Fe+Fe3C+CO+CO2"
>>> reaction = ChemicalReaction(reaction_string, mode="balance", intify=False)
>>> reaction.coefficients = reaction.balancer.gpinv()
[1.4169688179840456, 1.3444525018129083, 0.37563451776649776, 0.7926033357505439, 0.7947788252356772, 0.03988397389412329, 0.6533720087019586, 0.6511965192168249]
Note
Coefficients calculations by ChemicalReaction.balancer.x() methods works in all three modes (force, check and balance).
Coefficients property setter
Unlike other properties of ChemicalFormula and ChemicalReaction, which are programmed as read-only cached properties without setters, the coefficients property can be set directly with appropriate list of coefficients:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "KIO3+KI+H2SO4=I2+K2SO4+H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="check")
>>> reaction.coefficients = [1, 5, 3, 3, 3, 3]
>>> reaction.coefficients
[1, 5, 3, 3, 3, 3]
>>> reaction.is_balanced
True
The coefficients_validation() method checks setted coefficients after setting them in such properties as normalized_coefficients and masses, therefore does not allow methods to calculate values with bad coefficients, for example:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "KIO3+KI+H2SO4=I2+K2SO4+H2O"
>>> reaction = ChemicalReaction(reaction_string, mode="check")
>>> reaction.coefficients = [1]
>>> reaction.coefficients
[1]
>>> reaction.is_balanced
False
>>> reaction.masses
chemsynthcalc.chem_errors.BadCoeffiecients: Number of coefficients should be equal to 6
This is one more way (along with direct input of coefficients in the reaction string for force and check modes) to input a custom set of coefficients for a particular reaction.
Note
Coefficients setting works in all three modes (force, check and balance).
Now, when we covered modes and coefficients property calculations, we can get back to other arguments for reaction object creation.
Target
The "target" parameter is the target chemical synthesis substance (i.e., whose mass is known in advance). The target choice is implemented as an integer pointer to formula index in products (right side of equation). There are, of course, other ways to do this (like explicitly input target as formula string), but integer index target was chose as method less prone to errors. Most of the time the target of the synthesis is the first product anyway (which is equal to 0 by default). We can set a target with object instantiation (the target is 1 which is FeO):
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "Fe2O3+C=Fe3O4+FeO+Fe+Fe3C+CO+CO2"
>>> reaction = ChemicalReaction(reaction_string, mode="balance", target=1)
>>> reaction.masses
[3.97359366, 0.28358172, 1.52731369, 1.0, 0.77944268, 0.12575572, 0.32138621, 0.5032771]
If we change the target, masses will obviously change too:
>>> reaction = ChemicalReaction(reaction_string, mode="balance", target=2)
>>> reaction.masses
[5.09799345, 0.36382626, 1.95949455, 1.28296797, 1.0, 0.16134056, 0.41232821, 0.64568841]
The target can also be negative (limiting compoud - C in that case):
>>> reaction = ChemicalReaction(reaction_string, mode="balance", target=-1)
>>> reaction.masses
[14.01216438, 1.0, 5.38579736, 3.52632041, 2.74856462, 0.443455, 1.13331074, 1.77471629]
Target mass
The mass of target compound, the only mass that we know in advance before synthesis (in grams). We can change the target mass during the ChemicalReaction object instantiation (1 gram is the default):
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "Fe2O3+C=Fe3O4+FeO+Fe+Fe3C+CO+CO2"
>>> reaction = ChemicalReaction(reaction_string, mode="balance", target=1, target_mass=2)
>>> reaction.masses
[7.94718732, 0.56716344, 3.05462737, 2.0, 1.55888536, 0.25151145, 0.64277241, 1.0065542]
ChemicalReaction properties
After the object initialization, we can access the ChemicalReaction properties:
- reaction
- decomposed_reaction
- _calculated_target
- chemformula_objs
- parsed_formulas
- matrix
- balancer
- molar_masses
- coefficients
- normalized_coefficients
- is_balanced
- final_reaction
- final_reaction_normalized
- masses
- output_results
Output
A typical ChemicalReaction results output will look like this:
>>> from chemsynthcalc import ChemicalReaction
>>> reaction_string = "KIO3+KI+H2SO4=I2+K2SO4+H2O"
>>> reaction = ChemicalReaction(reaction_string)
>>> reaction.print_results()
initial reaction: KIO3+KI+H2SO4=I2+K2SO4+H2O
reaction matrix:
[[1. 1. 0. 0. 2. 0.]
[1. 1. 0. 2. 0. 0.]
[3. 0. 4. 0. 4. 1.]
[0. 0. 2. 0. 0. 2.]
[0. 0. 1. 0. 1. 0.]]
mode: balance
formulas: ['KIO3', 'KI', 'H2SO4', 'I2', 'K2SO4', 'H2O']
coefficients: [1, 5, 3, 3, 3, 3]
normalized coefficients: [0.33333333, 1.66666667, 1, 1, 1, 1]
algorithm: inverse
is balanced: True
final reaction: KIO3+5KI+3H2SO4=3I2+3K2SO4+3H2O
final reaction normalized: 0.33333333KIO3+1.66666667KI+H2SO4=I2+K2SO4+H2O
molar masses: [213.99947, 166.00247, 98.072, 253.80894, 174.252, 18.015]
target: I2
masses: [0.2811, 1.0901, 0.3864, 1.0, 0.6865, 0.071]
KIO3: M = 213.9995 g/mol, m = 0.2811 g
KI: M = 166.0025 g/mol, m = 1.0901 g
H2SO4: M = 98.0720 g/mol, m = 0.3864 g
I2: M = 253.8089 g/mol, m = 1.0000 g
K2SO4: M = 174.2520 g/mol, m = 0.6865 g
H2O: M = 18.0150 g/mol, m = 0.0710 g
One can output ChemicalReaction results using one of the 4 methods:
- print_results: print to stdout
- to_txt: save as plain txt file
- to_json: serialization of output into an JSON object
- to_json_file: save as JSON file