3.7 Método de Gauss-Seidel con Python

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


Referencia: Chapra 11.2 p310, Burden 7.3 p337, Rodríguez 5.2 p162

El método de Gauss-Seidel, reescribe las expresiones del sistema de ecuaciones, despejando la incógnita de la diagonal en cada ecuación. Usa el vector inicial X0, actualizando el vector X por cada nuevo valor calculado del vector. La diferencia principal con el método de Jacobi es la actualización de X en cada cálculo, por lo que las iteraciones llegan más rápido al punto cuando el método converge.

Gauss-Seidel iteraciones animado

Las iteraciones pueden ser o no, convergentes.

La expresión para encontrar nuevos valores usando la matriz de coeficientes A, el vector de constantes B y como incógnitas X es la misma que Jacobi:

x_i = \bigg(b_i - \sum_{j=0, j\neq i}^n a_{i,j}x_j\bigg) \frac{1}{a_{ii}}

La convergencia del método iterativo se puede revisar usando el número de condición de la matriz de coeficientes A. Un resultado cercano a 1, implica que el sistema será convergente.

cond(A) = ||A|| ||A-1||

np.linalg.cond(A)

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


2. Ejercicio

Referencia: 1Eva_IIT2011_T2 Sistema de Ecuaciones, diagonal dominante

Considere el siguiente sistema de ecuaciones AX=B dado por:

\begin{cases} -2x+5y+9z=1\\7x+y+z=6\\-3x+7y-z=-26\end{cases}

Luego del pivoteo parcial por filas, el sistema de ecuaciones a usar para el método de Gauss-Seidel es:

\begin{cases} 7x+y+z=6\\-3x+7y-z=-26\\-2x+5y+9z=1\end{cases}

Desarrolle el ejercicio usando el método de Gauss-Seidel, tomando como vector inicial X0=[0,0,0] y tolerancia de 0.0001

Compare los resultados con el método de Jacobi.

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


3. Desarrollo Analítico

Para el método de Gauss-Seidel, se usan las ecuaciones reordenas con el pivoteo parcial por filas, para que en lo posible sea diagonal dominante que mejora la convergencia. El resultado del algoritmo a partir de las ecuaciones presentadas al inicio es:

Pivoteo parcial por filas AB:
[[  7.   1.   1.   6.]
 [ -3.   7.  -1. -26.]
 [ -2.   5.   9.   1.]]
A:
[[ 7.  1.  1.]
 [-3.  7. -1.]
 [-2.  5.  9.]]
B:
[  6. -26.   1.]
\begin{cases} 7x+y+z=6\\-3x+7y-z=-26\\-2x+5y+9z=1\end{cases}

se reescriben despejando la incógnita de la diagonal en cada ecuación.

x=\frac{6-y-z}{7} y= \frac{-26+3x+z}{7} z=\frac{1+2x-5y}{9}

El vector inicial X0=[0,0,0] representa un punto de partida, valores estimados o semilla para buscar la solución a las variables X0=[x0,y0,z0].

Como las ecuaciones son el comportamiento del 'sistema', se usan con  X0 para obtener un nuevo y mejor valor estimado para la solución.

itera = 0

X_0=[x_0,y_0,z_0] X_1 = X_0=[0,0,0] x_1=\frac{6-(0)-(0)}{7} =\frac{6}{7}=0.8571

Se actualiza el vector X1,

X_1 = [0.8571,0,0] y_1= \frac{-26+3(0.8571)+(0)}{7} =-3.3469

Se actualiza el vector X1,

X_1 = [0.8571,-3.3469,0] z_1=\frac{1+2(0.8571)-5(-3.3469)}{9} =\frac{1}{9}=2.161 X_1=[0.8571, -3.3469, 2.161]

Para revisar si se está más cerca o no de la solución se calculan los desplazamientos o diferencias entre los valores anteriores y los nuevos:

\text{diferencias}=X_1-X_0 =[0.8571-0, -3.3469-0, 2.161-0]
 [0.8571, -3.3469, 2.161]
-[0.    ,  0.    , 0.    ]
__________________________
 [0.8571, -3.3469, 2.161]
\text{diferencias}=[0.8571, -3.3469, 2.161]

Para estimar el valor del error, se presenta un valor simplificado o escalar de las diferencias (norma del error). Por simplicidad se usa el valor de magnitud máxima. Se usa como variable 'errado', para evitar el conflicto de nombre de variable error usada como palabra reservada en Python:

