Automated Balancing of Chemical Equations

Other related questions at Mathematics Stack Exchange (MSE) are mentioned in MSE publications / references 2020, which is on my personal website. Automated Balancing of Chemical Equations is the title of a computer program which has been developed for the purpose. Development has taken place as early as in 2004. However, publications about automatic balancing have already appeared in 1995. So it seems that I've been reinventing the wheel; or at least parts of it.
Meanwhile, quite a few automatic balancers have become available on the internet; let Google be your friend to find some. Seemingly a popular one is Wolfram Alpha, as is advertized in one of the questions.
We decided to elaborate further on the answer given by lynn and explain a bit about our computer program. Not because it would be better than other "apps" of the kind, but for other reasons, the most important being that the mathematics involved may not very familiar. By the way, the accompanying software is CopyLefted open source; there is nothing secret under the hood.
An important remark is in the abstract of an article titled Generalized matrix inverse approach for automatic balancing of chemical equations. Quote: Chemical reactions are classified as not feasible, uniquely‐feasible (unique within relative proportions) and non‐uniquely feasible. We shall arrive at each of these three cases in turn.

From input to matrix

The above equation has been cast into the following form, as plain text input:
? C_2 H_2 Cl_4 + ? Ca(O H)_2 ==> ? C_2 H Cl_3 + ? Ca Cl_2 + ? H_2 O
An essential statement in our input parser is that it throws out all characters that are not OK, mainly according to:
OK := c in ['A'..'Z','a'..'z','0'..'9','+','=','(',')'];   { with Pascal built-in Set Theory }
The advantage of this is flexibility of input: most of it is "comment". It leaves us with the following much shorter string, containing only the essentials, to be processed futher:
C2H2Cl4+Ca(OH)2=C2HCl3+CaCl2+H2O
The end result of input processing is an array that stores the following data structure:
  onthou = record
    e : string[2]; { name of chemical element }
    L : integer;   { length of element name (1 or 2) }
    p : integer;   { position in short formula string }
    r : integer;   { numbering of matrix rows }
    m : integer;   { numbering of matrix columns }
    g : integer;   { number of atoms in molecule }
  end;

Then comes the computational part. The data stored in the above structure is converted into a matrix and a vector, essentially according to the answer given by lynn: $$ \begin{bmatrix} 2 & 2 & -1 & 0 \\ 2 & 0 & -2 & 0 \\ 0 & 2 & 0 & 0 \\ 4 & 0 & -3 & -2 \\ 0 & 1 & 0 & -1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ d \end{bmatrix} = \begin{bmatrix} 2 \\ 0 \\ 1 \\ 0 \\ 0 \end{bmatrix} $$ Only the numbering of the atoms (matrix rows) is different, which should not come as a surprise. The above is an overdetermined system which can be solved as: $$ A^TA x = \begin{bmatrix} 24 & 4 & -18 & -8 \\ 4 & 9 & -2 & -1 \\ -18 & -2 & 14 & 6 \\ -8 & -1 & 6 & 5 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \\ d \end{bmatrix} = \begin{bmatrix} 4 \\ 6 \\ -2 \\ 0 \end{bmatrix} = A^T b $$ This raises the first question: is the system of equations $A^TAx=A^Tb$ indeed equivalent with $Ax=b$?

Least Squares method

Let a system of (overdetermined) equations $(Ax-r=\,?)$ be squared, summed and minimized: $$ (Ax-r,Ax-r) = \sum_{k=1}^M \left( \sum_{i=1}^N A_{ki} x_i - r_k \right)^2 = \,\operatorname{residual}(x) $$ By differentiating to $\,x_i\,$ we get $N$ (symmetric) equations with $N$ unknowns $Sx=b\;$:
$\partial/\partial x_i\, \operatorname{residual}(x) =$ $$ 2\sum_{k=1}^M A_{ki} \left( \sum_{j=1}^N A_{kj} x_j - r_k \right) = 0 \quad \Longrightarrow \\ \sum_{j=1}^N \underbrace{\sum_{k=1}^M A_{ki} A_{kj}}_{\large S_{ij}}\, x_j = \underbrace{\sum_{k=1}^M A_{ki} r_k}_{\large b_i} $$ It is seen that, with a Least Squares method, the ( numbering of the atoms = row numbering of $A$ ) is effectively eliminated. That numbering isn't relevant anyway. Instead we have now: row numbering = column numbering = order of molecules in the chemical equation. Only the latter is relevant. It is also obvious that the matrix $S = A^TA$ is symmetric. And it is positive definite. Which means that no pivot search is needed: all pivots are on the main diagonal of the matrix $S$. And the Hessian matrix of the function $\,\operatorname{residual}(x)\,$ happens to be the same matrix $S$. As a consequence the residual indeed reaches a local minimum for the solution values $x_j$. It is not guaranteed, though, that the minimum is zero. Most of the time it will be, but not always. We shall see an example soon. It all means that we always have to check afterwards if the chemical equation is truly balanced, by multiplying the augmented matrix $A$ with the coefficients (solution) vector and verify that the result is the zero vector.
So far so good. The next question is: how are we going to solve these equations? Three strategies have been employed for the purpose.

