<- previous    index    next ->

Lecture 3 Solving Simultaneous Equations


Simultaneous equations are multiple equations involving the same
variables. In general, to get a unique solution we need the
same number of equations as the number of unknown variables,
and the equations mutually linearly independent.

A sample set of three equations in three unknowns is:

  eq1:  2*x + 3*y + 2*z = 13 
  eq2:  3*x + 2*y + 3*z = 17
  eq3:  4*x - 2*y + 2*z = 12
  
One systematic solution method is called the Gauss-Jordan reduction.
We will reduce the three equations such that eq1: has only x,
eq2: has only y and eq3: has only z making the constant yield
the solution. Operations are always based on the latest version
of each equation. The numeric solution will perform the same
operations on a matrix.

  Reduce the coefficient of x in the first equation to 1, dividing by 2

    eq1:  1*x + 1.5*y + 1*z = 6.5 

  Eliminate the variable x from eq2: by subtracting eq2 coefficient of
  x times eq1:

    eq2:  becomes   eq2: - 3* eq1:
  
    eq2:  (3-3)*x + (2-4.5)*y + (3-3)*z = (17-19.5)  then simplifying
    eq2:      0*x - 2.5*y + 0*z = -2.5
  
  Eliminate the variable x from eq3: by subtracting eq3 coefficient of
  x times eq1:

    eq3:  becomes   eq3: - 4* eq1:
  
    eq3:  (4-4)*x (-2 -6)*y + (2-4)*z = (12-26) then becomes
    eq3:      0*x - 8*y - 2*z = -14 

The three equations are now

  eq1:  1*x + 1.5*y + 1*z =   6.5 
  eq2:  0*x - 2.5*y + 0*z = - 2.5
  eq3:  0*x - 8.0*y - 2*z = -14.0
  
  Reduce the coefficient of y in eq2: to 1, dividing by -2.5

    eq2:  0*x + 1*y + 0*z = 1 
  
  Eliminate the variable y from eq1: by subtracting eq1 coefficient of y times eq2:

    eq1:  becomes  eq1: -1.5* eq2:
  
    eq1:  1*x + 0*y + 1*z =  (6.5-1.5)  then becomes
    eq1:  1*x + 0*y + 1*z = 5
  
  Eliminate the variable y from eq3: by subtracting eq3 coefficient of y times eq2:

    eq3:  becomes  eq3: -8* eq2:
  
    eq3:  0*x + 0*y - 2*z =  (-14 +8)  then becomes
    eq3:  0*x + 0*y - 2*z = -6
  
The three equations are now

  eq1:  1*x + 0*y + 1*z =  5 
  eq2:  0*x + 1*y + 0*z =  1
  eq3:  0*x + 0*y - 2*z = -6

  Reduce the coefficient of z in eq3: to 1, dividing by -2

    eq3:  0*x + 0*y + 1*z = 3 
  
  Eliminate the variable z from eq1: by subtracting eq1 coefficient of z times eq2:

    eq1:  becomes  eq1: -1* eq2:
  
    eq1:  1*x + 0*y + 0*z =  (5-3)  then becomes
    eq1:  1*x + 0*y + 0*z = 2
  
  Eliminate the variable z from eq2: by subtracting eq2 coefficient of z times eq2:

    eq2:  becomes  eq3: 0* eq2:
  
    eq2:  0*x + 1*y + 0*z = 1

The three equations are now

  eq1:  1*x + 0*y + 0*z = 2   or  x = 2
  eq2:  0*x + 1*y + 0*z = 1   or  y = 1
  eq3:  0*x + 0*y + 1*z = 3   or  z = 3  the desired solution.


The numerical solution simply places the values in a matrix and uses the same
reductions shown above.

Given the equations:                        |A|*|X| = |Y|    B = |AY|

  eq1:  2*x + 3*y + 2*z = 13         | 2  3  2 | |x| |13|
  eq2:  3*x + 2*y + 3*z = 17         | 3  2  3 |*|y|=|17|
  eq3:  4*x - 2*y + 2*z = 12         | 4 -2  2 | |z| |12|

Create the matrix  | 2  3  2  13 |  having 3 rows and 4 columns
               B = | 3  2  3  17 |
                   | 4 -2  2  12 |
                   
The following code, using n=3 for these three equations, computes the
same desired solution as the manual method above.

  for k=1:n
    for j=k+1:n
      B(k,j) = B(k,j)/B(k,k)
    end
    for i=1:n
      if(i not k)
        for j=1:n
          B(i,j) = B(i,j) - B(i,k)*B(k,J)