\text{errado}= max \Big|\text{diferencias}\Big| \text{errado}= max \Big|[0.8571, -3.3469, 2.161]\Big| = 3.3469

Al comparar el valor errado con la tolerancia, se observa que el error aún es mayor que lo tolerado. Por lo que se sigue con la siguiente iteración.

itera = 1

X_1=[x_1,y_1,z_1] X_2 = X_1=[0.8571, -3.3469, 2.161] x_2=\frac{6-(-3.3469)-(2.161)}{7} = 1.0266

Se actualiza el vector X2,

X_2 = [1.0266, -3.3469, 2.161] y_2= \frac{-26+3(1.0266)+(2.161)}{7} =-2.9656

Se actualiza el vector X2,

X_2 = [1.0266, -2.9656, 2.161] z_2=\frac{1+2(1.0266)-5(-2.9656)}{9} =1.9868 X_2 = [1.0266, -2.9656, 1.9868] \text{diferencias}=|X_2-X_1| =|[1.0266-0.8571, -2.9656-(-3.3469), 1.9868-2.161]|
 [1.0266, -2.9656, 1.9868]
-[0.8571, -3.3469, 2.161 ] 
__________________________
 [0.1694,  0.3813, 0.1742]
\text{diferencias}= |[0.1694, 0.3813, 0.1742]| \text{errado}=max |X_2-X_1|= 0.3813

itera = 2

X_3 = X_2=[1.0266, -2.9656, 1.9868] x_3=\frac{6-(-2.9656)-(1.9868)}{7} = 0.997 X_3 =[0.997, -2.9656, 1.9868] y_3= \frac{-26+3(0.997)+(1.9868)}{7} = -3.0032 X_3 =[0.997, -3.0032, 1.9868] z_3=\frac{1+2(0.997)-5(-3.0032)}{9} = 2.0011 X_3 =[0.997, -3.0032, 2.0011] \text{diferencias}=|X_3-X_2| =|[0.997-1.0266, -3.0032-(-2.9656), 2.0011-1.9868]|
 [ 0.997 , -3.0032, 2.0011]
-[ 1.0266, -2.9656, 1.9868] 
__________________________
 [-0.0296,  0.0376, 0.0143]
\text{diferencias}=|[-0.0296, 0.0376, 0.0143]| \text{errado}=max |X_2-X_1|= 0.0376

Método Gauss-Seidel X itera ErroresEn cada iteración el valor de error disminuye, por lo que se estima que el método converge.

Al usar el algoritmo, de los resultados se observa que se detiene luego de 6 iteraciones, los errores son menores a la tolerancia:

Iteraciones Gauss-Seidel
itera,[X]
   errado,[diferencia]
0 [0. 0. 0.] 0.0002
1 [ 0.8571 -3.3469  2.161 ]
  3.3469387755102042 [0.8571 3.3469 2.161 ]
2 [ 1.0266 -2.9656  1.9868]
  0.381322597066037 [0.1694 0.3813 0.1742]
3 [ 0.997  -3.0032  2.0011]
  0.03756644188210334 [0.0296 0.0376 0.0143]
4 [ 1.0003 -2.9997  1.9999]
  0.00346691102194141 [0.0033 0.0035 0.0012]
5 [ 1. -3.  2.]
  0.0003256615309844557 [3.2566e-04 3.0918e-04 9.9398e-05]
6 [ 1. -3.  2.]
  2.996898191776065e-05 [2.9969e-05 2.7044e-05 8.3644e-06]
Metodo de Gauss-Seidel
numero de condición: 1.9508402675105447
X:  [ 1. -3.  2.]
errado: 2.996898191776065e-05
iteraciones: 6

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


4. Algoritmo en Python

El algoritmo para Gauss-Seidel es semejante al realizado para Jacobi, se debe incorporar las instrucciones para actualizar los valores de X[i] en cada uso de ecuación o fila.

Para el algoritmo se usa el sistema de ecuaciones en su forma matricial A.X=B.
Se asume que se ha aplicado el pivoteo parcial por filas y matriz aumentada.

i=0 # una ecuacion
suma = B[i] # numerador
for j in range(0,m,1): # un coeficiente
    if (i!=j): # excepto diagonal de A
        suma = suma-A[i,j]*X[j]
                