Employing Cramer's rule

Proposed recipe. Invert the matrix according to Cramer's rule: compute subdeterminants of the matrix elements and - do not yet - divide by the determinant of the whole matrix. Then multiply the right hand side vector with this inverse times the determinant. Divide the outcomes by their greatest common divisor.
An obvious advantage of Cramer's method is that integer arithmetic is preserved. And a matrix sometimes needs not to be (least) squared, namely if it is already a square matrix by itself: $$ \operatorname{Det}\left(A^TA\right) = \operatorname{Det}\left(A\right)^2 \gt 0 \quad \Longrightarrow \quad \operatorname{Det}\left(A\right) \ne 0 $$ But the disadvantage is also well known: the method quickly becomes computationally prohibitive, even with moderate values of the matrix size $n$. That happens because the number of computations involved is proportional to $n!$ (or some such), as confirmed by Wikipedia and answers to questions at MSE, for example this one: Is Cramer's rule efficient for computational point of view?

Floating point Gauss solver

Cast all integer values to floating point and use a common Gauss solver. This can be the simplest one, since our matrix is positive definite and hence all pivots are on the main diagonal. A definite advantage of the floating point method is that it is fast, and accompanying software is (almost) ready for use. Here is the basic template:
procedure Oplossen(A : matrix; var b : vector);
{
  Simple Gauss Solver
}
var
  N,i,j,k : integer;
  p,s : double;
begin
  N := Length(b);
{ Elimination }
  for k := 0 to N-1 do
  begin
    p := A[k,k]; { pivot }
    for i := k+1 to N-1 do
    begin
      if A[i,k] = 0 then Continue;
      A[i,k] := A[i,k]/p;
      for j := k+1 to N-1 do
        A[i,j] := A[i,j] - A[i,k]*A[k,j];
      b[i] := b[i] - A[i,k]*b[k];
    end;
  end;
{ Backsubstitution }
  for k := N-1 downto 0 do
  begin
    s := b[k];
    for j := k+1 to N-1 do
      s := s - A[k,j]*b[j];
    b[k] := s/A[k,k];
  end;
end;
After having obtained an upper triangular matrix, the product of all main diagonal elements is the determinant. The solver is part of our open source, so it's easy to expand the output parameter list with a `Det`. Due to the integer nature of $A^TA$, the determinant must be an integer as well. Hence multiply the floating point solution with the determinant. Then, after rounding everything to the nearest integer and using GCD's, we should obtain the coefficients of the chemical equation.
However, without further modifications of the source, you may run in an error message like the one below:
Runtime error 207 at 00405D4E 
What it means by the way is: division by zero. In order to avoid it, we have modified the source code in such a way that divisions are simply not carried out for pivot values zero. The resulting Garbage Out will do no harm, because we have an overall balance check in the end.

Rational number arithmetic

Instead of using floating point arithmetic, we can define a true rational number (fractions) arithmetic, with integers in the numerators and integers in the denominators:
type
  breuk = record { fraction }
    teller,noemer : integer; { numerator,denominator }
  end;
Apologies for using so many Dutch words in my programming. One reason is that it protects us from conflicts with the (IMHO too many) reserved (and English) words in Pascal. Anyway, define alternatives for common arithmetic and other common operations. Mind the `Simplify` routine which should be present in most of the fraction functions. Without enduring simplification, the size of numerators and denominators can run quickly out of hand, maybe more quickly than expected. So I've used `int64` inside, which is the largest integer type available in Delphi Pascal.
function maak(a,b : integer) : breuk; { make }
function vereenvoudig(f : breuk) : breuk; { Simplify }
function afdruk(f : breuk) : string; { print }
function plus(a,b : breuk) : breuk; { addition }
function minus(a,b : breuk) : breuk; { substraction }
function maal(a,b : breuk) : breuk; { multiplication }
function deel(a,b : breuk) : breuk; { division }
function gelijk(a,b : breuk) : boolean; { equality }
function groter(a,b : breuk) : boolean; { greater }
function dubbel(a : breuk) : double; { Double precision }
As a next step, take the above open source simple Gauss solver and replace in there all common arithmetic operators by their rational number equivalents. The "Fractions" module can be designed in such a way that it is insensitive to division by zero. Which does not mean that you will always have a sensible outcome. But that doesn't matter much, because we can always check the balancing afterwards and there are no divisions in the latter procedure.

