This article describes a way in which a set of three linear equations with three unknown variables can be solved. First, some basic information about linear equations is given. Then one solution method, a method of determinants, is explained.
Next, two classes used by the solution procedure are illustrated. One of them, called Matrix3x3, stores coefficients of the equation system. The other, Vector3, holds three constants on the right-hand side of the system. If the solution is successful, it also gets values of three unknown variables. The solution procedure is built into the Matrix3x3 class as its static member function.
Finally, two variants of an user interface suitable for the problem are mentioned: a Windows Form and a Web User Control that can be placed on a web page.
The demo download contains one file only: System3x3.exe. It is a Windows application enabling the user to set nine coefficients of three linear equations and a vector of constants and then to find the solution. The source download includes two projects. One is a program based on Windows Forms; the other is an ASP.NET application providing a user interface with a help of User Web Control. Both projects use C# and both are prepared in Visual Studio 2005 Express Edition.
Background
There are many problems in physics and economics that can be described by sets of linear equations. As an illustration, let me consider the following electric circuit:
Applying Kirchoff's laws, we get a set of three equations. These must be solved for three unknown currents: I1, I2 and I3.
I1 + I2 – I3 = 0
0.2 I1 – 0.2 I2 = –0.2
0.2 I2 + 0.75 I3 = 3.5
This set is an example of a system of three linear equations. Such a system can be more generally written in the form:
a11 x1 + a12 x2 + a13 x3 = b1a21 x1 + a22 x2 + a23 x3 = b2a31 x1 + a32 x2 + a33 x3 = b3
or even a little bit more symbolically:
A x = b
Here, A is a 3 times 3 matrix of coefficients; b is a three-dimensional vector of right-hand side and x is a vector of unknown variables, also three-dimensional in this case. Many methods of solving such equation systems exist. One of them is a method of determinants. The method claims that:
x1 = D1 / D
x2 = D2 / D
x3 = D2 / D
where D is the value of determinant associated with coefficient matrix A. D1 is the value of the determinant obtained from matrix A by replacing its first column by the vector of right-hand side. D2 and D3 are analogous. The value of the determinant is obtained from matrix coefficients as follows:
D = a11 (a22 a33 - a23 a32) – a12 (a21 a33 - a23 a31) + a13 (a21 a32 - a22 a31)
In writing a program in C# to solve a system of 3 linear equations, we meet the following challenge: we would like to have a class that holds the coefficient matrix and provides at least its initialization. At the same time, we want an easy approach to distinct coefficients of the matrix. In the next section, I describe how such a class can be made.
Using the code
How to store a matrix of coefficients
A two-dimensional array is a suitable container for a matrix of coefficients.
class Matrix3x3
{
public double [,] A = new double [3,3];
public Matrix3x3()
{
for ( int i = 0; i < 3; i++ )
for ( int j = 0; j < 3; j++ )
A[i,j] = 0;
}
}
I declared the two-dimensional array A and set its size equal to 3 times 3. In the constructor, I initialized all of the coefficients to zero. Because the array A was declared as public, I can use my matrices as in the following example:
Matrix3x3 A = new Matrix3x3(), B = new Matrix3x3(), C = new Matrix3x3();
A.A[1,2] = 1.25;
B.A[1,2] = 6.78;
C.A[1,2] = A.A[1,2] + B.A[1,2];
This works, but it is not very convenient and is certainly a bit confusing. A better approach is:
class Matrix3x3
{
private double [,] A = new double [3,3];
public Matrix3x3()
{
for ( int i = 0; i < 3; i++ )
for ( int j = 0; j < 3; j++ )
A[i,j] = 0;
}
public double this[int i, int j]
{
get
{
if (!((i < 0 || i >= 3) && (j < 0 || j <= 3)))
return A[i,j];
else
return 0;
}
set
{
if (!((i < 0 || i >= 3) && (j < 0 || j <= 3)))
A[i,j] = value;
}
}
}
Now the coefficient array is private and cannot be touched directly from outside. Square brackets play the role of indexer, thus enabling us to get or set the desired matrix coefficient. Moreover, index range is checked and out-of-range index values are ignored. Our matrices can now be used like arrays, which may be understood as quite convenient. See the following code:
Matrix3x3 A = new Matrix3x3(), B = new Matrix3x3(), C = new Matrix3x3();
A[1,2] = 1.25;
B[1,2] = 6.78;
C[1,2] = A[1,2] + B[1,2];
A vector of right-hand side constants
The problem with a vector is similar to that of a matrix. Although in this article we work only with three-dimensional vectors, a vector can have more elements than three. So, it is natural to store vector elements in a one-dimensional array. We want to get a direct approach to distinct vector elements, but we also want to keep the array-container itself as a private variable and possibly provide some kind of range checking. Moreover, in classical physics it is usual to denote vector components with indices x, y and z. The following code shows how these goals can be achieved:
class Vector3
{
private double[] v = new double[3];
public Vector3()
{
v[0] = v[1] = v[2] = 0;
}
public double this[int i]
{
get
{
if (i < 0 || i >= 3)
return 0;
else
return v[i];
}
set
{
if (!(i < 0 || i >= 3))
v[i] = value;
}
}
}
To enable indexing in the style of x, y, z, I define public get/set variables named x, y and z:
public double x
{
get { return v[0]; }
set { v[0] = value; }
}
public double y
{
get { return v[1]; }
set { v[1] = value; }
}
public double z
{
get { return v[2]; }
set { v[2] = value; }
}
Indices x, y and z allow the user to manipulate vectors in the familiar style of math textbooks. Let us see a function calculating the so-called cross product of two vectors:
public static Vector3 CrossProduct(Vector3 a, Vector3 b)
{
Vector3 r = new Vector3();
r.x = a.y * b.z - a.z * b.y;
r.y = a.z * b.x - a.x * b.z;
r.z = a.x * b.y - a.y * b.x;
return r;
}
Note that such a definition of the cross product of two vectors is applicable to vectors having three components only. When combining vectors and matrices together, it is better to use numerical indices. See the function using the matrix as the first argument and the vector as the second multiply a matrix by a vector.
Vector3 Multiply(Matrix3x3 A, Vector3 b)
{
Vector3 r = new Vector3(0);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
r[i] += A[i, j] * b[j];
return r;
}
In this case, it was clearer to use numerical indices for both matrix and vector.
Functions to solve a system of linear equations
As mentioned in the Background section, a system of three linear equations can be solved with the help of determinants. The first step should be to define a function calculating the value of the determinant. Although mathematics makes a distinction between square matrices and determinants -- even using the term determinant in two meanings, once for a table-like object filled by numbers and secondly for one number assigned to that object -- I will simply calculate the determinant value from the coefficients of our 3 times 3 square matrix. The function will be a member of the Matrix3x3 class.
public double Determinant()
{
double d = 0;
d += A[0, 0] * (A[1, 1] * A[2, 2] - A[1, 2] * A[2, 1]);
d -= A[0, 1] * (A[1, 0] * A[2, 2] - A[1, 2] * A[2, 0]);
d += A[0, 2] * (A[1, 0] * A[2, 1] - A[1, 1] * A[2, 0]);
return d;
}
The method of solution also requires calculating the determinant value of three matrices that were obtained from the "main" system matrix by an interchange of one of its columns by the vector of right-hand side. So we need a function which does this substitution:
public static int ChangeColumn(out Matrix3x3 R,
Matrix3x3 A, Vector3 b, int j)
{
R = new Matrix3x3();
R.Copy(A);
if(j < 0 || j >= 3)
return 1;
R[0, j] = b[0];
R[1, j] = b[1];
R[2, j] = b[2];
return 0;
}
The function is straightforward. I create a new matrix denoted R as "result" and then copy the original matrix A to it. Then I rewrite its j-th column by the vector b. What about the Copy function? What does it do and why is it used? The answer is that classes are always of the reference type. If I simply wrote R = A, then R would become directed to A and any subsequent changes in R would modify the A itself. Here is how the Copy member function looks:
public void Copy(Matrix3x3 X)
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
this[i, j] = X[i, j];
}
The solution itself is provided by a static function, SolveEquations. The function has three parameters. The first one is a vector that will get the values of three unknown variables. The second parameter is a matrix containing coefficients of the system of equations. The third parameter is a vector holding three constants on the right side of equation system.
public static int SolveEquations(out Vector3 x,
Matrix3x3 A, Vector3 b)
{
x = new Vector3();
const double eps = 1.0E-12;
double d = A.Determinant();
if (Math.Abs(d) < eps)
return 1;
Matrix3x3 M0 = new Matrix3x3();
Matrix3x3 M1 = new Matrix3x3();
Matrix3x3 M2 = new Matrix3x3();
ChangeColumn(out M0, A, b, 0);
ChangeColumn(out M1, A, b, 1);
ChangeColumn(out M2, A, b, 2);
x[0] = M0.Determinant() / d;
x[1] = M1.Determinant() / d;
x[2] = M2.Determinant() / d;
return 0;
}
It is true that not every system of linear equations has a solution. Non-existence of the solution is indicated by zero value of the coefficient matrix determinant. It fact, even values that are close to zero can cause rounding errors, thus making the result unreliable. The simplest way to deal with this problem is to refuse any system having a small value of the coefficient matrix determinant. What is a small value? For the SolveEuations function, any determinant value less than 10-12 is too small and the function stops its calculation, returning an error value of 1.
Otherwise, SolveEquations creates three matrices that are derived from the main matrix and have gotten one of their columns from the right-hand side vector. Dividing the determinants of these matrices by the determinant of the main matrix, the function gets the solution and puts it into output vector x. The return-value of zero indicates that the calculation was successful.
How to use
As an example, let us consider the system of three linear equations mentioned in the Background section. The only difference is that to obtain a standard look, I denote the vector of unknown variables as x instead of I.
x1 + x2 – x3 = 0
0.2 x1 – 0.2 x2 = –0.2
0.2 x2 + 0.75 x3 = 3.5
The following code shows how to solve this set of equations with the help of the Vector3 and Matrix3x3 classes.
Collapse
using System;
using System3x3;
static void Test()
{
// Declare a matrix of coefficients
Matrix3x3 A = new Matrix3x3();
// Set coefficient values
A[0, 0] = 1; A[0, 1] = 1; A[0, 2] = -1;
A[1, 0] = 0.2; A[1, 1] = -0.2; A[1, 2] = 0;
A[2, 0] = 0; A[2, 1] = 0.2; A[2, 2] = 0.75;
// Declare a vector of right-hand side
Vector3 b = new Vector3();
// Set constants on the right-hand side
b[0] = 0; b[1] = -0.2; b[2] = 3.5;
// Declare a solution vector. Not necessary to initialize
Vector3 x;
// Solve the system
if (Matrix3x3.SolveEquations(out x, A, b) == 0)
{
// OK, put down the results
Console.WriteLine("x[0] = {0:G}",x[0]);
Console.WriteLine("x[1] = {0:G}",x[1]);
Console.WriteLine("x[2] = {0:G}",x[2]);
}
else
{
// Problem, unable to solve
Console.WriteLine("Solution not found");
}
}
Because normally we can never be sure that the given set of equations is solvable, the user should always check the return value of the SolveEquations function. Only when the function returns zero can the user use its output parameter x. The result of the example:
x[0] = 1,5
x[1] = 2,5
x[2] = 4
The user interface
A Windows form suits well for an application that allows the user to input nine coefficients of a linear system and three values of a right side vector and then solves the equations and displays results. See the picture at the beginning of this article.
If we decide to build a Web user control that can be placed on a web page, the situation will become a bit more complicated. Comparing to a Windows form, a Web form has got one specific feature: variables in the code behind the form do not hold their values between subsequent form presentations. Instead, whenever a user clicks a button to ask the server to provide some action, a new instance of a page is created and sent to the browser. Special effort must even be put to the recognition of the first page exhibition because this is just the moment when the control is to be initialized. Here is how the first occurrence of the control can be detected:
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
InitInputFields();
}
protected void InitInputFields()
{
TextBoxA00.Text = "0";
TextBoxA01.Text = "0";
. . .
}
The server calls the Page_Load method each time the page is being created. The IsPostBack property gets the false value in the case where the page is created for the first time; this is the right instant for control initialization. In our example, the initialization itself is done by the InitInputFields method, which simply sets the Text property of all input text boxes to zero.
Another aspect in which a Web form differs from a Windows form is its way of displaying error messages. A typical approach is to place a label control to some appropriate area on the form and -- if necessary -- to write the message text into it. The message then stays visible until it is explicitly cleaned. In the following example, the ShowErrorMessage method puts the text string into the LabelErrorMessage control. The control is then positioned in the free center of the form and set to display its contents in red. CleanErrorMethod simply removes the text, thus hiding the message.
protected void ShowErrorMessage(string s)
{
LabelErrorMessage.Text = s;
}
protected void CleanErrorMessage()
{
LabelErrorMessage.Text = "";
}
The following portion of code demonstrates how the user input can be easily validated. Because the only condition given for input boxes is that all of them should contain a number, we simply try to convert their content to double. When the try block is broken, the pos variable points to the input field with incorrect input. The pos value then enables both the formatting and display of the error message and also sets focus to the incriminated control.
Collapse
try
{
A[0, 0] = Convert.ToDouble(TextBoxA00.Text); pos += 1;
A[0, 1] = Convert.ToDouble(TextBoxA01.Text); pos += 1;
A[0, 2] = Convert.ToDouble(TextBoxA02.Text); pos += 1;
A[1, 0] = Convert.ToDouble(TextBoxA10.Text); pos += 1;
A[1, 1] = Convert.ToDouble(TextBoxA11.Text); pos += 1;
A[1, 2] = Convert.ToDouble(TextBoxA12.Text); pos += 1;
A[2, 0] = Convert.ToDouble(TextBoxA20.Text); pos += 1;
A[2, 1] = Convert.ToDouble(TextBoxA21.Text); pos += 1;
A[2, 2] = Convert.ToDouble(TextBoxA22.Text); pos += 1;
b[0] = Convert.ToDouble(TextBoxB0.Text); pos += 1;
b[1] = Convert.ToDouble(TextBoxB1.Text); pos += 1;
b[2] = Convert.ToDouble(TextBoxB2.Text); pos += 1;
}
catch (System.Exception)
{
string s = string.Format("Wrong input at {0}", GetPositionName(pos));
ShowErrorMessage(s);
SetFocus(pos);
return;
}
The picture below shows the resulting web control. All input fields were found to be correct and the solution of the equation system was successfully found, so there was no need to display a message.
Points of interest
Because both the Vector3 and Matrix3x3 classes serve mainly as containers for a given number of numerical values, a question arises if it is really necessary to define these objects as classes. Possibly structures may play the same role and be more effective. The main reason why classes were used here is simply that classes are traditional in C#.
In mathematical books, elements of matrices and vectors are indexed from 1. C# uses zero-based indices and for this reason, I also counted the matrix and vector elements from zero. However, indexing from 1 can be easily implemented. See the following modification of the Matrix3x3.this[int i, int j] function:
public double this[int i, int j]
{
get
{
i -= 1;
j -= 1;
if (!((i < 0 || i >= 3) && (j < 0 || j <= 3)))
return A[i,j];
else
return 0;
}
set
{
i = -1;
j = -1;
if (!((i < 0 || i >= 3) && (j < 0 || j <= 3)))
A[i,j] = value;
}
}
It's not difficult to imagine a member variable, probably static, with cases switching between zero- and one-based indices.
Conclusion
I described a method of solving a set of three linear equations with three unknown variables. The main attention was brought to two supporting classes that were able to both keep the equation system and solve it. Despite of the fact that the solution of equations was my primary goal, classes which I created can possibly be useful for other tasks in 3-dimensional space. I built the method into two applications. One of them is based on Windows Forms and produces a regular Windows application. The other application uses Web Forms to create a web control that can be placed on a web page.
History