Está en la página 1de 19

Eliminacin de Gauss-Jordan en C#

Cdigo de la tcnica Eliminacin de Gauss-Jordan para resolver sistemas de


ecuaciones lineales de reales y complejos

Por Harvey Triana, 22 de noviembre de 2006

Introduccin

La Eliminacin de Gauss-Jordan, es una de mis soluciones preferidas, el cdigo lo escrib
inicialmente en FORTRAN en 1992, Visual Basic clsico en 1996, y ahora en C# en 2006. Si
bien la solucin aplicada a nmeros reales es aceptable en Visual Basic, hablo de Visual Basic
clsico, la solucin para nmeros complejos produce un cdigo Visual Basic necesariamente
basado en mtodos aritmticos con nombre, lo que limita las expresiones a una forma difcil
de construir, y difcil de depurar en trminos intuitivamente lgicos. El tratamiento de
nmeros complejos con C, o C#, es la aplicacin clsica de sobrecarga de operadores, en
donde al final, tenemos un cdigo natural y tenazmente equivalente a la solucin con
nmeros reales.

El articulo de Fahad Gilani (ver referencia 4), es una interesante exposicin acerca del
rendimiento de C# sobre la cuestin, y una inspiracin para mi.


Eliminacin de Gauss-Jordan Aplicada a Nmeros Reales

En la matemtica, la eliminacin Gaussiana o eliminacin de Gauss-Jordan, llamada as
debido a Carl Friedrich Gauss y Wilhelm Jordan, es un algoritmo del lgebra lineal para
determinar las soluciones de un sistema de ecuaciones lineales, y encontrar matrices e
inversas. El algoritmo lo puede consultar de las referencias 1 y 2. Aqu les presento la loable
versin en C#.

Cre un programa de consola, Article_GJE, y agregu una clase de nombre
LinearEquationsSolver.cs. El cdigo de la solucin por eliminacin de Gauss-Jordan para
nmeros reales es como sigue:


Eliminacin de Gauss-Jordan para nmeros reales
using System;

namespace Article_GJE
{
class LinearEquationsSolver
{
/// <summary>
/// GaussianElimination()
/// Gaussian elimination is a method for solving matrix
equations
/// By Harvey Triana
/// </summary>
/// <param name="a"> The matrix</param>
/// <param name="r"> The solution array</param>
/// <returns>Success function</returns>
public static bool GaussianElimination(double[,] a, double[]
r)
{
double t, s;
int i, l, j, k, m, n;

try
{
n = r.Length - 1;
m = n + 1;
for (l = 0; l <= n - 1; l++)
{
j = l;
for (k = l + 1; k <= n; k++)
{
if (!(Math.Abs(a[j, l]) >= Math.Abs(a[k, l])))
j = k;
}
if (!(j == l))
{
for (i = 0; i <= m; i++)
{
t = a[l, i];
a[l, i] = a[j, i];
a[j, i] = t;
}
}
for (j = l + 1; j <= n; j++)
{
t = (a[j, l] / a[l, l]);
for (i = 0; i <= m; i++) a[j, i] -= t * a[l,
i];
}
}
r[n] = a[n, m] / a[n, n];
for (i = 0; i <= n - 1; i++)
{
j = n - i - 1;
s = 0;
for (l = 0; l <= i; l++)
{
k = j + l + 1;
s += a[j, k] * r[k];
}
r[j] = ((a[j, m] - s) / a[j, j]);
}
return true;
}
catch
{
return false;
}
}
}
}

La funcin GaussianElimination toma como parmetros la matriz, que es el sistema de
ecuaciones lineales, y un arreglo, en el cual se almacenar la posible solucin. La funcin
retorna el bolean true si el sistema de ecuaciones tiene solucin y fue calculado. La funcin
GaussianElimination es dinmica en cuanto al tamao de la matriz, lo que la hace muy
potente. La dimensin del arreglo que contiene la solucin debe ser igual al nmero de
variables del sistema de ecuaciones.

Para probar la funcin, dispuse un ejemplo, en la clase Program (predeterminada de la
aplicacin de consola), como sigue:

Un ejemplo de uso de la funcion GaussianElimination para numeros reales
using System;