Back to our problem. With rational number arithmetic, matrix and vector may be displayed as follows, after pivoting: $$ \begin{bmatrix} 24/1 & 4/1 & -18/1 & -8/1 & | & 4/1 \\ & 25/3 & 1/1 & 1/3 & | & 16/3 \\ & & 19/50 & -1/25 & | & 9/25 \\ & & & 44/19 & | & 22/19 \end{bmatrix} $$ Backsubstitution as usual gives the following solution for the coefficients in the chemical equation: $$ \begin{bmatrix} 1/1 & 1/2 & 1/1 & 1/2 & 1/1 \end{bmatrix} $$ At last multiply with the maximum of the denominators (GCD is not necessary !) and we are finished: $$ \mathrm{2\, C_2 H_2 Cl_4 + Ca(OH)_2 \,\rightarrow \, 2\, C_2 H Cl_3 + Ca Cl_2 + 2\, H_2 O} $$

Multiple solutions

Consider the following input to the program:
? Fe + ? O_2 ==> ? Fe O + ? Fe_2 O_3
The accompanying balancing equations are: $$ \begin{matrix} \, O : \\ Fe : \end{matrix} \begin{bmatrix} 0 & 2 & -1 & -3 \\ 1 & 0 & -1 & -2 \end{bmatrix} \begin{bmatrix} p \\ q \\ r \\ 1 \end{bmatrix} = 0 \quad \Longrightarrow \quad \begin{bmatrix} 0 & 2 & -1 \\ 1 & 0 & -1 \end{bmatrix} \begin{bmatrix} p \\ q \\ r \end{bmatrix} = \begin{bmatrix} 3 \\ 2 \end{bmatrix} $$ Do the least squares: $$\begin{bmatrix} 0 & 1 \\ 2 & 0 \\ -1 & -1 \end{bmatrix} \begin{bmatrix} 0 & 2 & -1 \\ 1 & 0 & -1 \end{bmatrix} \begin{bmatrix} p \\ q \\ r \end{bmatrix} = \begin{bmatrix} 0 & 1 \\ 2 & 0 \\ -1 & -1 \end{bmatrix} \begin{bmatrix} 3 \\ 2 \end{bmatrix} $$ $$ \begin{bmatrix} 1 & 0 & -1 \\ 0 & 4 & -2 \\ -1 & -2 & 2 \end{bmatrix} \begin{bmatrix} p \\ q \\ r \end{bmatrix} = \begin{bmatrix} 2 \\ 6 \\ -5 \end{bmatrix} $$ The matrix is pivoted by hand: $$ \begin{bmatrix} 1 & 0 & -1 \\ 0 & 4 & -2 \\ -1 & -2 & 2 \end{bmatrix} \sim \begin{bmatrix} 1 & 0 & -1 \\ 0 & 4 & -2 \\ 0 & -2 & 1 \end{bmatrix} \sim \begin{bmatrix} 1 & 0 & -1 \\ 0 & 4 & -2 \\ 0 & 0 & 0 \end{bmatrix} $$ It's easily concluded that the system of equations is singular. Which means that there are multiple solutions. To mention a few of these: $$ \mathrm{3\, Fe + 2\, O_2 \,\rightarrow \,Fe O + Fe_2 O_3} \\ \mathrm{5\, Fe + 3\, O_2 \,\rightarrow \, 3\,Fe O + Fe_2 O_3} \\ \mathrm{7\, Fe + 5\, O_2 \,\rightarrow \,Fe O + 3\, Fe_2 O_3} $$

Zero solution

Another pathological case is illustrated by the following input:
? Fe S + ? H_2 S O_4 ==> ? Fe S O_4 + ? H_2 O
The accompanying (augmented) balancing matrix is pivoted by hand, without least squares though: $$ \begin{matrix} \text{Fe :} \\ \text{S :} \\ \text{O :} \\ \text{H :} \end{matrix} \begin{bmatrix} 1 & 0 & -1 & 0 \\ 1 & 1 & -1 & 0 \\ 0 & 4 & -4 & -1 \\ 0 & 2 & 0 & -2 \end{bmatrix} \sim \begin{bmatrix} 1 & 0 & -1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 4 & -4 & -1 \\ 0 & 2 & 0 & -2 \end{bmatrix} \sim \begin{bmatrix} 1 & 0 & -1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -4 & -1 \\ 0 & 0 & 0 & -2 \end{bmatrix} $$ The determinant is the product of the main diagonal elements of the last matrix, which is $8$. Thus it is easily seen that the balancing matrix is non-singular. This would have resulted in a Least Squares problem with non-zero residual. Therefore it is not allowed to put any of the unknown coefficients equal to a fixed value (i.e. the last one $=1$). There is no other solution than the zero solution: $$ \mathrm{0\, Fe S + 0\, H_2 S O_4 \,\rightarrow 0\,Fe S O_4 + 0\, H_2 O} $$ Herewith all possible cases have been covered.