Eliminación de Gauss-Jordan en C#

 

Código de la técnica Eliminación de Gauss-Jordan para resolver sistemas de ecuaciones lineales de reales y complejos

 

Por Harvey Triana, 22 de noviembre de 2006

 

Introducción

 

La “Eliminación de Gauss-Jordan”, es una de mis soluciones preferidas, el código lo escribí inicialmente en FORTRAN en 1992, Visual Basic clásico en 1996, y ahora en C# en  2006. Si bien la solución aplicada a números reales es aceptable en Visual Basic, hablo de Visual Basic clásico, la solución para números complejos produce un código Visual Basic necesariamente basado en métodos aritméticos con nombre, lo que limita las expresiones a una forma difícil de construir, y difícil de depurar en términos intuitivamente lógicos. El tratamiento de números complejos con C, o C#, es la aplicación clásica de sobrecarga de operadores, en donde al final, tenemos un código natural y tenazmente equivalente a la solución con números reales.

 

El articulo de Fahad Gilani (ver referencia 4), es una interesante exposición acerca del rendimiento de C# sobre la cuestión, y una inspiración para mi.

 

 

Eliminación de Gauss-Jordan Aplicada a Números Reales

 

En la matemática, la eliminación Gaussiana o eliminación 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 versión en C#.

 

Creé un programa de consola, Article_GJE, y agregué una clase de nombre LinearEquationsSolver.cs. El código de la solución por eliminación de Gauss-Jordan para números reales es como sigue:

 

 

Eliminación de Gauss-Jordan para números 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 función GaussianElimination toma como parámetros la matriz, que es el sistema de ecuaciones lineales, y un arreglo, en el cual se almacenará la posible solución. La función retorna el bolean “true” si el sistema de ecuaciones tiene solución y fue calculado. La función GaussianElimination es dinámica en cuanto al tamaño de la matriz, lo que la hace muy potente. La dimensión del arreglo que contiene la solución debe ser igual al número de variables del sistema de ecuaciones.

 

Para probar la función, dispuse un ejemplo, en la clase Program (predeterminada de la aplicación 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("Solución por Eliminación 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 asignación de la matriz con sintaxis de C# parece muy natural.

 

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

 

 

Números Complejos

 

La solución de un sistema de ecuaciones lineales de números complejos es compleja, valga la redundancia. El trabajo algebraico es bastante laborioso por el gran número de operaciones matemáticas que requiere. El sistema de ecuaciones más pequeño, que es de dos por dos, con calculadora en mano, puede tomarnos mucho tiempo, y estar sujeto a errores humanos, imagínese uno grande.

 

La programación de la matemática de números complejos hace natural con la sobrecarga de operadores, lo cual es un mecanismo clásico del lenguaje C. Como alternativa a usar sobrecarga de operadores podemos usar métodos para cada operación, pero las líneas de código 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 versión. Noten que use métodos de propiedad en vez de variables públicas. A mi parecer da un código más 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 matemática de complejos es bastante más amplia, otras operaciones y funciones transcendentales puedes ser agregadas a la clase.

 

Note que escribí el método Let, el cual se usa para asignar un número complejo a otro, ya que el operador “=” no se puede sobrecargar.

 

 

 Eliminación de Gauss-Jordan Aplicada a Matrices de Complejos

 

Sobrecargando, y ajustando en algo el código de GaussianElimination, tengo la portentosa solución de ecuaciones lineales para números complejos. He aquí el resultado, el cual se agrega a la clase LinearEquationsSolver.cs.

 

 

Eliminación Gauss-Jordan para números 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 número complejo se mide con la función Absoluto para número complejo. Las asignaciones se hacen por el método Let, y de resto lo hacemos naturalmente gracias a la sobrecarga de operadores; quedando oculta la complejidad de las operaciones matemáticas.

 

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

 

Ejemplo de la Eliminación Gauss-Jordan para números 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("Solución por Eliminación 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 asignación de la matriz, la cual se hizo con la función C, la cual retorna una variable de la clase ComplexNumber. Esto me permite escribir una asignación de variable más leíble, 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 código sabrá que las matrices, tanto en el caso de números reales como complejos, se pasan por referencia. El método de Gauss-Jordan normalmente hace una transformación de la matriz durante la solución. Si quisiéramos hacer alguna operación posterior con la matriz, por ejemplo comprobar la solución, no seria valido. Por ejemplo, la siguiente rutina la use para comprobar la validez de la función GaussianElimination aplicada a números complejos

 

 

Rutina que pueba la solucion Gauss-Jordan para números complejos

static void HardTest(ComplexNumber[,] a, ComplexNumber[] r)

{

    ComplexNumber s = new ComplexNumber();

 

    Console.WriteLine("Prueba de la solución");

    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("Ecuación {0}: {1} \n", i.ToString(), s.ToString());

    }

 

Por supuesto la función HardTest debe llamarse después de la solución. 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 solución, el método Clone, el cual aplicado al caso es así:

 

 

Pasar una matriz por valor usando el método Clone

if (LinearEquationsSolver.GaussianElimination((ComplexNumber[,]) a.Clone(), r))

{

    ShowSolution(r);

    HardTest(a, r);

}

 

 

Conclusiones

 

Este articulo pone de manifiesto las potentes capacidades de C# para escribir aplicaciones que conciernen a problemas generales de matemática aplicada e ingeniería.

 

 

Si usted tiene una sugerencia para mejorar el código, por favor envíeme un e-mail.

 

 

Referencias

 

1.      Gaussian Elimination, http://mathworld.wolfram.com/GaussianElimination.html

2.      Eliminación de Gauss-Jordan, http://es.wikipedia.org/wiki/Eliminaci%C3%B3n_de_Gauss-Jordan

3.      Solución de Ecuaciones Lineales Simultaneas, http://vexpert.mvps.org/articles/GJE_VB6.htm

4.      Harness the Features of C# to Power Your Scientific Computing Projects, http://msdn.microsoft.com/msdnmag/issues/04/03/ScientificC/

 

-----

 

Derechos Reservados. Este artículo no debe ser publicado en ningún medio publicitario o sitio de Internet sin previa autorización del autor.