nuevo = suma/A[i,i]
diferencia[i] = abs(nuevo-X[i])
X[i] = nuevo
errado = np.max(diferencia)

La actualización para el algoritmo es:

# Método de Gauss-Seidel
import numpy as np

# INGRESO
# NOTA: Para AB, se ha usado pivoteo parcial por filas
A = [[ 7,  1,  1],
     [-3,  7, -1],
     [-2,  5,  9]]
B = [6, -26, 1]

X0 = [0,0,0]
tolera = 0.0001
iteramax = 100

# PROCEDIMIENTO
# Matrices como arreglo, numeros reales
A = np.array(A,dtype=float)
B = np.array(B,dtype=float)
X0 = np.array(X0,dtype=float)
tamano = np.shape(A) # tamaño A
n = tamano[0]
m = tamano[1]

# valores iniciales
diferencia = np.ones(n, dtype=float)
errado = 2*tolera # np.max(diferencia)

itera = 0 
X = np.copy(X0)
while errado>tolera and itera<=iteramax:
    
    for i in range(0,n,1): # una ecuacion
        suma = B[i] # numerador
        for j in range(0,m,1): # un coeficiente
            if (i!=j): # excepto diagonal de A
                suma = suma-A[i,j]*X[j]
                
        nuevo = suma/A[i,i]
        diferencia[i] = abs(nuevo-X[i])
        X[i] = nuevo
    errado = np.max(diferencia)
    
    print(itera, X)
    print('  ',errado,diferencia)
    itera = itera + 1

if (itera>iteramax): # No converge
    X = np.nan
    print('No converge,iteramax superado')

# numero de condicion
ncond = np.linalg.cond(A)

# SALIDA
print('Método de Gauss-Seidel')
print('numero de condición:', ncond)
print('X: ',X)
print('errado:',errado)
print('iteraciones:', itera)

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


5. Algoritmo en Python como función

Las instrucciones del algoritmo, empaquetadas como función, añaden algunas facilidades para el desarrollo del ejercicio. Antes de usar el método de Gauss-Seidel, se aplica el pivoteo parcial por filas para mejorar de ser posible la convergencia.

En la función gauss_seidel() se añaden las instrucciones para mostrar los resultados parciales en cada iteración. Se añade una tabla para revisión de valores al final del algoritmo, o para realizar la gráfica de errores por iteración.

Si el sistema es de 3x3 se puede añadir una gráfica de los resultados por iteración, mostrando la trayectoria descrita por los resultados parciales en 3D.

# Algoritmo Gauss-Seidel
# solución de matrices
# métodos iterativos
# Referencia: Chapra 11.2, p.310,
#      Rodriguez 5.2 p.162
import numpy as np

def gauss_seidel(A,B,X0,tolera, iteramax=100, vertabla=False, precision=4):
    ''' Método de Gauss Seidel, tolerancia, vector inicial X0
        para mostrar iteraciones: vertabla=True
    '''
    # Matrices como arreglo, numeros reales
    A = np.array(A, dtype=float)
    B = np.array(B, dtype=float)
    X0 = np.array(X0, dtype=float)
    tamano = np.shape(A)
    n = tamano[0]
    m = tamano[1]

    # valores iniciales
    diferencia = 2*tolera*np.ones(n, dtype=float)
    errado = 2*tolera # np.max(diferencia)
   
    tabla = [np.copy(X0)] # tabla de iteraciones
    tabla = np.concatenate((tabla,[[np.nan]]),
                            axis=1) # añade errado

    if vertabla==True:
        print('Iteraciones Gauss-Seidel')
        print('itera,[X]')
        print('   errado,[diferencia]')
        print(0,X0)
        print(' ',np.nan)
        np.set_printoptions(precision)

    itera = 0 # Gauss-Sediel
    X = np.copy(X0)
    while (errado>tolera and itera<iteramax):
        
        for i in range(0,n,1): # una ecuacion
            suma = B[i]
            for j in range(0,m,1):
                if (i!=j):
                    suma = suma-A[i,j]*X[j]
            nuevo = suma/A[i,i]
            diferencia[i] = np.abs(nuevo-X[i])
            X[i] = nuevo
        errado = np.max(diferencia)

        Xfila= np.concatenate((X,[errado]),axis=0)
        tabla = np.concatenate((tabla,[Xfila]),axis = 0)
        itera = itera + 1
        if vertabla==True:
            print(itera, X)
            print(' ', errado,diferencia)
        
    # No converge
    if (itera>iteramax):
        X = np.nan
        print('No converge,iteramax superado')
    return(X,tabla)