Pivoting to avoid zero on diagonal and improve accuracy

Now we must consider the possible problem of B(k,k) being zero for some value of k. It is rather obvious that the order of the equations does not matter. The equations can be given in any order and we get the same solution. Thus, simply interchange any equation where we are about to divide by a zero B(k,k) with some equation below that would not result in a zero B(k,k). It turns out that we get better accuracy if we always pick the equation that has the largest absolute value for B(k,k). If the largest value turns out to be zero then there is no unique solution for the set of equations. We generally want numerical code to run efficiently and thus we will not physically interchange the equations but rather keep a row index array that tells us where the k th row is now. The code for the final algorithm is given in the links below.

Note types of errors

test_simeq_small.c source code test_simeq_small_c.out output test_simeq_small.c results should be exactly X[0]=1.0, X[1]=-1.0 err is the error multiplying the solution, X, times matrix A*X=Y a small change in data gives a big change in solution. the big change is not indicated by the computed error. decimal .835*X[0] + .667*X[1] = .168 Y[0] decimal .333*X[0] + .266*X[1] = .067 Y[1] flt-pt 0.835000*X[0] + 0.667000*X[1] = 0.168000 flt-pt 0.333000*X[0] + 0.266000*X[1] = 0.067000 initialization complete, solving solution X[0]=1.0, err=0 solution X[1]=-1.0, err=0 -- expected now perturb Y[1] from .067 to .066, .001 change, and run again flt-pt 0.835000*X[0] + 0.667000*X[1] = 0.168000 Y[0] flt-pt 0.333000*X[0] + 0.266000*X[1] = 0.066000 Y[1] -- only change solution X[0]=-666.0, err=0 solution X[1]=834.0, err=2.84217e-14 -- X[1] WOW! notice computed errors are still small, yet, the values of X[0] and X[1] are very different. Typical problem with people and computers: Often called garbage in, garbage out! test_inverse_small.c source code test_inverse_small_c.out output We will see later, when the norm of the coefficients of simultaneous equations is inverted, and the norm of the inverse is different by many orders of magnitude, the system will be called ill-conditioned.

solving for function values on a grid

Solving for unknown function values on a given x,y grid: test_eqn1 2*U(x,y) = C(x,y) test_eqn2 2*U(x,y) + 3*V(x,y) = C(x,y) 3*U(x,y) + 3*V(x,y) = D(x,y) test eqn3 2*U(x,y) + 3*V(x,y) + 4*W(x,y) = C(x,y) 4*U(x,y) + 2*V(x,y) + 3*W(x,y) = D(x,y) 3*U(x,y) + 4*V(x,y) + 2*W(x,y) = E(x,y) test_eqn1.java source code test_eqn1_java.out source code test_eqn2.java source code test_eqn2_java.out source code test_eqn3.java source code test_eqn3_java.out source code test_eqn33.java source code test_eqn33_java.out source code test_eqn1.py3 source code test_eqn1_py3.out source code test_eqn2.py3 source code test_eqn2_py3.out source code test_eqn3.py3 source code test_eqn3_py3.out source code test_eqn33.py3 source code test_eqn33_py3.out source code Later lecture covers non linear equations.

Working code in many languages