namespace Article_GJE
{
class Program
{
static void Main(string[] args)
{
Sample1();
}

static void Sample1()
{
/* Solucionar el siguiente sistema de ecuaciones lineales
x + y + z = 6 | 1 1 1 6 |
x + z = 4 | 1 0 1 4 |
x + y = 3 | 1 1 0 3 | */

double[,] a = { { 1, 1, 1, 6 },
{ 1, 0, 1, 4 },
{ 1, 1, 0, 3 } };
double [] r = new double[3];

ShowMatrix(a, "Ejemplo 1");
if (LinearEquationsSolver.GaussianElimination(a, r))
ShowSolution(r);
else
Console.WriteLine("No es un sistema de ecuaciones
lineales");
}

#region formated output
static void ShowMatrix(double[,] a, string Title)
{
Console.WriteLine(Title + '\n');
for (int i = 0; i <= a.GetUpperBound(0); i++)
{
Console.Write('|');
for (int j = 0; j <= a.GetUpperBound(1); j++)
{
Console.Write(ToStringSign(a[i, j]));
}
Console.Write(" | \n");
}
Console.WriteLine('\n');
}
static void ShowSolution(double[] r)
{
Console.WriteLine("Solucin por Eliminacin Gaussiana");
for (int i = 0; i <= r.GetUpperBound(0); i++)
{
Console.WriteLine(ToStringSign(r[i]));
}
Console.WriteLine("\n");
}
static private string ToStringSign(double v)
{
if (v < 0) return ' ' + v.ToString(); else return " " +
v.ToString();
}
#endregion
}
}

Note que la asignacin de la matriz con sintaxis de C# parece muy natural.

La salida del programa es la siguiente, x = 1, y = 2, z = 3:


Nmeros Complejos

La solucin de un sistema de ecuaciones lineales de nmeros complejos es compleja, valga
la redundancia. El trabajo algebraico es bastante laborioso por el gran nmero de
operaciones matemticas que requiere. El sistema de ecuaciones ms pequeo, que es de
dos por dos, con calculadora en mano, puede tomarnos mucho tiempo, y estar sujeto a
errores humanos, imagnese uno grande.

La programacin de la matemtica de nmeros complejos hace natural con la sobrecarga de
operadores, lo cual es un mecanismo clsico del lenguaje C. Como alternativa a usar
sobrecarga de operadores podemos usar mtodos para cada operacin, pero las lneas de
cdigo de las operaciones resultan muy confusas.

Bien, lo primero que se debe hacer es escribir una clase ComplexNumber. Hay muchas
versiones en la red de tal clase, y todas a puntan a lo mismo, sobrecarga de operadores. La
clase Articles_CGE.ComplexNumber es mi versin. Noten que use mtodos de propiedad en
vez de variables pblicas. A mi parecer da un cdigo ms limpio.

La clase ComplexNumber
/*
* ComplexNumber Class
* By Harvey Triana
*/
namespace Article_GJE
{
using System;

public class ComplexNumber
{
// members
private double m_Real;
private double m_Imaginary;

// constructor
public ComplexNumber()
{
m_Real = 0;
m_Imaginary = 0;
}
public ComplexNumber(double Real, double Imaginary)
{
m_Real = Real;
m_Imaginary = Imaginary;
}

// properties
public double Real
{
get { return m_Real; }
set { m_Real = value; }
}
public double Imaginary
{
get { return m_Imaginary; }
set { m_Imaginary = value; }
}

// Equal method
public bool Equals(ComplexNumber a)
{
return (m_Real == a.Real && m_Imaginary == a.Imaginary);
}
// Let method
public void Let(ComplexNumber a)
{
m_Real = a.Real;
m_Imaginary = a.Imaginary;
}
// Absolute value of a complex number
public double Abs()
{
return (Math.Sqrt(m_Real * m_Real + m_Imaginary *
m_Imaginary));
}
// Argument of a complex number in radians
public double Arg()
{
double r = 0;
if (m_Real != 0) r = Math.Atan(m_Imaginary / m_Real);
return (r);
}
// Argument of a complex number in degree
public double ArgDeg()
{
return (180 / Math.PI) * this.Arg();
}
// overridden ToString to return format: a + bi
public override string ToString()
{
string r;
if (m_Real >= 0)
r = ' ' + m_Real.ToString();
else
r = m_Real.ToString();
if (m_Imaginary >= 0)
r += " + " + ImaginaryToString(m_Imaginary);
else
r += " - " + ImaginaryToString(-m_Imaginary);
return r + "i";
}
string ImaginaryToString(double v)
{
if (v == 1) return ""; else return v.ToString();
}
// ToString Gaussian to return format: (a, b)
public string ToStringGaussian()
{
return string.Format("({0}, {1})",
m_Real.ToString(), m_Imaginary.ToString());
}

#region OVERLOAD OPERATORS
// overloaded binary + operator
public static ComplexNumber operator +(ComplexNumber a, ComplexNumber
b)
{
return C(a.Real + b.Real, a.Imaginary + b.Imaginary);
}
// overloaded unary - operator
public static ComplexNumber operator -(ComplexNumber a)
{
return C(-a.Real, -a.Imaginary);
}
// overloaded binary - operator
public static ComplexNumber operator -(ComplexNumber a, ComplexNumber
b)
{
return C(a.Real - b.Real, a.Imaginary - b.Imaginary);
}
// overloaded binary * operator
public static ComplexNumber operator *(ComplexNumber a, ComplexNumber
b)
{
return C(a.Real * b.Real - a.Imaginary * b.Imaginary,
a.Real * b.Imaginary + b.Real * a.Imaginary);
}
// overloaded binary / operator
public static ComplexNumber operator /(ComplexNumber a, ComplexNumber
b)
{
double c1, c2, d;
d = b.Real * b.Real + b.Imaginary * b.Imaginary;
if (d == 0)
{
return C(0, 0);
}
else
{
c1 = a.Real * b.Real + a.Imaginary * b.Imaginary;
c2 = a.Imaginary * b.Real - a.Real * b.Imaginary;
return C(c1 / d, c2 / d);
}
}
#endregion

//shortcut to return new ComplexNumber ...
static ComplexNumber C(double r, double i) {return new
ComplexNumber(r, i);}

}
}

La matemtica de complejos es bastante ms amplia, otras operaciones y funciones
transcendentales puedes ser agregadas a la clase.

Note que escrib el mtodo Let, el cual se usa para asignar un nmero complejo a otro, ya
que el operador = no se puede sobrecargar.


Eliminacin de Gauss-Jordan Aplicada a Matrices de Complejos

Sobrecargando, y ajustando en algo el cdigo de GaussianElimination, tengo la portentosa
solucin de ecuaciones lineales para nmeros complejos. He aqu el resultado, el cual se
agrega a la clase LinearEquationsSolver.cs.


Eliminacin Gauss-Jordan para nmeros complejos
/// <summary>
/// GaussianElimination()
/// Gaussian elimination is a method for solving matrix equations
/// By Harvey Triana
/// </summary>
/// <param name="a"> The matrix</param>
/// <param name="r"> The solution array</param>
/// <returns>Success function</returns>

public static bool GaussianElimination(ComplexNumber[,] a,
ComplexNumber[] r)
{
ComplexNumber t = new ComplexNumber();
ComplexNumber s = new ComplexNumber();
int i, l, j, k, m, n;

try
{
n = r.Length - 1;
m = n + 1;
for (l = 0; l <= n - 1; l++)
{
j = l;
for (k = l + 1; k <= n; k++)
{
if (!(a[j, l].Abs() >= a[k, l].Abs())) j = k;
}
if (!(j == l))
{
for (i = 0; i <= m; i++)
{
t.Let(a[l, i]);
a[l, i].Let(a[j, i]);
a[j, i].Let(t);
}
}
for (j = l + 1; j <= n; j++)
{
t = (a[j, l] / a[l, l]);
for (i = 0; i <= m; i++) a[j, i] -= t * a[l, i];
}
}
r[n] = a[n, m] / a[n, n];
for (i = 0; i <= n - 1; i++)
{
j = n - i - 1;
s.Real = 0;
s.Imaginary = 0;

for (l = 0; l <= i; l++)
{
k = j + l + 1;
s += a[j, k] * r[k];
}
r[j] = ((a[j, m] - s) / a[j, j]);
}
return true;
}
catch
{
return false;
}
}

En que se diferencian? Note que la magnitud de un nmero complejo se mide con la funcin
Absoluto para nmero complejo. Las asignaciones se hacen por el mtodo Let, y de resto lo
hacemos naturalmente gracias a la sobrecarga de operadores; quedando oculta la
complejidad de las operaciones matemticas.

El ejemplo de uso es similar al anterior. La salida formateada ahora cambia solo en los
parmetros.

Ejemplo de la Eliminacin Gauss-Jordan para nmeros complejos
using System;

namespace Article_GJE
{
class Program
{
static void Main(string[] args)
{
Sample2();
}

static void Sample2()
{
/* Solucionar el siguiente sistema de ecuaciones lineales
(2+i)x - (i+1)y = 7i | (2,1) (-1,-1) (0, 7) |
(1+i)x + (2+3i)y = -2i | (1,1) ( 2, 3) (0,-2) | */

ComplexNumber[,] a = {{C(2, 1), C(-1, -1), C(0, 7)},
{C(1, 1), C( 2, 3), C(0, -2)}};
ComplexNumber[] r = new ComplexNumber[2];

ShowMatrix(a, "Ejemplo 2");
if (LinearEquationsSolver.GaussianElimination(a, r))
ShowSolution(r);
else
Console.WriteLine("No es un sistema de ecuaciones
lineales");
}
static ComplexNumber C(double Real,double Imaginary)
{
return new ComplexNumber(Real, Imaginary);
}

#region formated output
static void ShowMatrix(ComplexNumber[,] a, string Title)
{
Console.WriteLine(Title + '\n');
for (int i = 0; i <= a.GetUpperBound(0); i++)
{
Console.Write('|');
for (int j = 0; j <= a.GetUpperBound(1); j++)
{
Console.Write(a[i, j].ToStringGaussian() + ' ');
}
Console.Write("| \n");
}
Console.WriteLine("\n");
}
static void ShowSolution(ComplexNumber[] r)
{
Console.WriteLine("Solucin por Eliminacin Gaussiana");
for (int i = 0; i <= r.GetUpperBound(0); i++)
{
Console.WriteLine(r[i].ToStringGaussian());
}
Console.WriteLine("\n");
}
#endregion
}
}

Un detalle para resaltar en este ejemplo es la asignacin de la matriz, la cual se hizo con la
funcin C, la cual retorna una variable de la clase ComplexNumber. Esto me permite escribir
una asignacin de variable ms leble, y simple. Es decir:

Modo largo de definir la matriz de complejos
ComplexNumber[,] a =
{{new ComplexNumber(2, 1), new ComplexNumber(-1, -1), new
ComplexNumber(0, 7)},
{new ComplexNumber(1, 1), new ComplexNumber(2, 3), new
ComplexNumber(0, -2)}};
Modo corto de definir la matriz de complejos
ComplexNumber[,] a = {{C(2, 1), C(-1, -1), C(0, 7)},
{C(1, 1), C( 2, 3), C(0, -2)}};


Pasar por Valor la Matriz

Bien, si estudio el cdigo sabr que las matrices, tanto en el caso de nmeros reales como
complejos, se pasan por referencia. El mtodo de Gauss-Jordan normalmente hace una
transformacin de la matriz durante la solucin. Si quisiramos hacer alguna operacin
posterior con la matriz, por ejemplo comprobar la solucin, no seria valido. Por ejemplo, la
siguiente rutina la use para comprobar la validez de la funcin GaussianElimination aplicada
a nmeros complejos


Rutina que pueba la solucion Gauss-Jordan para nmeros complejos
static void HardTest(ComplexNumber[,] a, ComplexNumber[] r)
{
ComplexNumber s = new ComplexNumber();

Console.WriteLine("Prueba de la solucin");
for (int i = 0; i <= r.GetUpperBound(0); i++)
{
s.Real = 0;
s.Imaginary = 0;
for (int j = 0; j <= r.GetUpperBound(0); j++) s += a[i, j] *
r[j];
Console.Write("Ecuacin {0}: {1} \n", i.ToString(),
s.ToString());
}

Por supuesto la funcin HardTest debe llamarse despus de la solucin. Para que HardTest
retorne correctamente los valores, la matriz tiene que usar sus valores originales, o
alternativamente ser pasada por valor. Para pasar una matriz por valor, C# entrega una
elegante solucin, el mtodo Clone, el cual aplicado al caso es as:


Pasar una matriz por valor usando el mtodo Clone
if (LinearEquationsSolver.GaussianElimination((ComplexNumber[,])
a.Clone(), r))
{
ShowSolution(r);
HardTest(a, r);
}