def pivoteafila(A,B,vertabla=False):
    '''
    Pivotea parcial por filas, entrega matriz aumentada AB
    Si hay ceros en diagonal es matriz singular,
    Tarea: Revisar si diagonal tiene ceros
    '''
    A = np.array(A,dtype=float)
    B = np.array(B,dtype=float)
    # Matriz aumentada
    nB = len(np.shape(B))
    if nB == 1:
        B = np.transpose([B])
    AB  = np.concatenate((A,B),axis=1)
    
    if vertabla==True:
        print('Matriz aumentada')
        print(AB)
        print('Pivoteo parcial:')
    
    # Pivoteo por filas AB
    tamano = np.shape(AB)
    n = tamano[0]
    m = tamano[1]
    
    # Para cada fila en AB
    pivoteado = 0
    for i in range(0,n-1,1):
        # columna desde diagonal i en adelante
        columna = np.abs(AB[i:,i])
        dondemax = np.argmax(columna)
        
        # dondemax no es en diagonal
        if (dondemax != 0):
            # intercambia filas
            temporal = np.copy(AB[i,:])
            AB[i,:] = AB[dondemax+i,:]
            AB[dondemax+i,:] = temporal

            pivoteado = pivoteado + 1
            if vertabla==True:
                print(' ',pivoteado, 'intercambiar filas: ',i,'y', dondemax+i)
    if vertabla==True:
        if pivoteado==0:
            print('  Pivoteo por filas NO requerido')
        else:
            print('AB')
            print(AB)
    return(AB)

# PROGRAMA Búsqueda de solucion  --------
# INGRESO
A = [[ 7,  1,  1],
     [-3,  7, -1],
     [-2,  5,  9]]
B = [6, -26, 1]

X0 = [0,0,0]
tolera = 0.0001
iteramax = 100
verdecimal = 4

# PROCEDIMIENTO
AB = pivoteafila(A,B,vertabla=True)
n,m = np.shape(AB)

A = AB[:,:n] # separa en A y B
B = AB[:,n]

[X, tabla] = gauss_seidel(A,B,X0, tolera,
                           vertabla=True,
                           precision=verdecimal)
n_itera = len(tabla)-1
errado = tabla[-1,-1]

# numero de condicion
ncond = np.linalg.cond(A)

# SALIDA
print('Metodo de Gauss-Seidel')
print('numero de condición:', ncond)
print('X: ',X)
print('errado:',errado)
print('iteraciones:', n_itera)

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]
..


4.1 Gráficas de iteraciones

Se muestra la gráfica de errores vs iteraciones.

# GRAFICA de iteraciones
errados = tabla[:,n]

import matplotlib.pyplot as plt
# grafica de error por iteracion
fig2D = plt.figure()
graf = fig2D.add_subplot(111)
graf.plot(errados,color='purple')
graf.set_xlabel('itera')
graf.set_ylabel('|error|')
graf.set_title('Método de Gauss-Seidel: error[itera]')
graf.grid()

plt.show()

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]


4.2 Gráfica de iteraciones para sistema de 3×3

Si el sistema es de 3×3, se muestra una gráfica de la trayectoria de resultados parciales, semejante a la presentada para la descripción del concepto. Permite observar si la espiral es convergente o no.

# grafica de iteraciones x,y,z
# solo para 3x3
xp = tabla[:,0]
yp = tabla[:,1]
zp = tabla[:,2]

fig3D = plt.figure()
graf3D = fig3D.add_subplot(111,projection='3d')

graf3D.plot(xp,yp,zp,color='purple')
graf3D.plot(xp[0],yp[0],zp[0],'o',label='X[0]')
graf3D.plot(xp[n_itera],yp[n_itera],zp[n_itera],
            'o',label='X['+str(n_itera)+']')

graf3D.set_xlabel('x')
graf3D.set_ylabel('y')
graf3D.set_zlabel('z')
graf3D.set_title('Método de Gauss-Seidel: iteraciones X')

# graf3D.view_init(45,45)
plt.legend()
plt.show()

[ Gauss-Seidel ] [ Ejercicio ] [ Analítico ] [ Algoritmo ] [ Función ] [Gráfica]