The instructor understands that some students have a strong prejudice in favor of, or against, some programming languages. After about 50 years of programming in about 50 programming languages, the instructor finds that the difference between programming languages is mostly syntactic sugar. Yet, since students may be able to read some programming languages easier than others, these examples are presented in "C", Fortran 95, Java and Ada 95. The intent was to do a quick translation, keeping most of the source code the same, for the different languages. Style was not a consideration. Some rearranging of the order was used when convenient. The numerical results are almost exactly the same. The same code has been programmed in "C", Fortran 95, Java and Ada 95 etc. as shown below with file types .c, .f90, .java and .adb .rb .py .scala: Note the .h for C offers the user choices. simeq.c "C" language source code simeq.h "C" header file time_simeq.c "C" language source code time_simeq.out output simeq.f90 Fortran 95 source code time_simeq.f90 Fortran 95 source code time_simeq_f90_cs.out output simeq.java Java source code time_simeq.java Java source code time_simeq_java.out output simeq.cpp C++ language source code simeq.hpp C++ header file test_simeq.cpp C++ language source code test_simeq_cpp.out output simeq.adb Ada 95 source code real_arrays.ads Ada 95 source code real_arrays.adb Ada 95 source code Simeq.rb Ruby class Simeq test_simeq.rb Ruby source code test_simeq_rb.out Ruby output simeq in matrix.pm Perl package test_matrix.pl Perl source code test test_matrix_pl.out Perl output simeq.m MATLAB source code simeq_m.out MATLAB output With Python and downloading numpy using linalg test_solve.py Python source code test_solve_py.out Python output With Python using numpy and array test_simeq.py Python source code test_simeq_py.out Python output test_simeq.py3 Python source code test_simeq_py3.out Python output test_matmul.py3 Python source code test_matmul_py3.out Python output With Scala using Random, very small errors Simeq.scala Scala source code TestSimeq.scala Scala source code TestSimeq_scala.out Scala output Many methods have been devised for solving simultaneous equations. A sample is LU decomposition and Crout method. LU decomposition with pivoting: lup_decomp.f90 source code lup_decomp_f90.out output simeq_lup.h source code simeq_lup.c source code time_simeq_lup.c source code time_simeq_lup_cs.out output simeq_lup.java source code time_simeq_lup.java source code time_simeq_lup_java.out output Crout method without pivoting, uses back substitution: crout.adb source code crout_ada.out output crout.f90 source code time_crout.f90 source code time_crout_f90.out output crout.h source code crout.c source code time_crout.c source code time_crout.c source code time_crout_c.out output crout1.h source code crout1.c source code time_crout1.c source code time_crout1_c.out output crout.java source code test_crout.java accuracy test source test_crout_java.out output crout1.java source code time_crout.java accuracy test source time_crout_java.out output Back substitution: The difference from plain Gauss-Jordan with pivoting and back substitution is less inner reduction, then use back substitution. Solve A * X = Y The initial reduction creates a matrix of the form: | 1 a12 a13 a14 y1 | | 0 1 a23 a24 y2 | | 0 0 1 a34 y3 | | 0 0 0 1 y4 | Thus we have x4 = y4 Back substituting x3 = y3 - x4*a34 Back substituting x2 = y2 - x3*a23 - x4*a24 Back substituting x1 = y1 - x2*a12 - x3*a13 - x4*a14 simeq_back.h source code simeq_back.c source code test_simeq_back.c source code test_simeq_back.out output many methods in simeq.pdf

Tailoring

Throughout this course you will see variations of this source code, tailored for specific applications. The packaging will change with "C" files having code inside with 'static void', Fortran 95 code using modules and, Java and Ada code using packages. Python etc. code. It should be noted that the algorithm is exactly the same for sets of equations with complex values. The code change is simply changing the type in Fortran 95, Java, and Ada 95. The Java class 'Complex' is on my WEB page. The "C" code requires a lot of changes. I wrote the first version of this program for the IBM 650 in assembly language as an electrical engineering student. The program was for complex values and solved for node voltages in alternating current circuits. A quick and dirty version is ac_circuit.java that needs a number of java packages: Matrix.java Complex.java ComplexMatrix.java ac_analysis.java an improved version Then an even more complete version that plots up to eight node voltages. ac_plot.java simple Java plot added ac_plot.dat capacitor, inductor and tuned circuits Output of java myjava.ac_plot.java < ac_plot.dat There are systems of equations with no solutions: eq1: 1*x + 0*y + 0*z = 2 eq2: 2*x + 0*y + 0*z = 2 eq3: 4*x - 2*y + 3*z = 5 Some may ask: What about solving |A| * |X| = |Y| for X, given A and Y using |X| = |Y| * |A|^-1 (inverse of matrix A) ? The reason this is not a good numerical solution is that slightly more total error will be in the inverse |A|^-1 and then a little more error will come from the vector times matrix multiplication.

Matrix Inverse

The code for matrix inverse is very similar to the code for solving simultaneous equations. Added effort is needed to find the maximum pivot element and there must be both row and column interchanges. An example that shows the increasing error with the increasing size of the matrix, on a difficult matrix, is shown below. Note that results of a 16 by 16 matrix using 64-bit IEEE Floating point arithmetic that is ill conditioned may become useless. inverse.f90 test_inverse.f90 test_inverse_f90.out Extracted form test_inverse_f90.out is initializing big matrix, n= 2 , n*n= 4 sum of error= 1.84748050191530E-16 , avg error= 4.61870125478825E-17 initializing big matrix, n= 4 , n*n= 16 sum of error= 2.19971263426544E-12 , avg error= 1.37482039641590E-13 initializing big matrix, n= 8 , n*n= 64 sum of error= 0.00000604139304982709 , avg error= 9.43967664035483E-8 initializing big matrix, n= 16 , n*n= 256 sum of error= 83.9630735209012 , avg error= 0.327980755941020 initializing big matrix, n= 32 , n*n= 1024 sum of error= 4079.56590417946 , avg error= 3.98395107830025 initializing big matrix, n= 64 , n*n= 4096 sum of error= 53735.8765782488 , avg error= 13.1191104927365 initializing big matrix, n= 128 , n*n= 16384 sum of error= 85784.2643647822 , avg error= 5.23585597929579 initializing big matrix, n= 256 , n*n= 65536 sum of error= 1097119.16168229 , avg error= 16.7407098645368 initializing big matrix, n= 512 , n*n= 262144 sum of error= 1.36281435213093E+7 , avg error= 51.9872418262837 initializing big matrix, n= 1024 , n*n= 1048576 sum of error= 1.24247404738082E+9 , avg error= 1184.91558778841 Very similar results from the C version: inverse.c test_inverse.c test_inverse.out inverse.py test_inverse.py test_inverse_py.out test_inv.py numpy version test_inv_py.out Extracted form test_inverse.out is initializing big matrix, n=1024, n*n=1048576 sum of error=1.24247e+09, avg error=1184.92 Inverse.rb Ruby class Inverse test_inverse.rb Ruby test test_inverse_rb.out Ruby output

Multiple Precision

A case study using 32-bit IEEE floating point and 50, 100, and 200 digit multiple precision are shown in Lecture 3a

Reducing the number of equation when some values are known

Later, when we study partial differential equations, we will need cs455_l3c.shtml a process for reducing the number of equations when we know the value of one or more elements of the unknown vector.

Nonlinear equations and systems of nonlinear equations are covered in Lecture 16

Really difficult, are systems of nonlinear equations that need a solution. The following examples have many comments describing one or more possible methods of solution: (Later versions have fewer bugs) simeq_newton.adb with debug printout simeq_newton_ada.out simeq_newton2.adb simeq_newton2_ada.out simeq_newton5.adb test_simeq_newton5.adb test_simeq_newton5_ada.out real_arrays.ads used by above real_arrays.adb used by above inverse.adb used by above equation_nl.adb with debug printout equation_nl_ada.out simeq_newton.f90 with debug printout simeq_newton_f90.out simeq_newton2.f90 simeq_newton2_f90.out inverse.f90 used by above udrnrt.f90 used by above equation_nl.f90 with debug printout equation_nl_f90.out simeq_newton.c with debug printout simeq_newton.out simeq_newton2.c simeq_newton2.out simeq_newton3.h simeq_newton3.c test_simeq_newton3.c test_simeq_newton3_c.out simeq_newton4.h simeq_newton4.c test_simeq_newton4.c test_simeq_newton4_c.out invert.h used by above invert.c used by above udrnrt.h used by above udrnrt.c used by above equation_nl.c with debug printout equation_nl_c.out simeq_newton.java with debug printout simeq_newton2.java test_simeq_newton2.java test_simeq_newton2_java.out simeq_newton3.java test_simeq_newton3.java test_simeq_newton3.java simeq_newton5.java test_simeq_newton5.java test_simeq_newton5.java test_pde_nl.java test_pde_nl_java.out test_46_nl.java simple demo test_46_nl_java.out simple demo simeq_newton5.py3 test_pde_nl.py3 simple test test_pde_nl_py3.out simple test simeq_newton5.java test_simeq_newton5.java test_simeq_newton5_java.out invert.java used by above equation_nl.java with debug printout equation_nl_java.out inverse.java used by above Accuracy does degrade as the relative size of solution and matrix elements gets large. Expect similar results with any method. This program tests 0, 1, 2, 5, ... 1E9, 2E9, 5E9, 1E10 for various n. simeq_accuracy.c big range test simeq_accuracy_c.out big range results Accuracy of all methods degrades with size of matrix on random data. Not much error for 1024 by 1024 matrix. Errors increase at 2048 by 2048 and 4096 by 4096. Over 10K by 10K starts to need multiple precision floating point, see next lectures. For your information, modern manufacturing of automobiles: www.youtube.com/embed/8_lfxPI5ObM?rel=0
    <- previous    index    next ->

Other links

Go to top