7.1.3 EDP Parabólica – analítico explícito con Sympy-Python

EDP Parabólica [ contínua a discreta ] || sympy: [ explícito ] [ implícito ]
..


1. Ejercicio

Referencia:  Chapra 30.2 p888 pdf912, Burden 9Ed 12.2 p725, Rodríguez 10.2 p406

\frac{\partial ^2 u}{\partial x ^2} = K\frac{\partial u}{\partial t}

Valores de frontera: Ta = 60, Tb = 40, T0 = 25
Longitud en x a = 0, b = 1,  Constante K= 4
Tamaño de paso dx = 0.1, dt = dx/10

EDP Parabólica [ contínua a discreta ] || [ sympy_explícito ] [sympy_ implícito ]
..


2. EDP Parabólica contínua a discreta

El resultado del algoritmo para el ejercicio propuesto es:

EDP Parabólica contínua a discreta
 ordenDx : 2
 ordenDy : 1

 edp=0 :
  2                             
 ∂               ∂              
───(u(x, y)) - 4⋅──(u(x, y)) = 0
  2              ∂y             
∂x                              
 K_ : 4

 Lambda_L :
  Dy 
─────
    2
4⋅Dx 
 Lambda L_k : 0.250000000000000

 (discreta=0)*Dy**ordeny :
                            2⋅Dy⋅U(i, j)   Dy⋅U(i - 1, j)   Dy⋅U(i + 1, j)
4⋅U(i, j) - 4⋅U(i, j + 1) - ──────────── + ────────────── + ──────────────
                                  2               2                2      
                                Dx              Dx               Dx       

 discreta_L=0 :
⎛    2⋅Dy⎞                           Dy⋅U(i - 1, j)   Dy⋅U(i + 1, j)    
⎜4 - ────⎟⋅U(i, j) - 4⋅U(i, j + 1) + ────────────── + ────────────── = 0
⎜      2 ⎟                                  2                2          
⎝    Dx  ⎠                                Dx               Dx           

 discreta :
2⋅U(i, j) - 4⋅U(i, j + 1) + U(i - 1, j) + U(i + 1, j) = 0

El desarrollo analítico comienza en escribir la ecuación diferencial parcial parabólica de la forma contínua a la representación discreta, siguiendo los criterios indicados en la parte teórica usando derivadas expresadas en diferencias divididas.

La EDP se escribe en formato Sympy en el bloque de ingreso en dos partes: lado izquierdo (LHS) de la ecuación y lado derecho (RHS), definiendo u como una función de variables x,y.

# INGRESO
fxy = 0*x+0*y  # f(x,y) = 0 , ecuacion complementaria
# ecuacion edp : LHS=RHS
LHS = sym.diff(u,x,2) 
RHS = 4*sym.diff(u,y,1) + fxy
edp = sym.Eq(LHS-RHS,0)

La ecuación f(x,y) se añade para ser usada con otros ejercicios que puede revisar en la sección de Evaluaciones.

Las expresiones para diferencias divididas seleccionadas se ingresan como un diccionario:

# centrada, adelante 
dif_dividida ={sym.diff(u,x,2): (U.subs(i,i-1)-2*U+U.subs(i,i+1))/(Dx**2),
               sym.diff(u,y,1): (U.subs(j,j+1)-U)/Dy}

Las condiciones iniciales en los extremos a y b, y la temperatura en la barra, pueden ser funciones matemáticas, por lo que se propone usar el formato lambda de las variables x o y. En el ejercicio básico presentado en la parte teórica, la barra tiene temperatura constante, por lo que escribe como una  constante mas cero por la variable independiente, esta forma facilita la evaluación de un vector xi por ejemplo, en caso de disponer una expresión diferente.

# Valores de frontera
fya = lambda y: 60 +0*y  # izquierda
fyb = lambda y: 40 +0*y  # derecha
fxc = lambda x: 25 +0*x  # inferior, función inicial

los demás parámetros del ejercicio se ingresan más adelante de forma semejante al ejercicio con Numpy.

Todas las expresiones se escriben al lado izquierdo (LHS) de la igualdad, la parte del lado derecho(RHS) se la prefiere mantener en cero. La expresión se organiza manteniendo el coeficiente en 1 para el termino de Dx de mayor orden. Se sustituyen las derivadas por diferencias divididas, para luego simplificar la expresión usando λ como referencia.

Al desarrollar el método explícito se requiere indicar el nodo u(i,j+1) a buscar

buscar = U.subs(j,j+1) # U[i,j+1] método explícito

El valor de K se puede determinar en la expresión al reordenar para que el término de Dx de mayor grado sea la unidad. El valor de K será el del término Dy con signo negativo, al encontrarse en el lado izquierdo (LHS) de la expresión, en la propuesta teórica se encontraba como positivo del lado derecho.

El término de mayor grado se puede obtener al revisar la expresión por cada término de la suma, buscando el factor que contiene Dx o Dy en cada caso. Por ser un poco ordenado se añade la función edp_coef_Dx().

Instrucciones en Python

Para revisar los resultados de algunos pasos para obtener la expresión discreta de EDP, se usa un diccionario a ser usado en el bloque de salida. Para las expresiones de ecuación se usa sym.pprint().

# Ecuaciones Diferenciales Parciales Parabólicas
# EDP Parabólicas contínua a discreta con Sympy
import numpy as np
import sympy as sym

# u(x,y) funciones continuas y variables simbólicas usadas
t = sym.Symbol('t',real=True) # independiente
x = sym.Symbol('x',real=True)
y = sym.Symbol('y',real=True) 
u = sym.Function('u')(x,y) # funcion 
f = sym.Function('f')(x,y) # funcion complemento
K = sym.Symbol('K',real=True)
# U[i,j] funciones discretas y variables simbólicas usadas
i  = sym.Symbol('i',integer=True,positive=True) # indices
j  = sym.Symbol('j',integer=True,positive=True)
Dt = sym.Symbol('Dt',real=True,positive=True)
Dx = sym.Symbol('Dx',real=True,positive=True)
Dy = sym.Symbol('Dy',real=True,positive=True)
L  = sym.Symbol('L',real=True)
U  = sym.Function('U')(i,j)

# INGRESO
fxy = 0*x+0*y  # f(x,y) = 0 , ecuacion complementaria
# ecuacion edp : LHS=RHS
LHS = sym.diff(u,x,2) 
RHS = 4*sym.diff(u,y,1) + fxy
edp = sym.Eq(LHS-RHS,0)

# centrada, adelante 
dif_dividida ={sym.diff(u,x,2): (U.subs(i,i-1)-2*U+U.subs(i,i+1))/(Dx**2),
               sym.diff(u,y,1): (U.subs(j,j+1)-U)/Dy}

buscar = U.subs(j,j+1) # U[i,j+1] método explícito

# Valores de frontera
fya = lambda y: 60 +0*y  # izquierda
fyb = lambda y: 40 +0*y  # derecha
fxc = lambda x: 25 +0*x  # inferior, función inicial

# [a,b] dimensiones de la barra
a = 0  # longitud en x
b = 1
y0 = 0 # tiempo inicial, aumenta con dt en n iteraciones

# muestreo en ejes, discreto
x_tramos = 10
dx  = (b-a)/x_tramos
dy  = dx/10
n_iteraciones = 4 # iteraciones en tiempo

verdigitos = 2      # para mostrar en tabla de resultados
casicero = 1e-15    # para redondeo de términos en ecuacion

# PROCEDIMIENTO --------------------------------------

def edp_coef_Dx(edp,x,ordenx):
    ''' Extrae el coeficiente de la derivada Dx de ordenx,
    edp es la ecuación como lhs=rhs
    '''
    coeff_x = 1.0 # valor inicial
    # separa cada término de suma
    term_suma = sym.Add.make_args(edp.lhs)
    for term_k in term_suma:
        if term_k.is_Mul: # mas de un factor
            factor_Mul = sym.Mul.make_args(term_k)
            coeff_temp = 1; coeff_usar = False
            # separa cada factor de término 
            for factor_k in factor_Mul:
                if not(factor_k.is_Derivative):
                    coeff_temp = coeff_temp*factor_k
                else: # factor con derivada de ordenx
                    partes = factor_k.args
                    if partes[1]==(x,ordenx):
                        coeff_usar = True
            if coeff_usar==True:
                coeff_x = coeff_x*coeff_temp
    return(coeff_x)

def redondea_coef(ecuacion, precision=6,casicero = 1e-15):
    ''' redondea coeficientes de términos suma de una ecuacion
    ecuación como lhs=rhs
    '''
    tipo = type(ecuacion)
    tipo_eq = False
    if tipo == sym.core.relational.Equality:
        RHS = ecuacion.rhs  # separa lado derecho
        ecuacion = ecuacion.lhs # analiza lado izquierdo
        tipo = type(ecuacion)
        tipo_eq = True

    if tipo == sym.core.add.Add: # términos suma de ecuacion
        term_sum = sym.Add.make_args(ecuacion)
        ecuacion = sym.S.Zero  # vacia
        for term_k in term_sum:
            # factor multiplicativo de termino suma
            term_mul = sym.Mul.make_args(term_k)
            producto = sym.S.One
            for factor in term_mul:
                if not(factor.has(sym.Symbol)): # es numerico
                    factor = np.around(float(factor),precision)
                    if (abs(factor)%1)<casicero: # si es entero
                        factor = int(factor)
                producto = producto*factor
            ecuacion = ecuacion + producto
            
    if tipo == sym.core.mul.Mul: # termino único, busca factores
        term_mul = sym.Mul.make_args(ecuacion)
        producto = sym.S.One
        for factor in term_mul:
            if not(factor.has(sym.Symbol)): # es numerico
                factor = np.around(float(factor),precision)
                if (abs(factor)%1)<casicero: # si es entero
                    factor = int(factor)
            producto = producto*factor
        ecuacion = producto
        
    if tipo == float: # solo un numero
        if (abs(ecuacion)%1)<casicero: 
            ecuacion = int(ecuacion)
            
    if tipo_eq==True: # era igualdad, integra lhs=rhs
        ecuacion = sym.Eq(ecuacion,RHS)

    return(ecuacion)


resultado = {} # resultados en diccionario
# orden de derivada por x, y
edp_x = edp.subs(x,0)
edp_y = edp.subs(y,0)
ordenDx = sym.ode_order(edp_x,u)
ordenDy = sym.ode_order(edp_y,u)
resultado['ordenDx'] = ordenDx  # guarda en resultados
resultado['ordenDy'] = ordenDy
# coeficiente derivada orden mayor a 1 (d2u/dx2)
coeff_x = edp_coef_Dx(edp,x,ordenDx)
LHS = edp.lhs  # lado izquierdo de edp
RHS = edp.rhs  # lado derecho de edp
if not(coeff_x==1):
    LHS = LHS/coeff_x
    RHS = RHS/coeff_x
# toda la expresión a la izquierda
edp = sym.Eq(LHS-RHS,0) 
K_ = -edp_coef_Dx(edp,y,ordenDy)
if abs(K_)%1<casicero: # si es entero
    K_ = int(K_)
resultado['edp=0']  = edp
resultado['K_']  = K_

# lambda en ecuacion edp
lamb = (Dy**ordenDy)/(Dx**ordenDx)
if ordenDy==1 and ordenDx==2:
        lamb = lamb/K_
resultado['Lambda_L'] = lamb
# valor de Lambda en ecuacion edp
L_k = lamb.subs([(Dx,dx),(Dy,dy)])
if abs(L_k)%1<casicero:
    L_k = int(L_k)
resultado['Lambda L_k'] = L_k

discreta = edp.lhs # EDP discreta
for derivada in dif_dividida: # reemplaza diferencia dividida
    discreta = discreta.subs(derivada,dif_dividida[derivada])

# simplifica con Lambda_L
discreta_L = sym.expand(discreta*(Dy**ordenDy),mul=True)
resultado['(discreta=0)*Dy**ordeny'] = discreta_L

discreta_L = discreta_L.subs(lamb,L)
discreta_L = sym.collect(discreta_L,U) #agrupa u[,]
discreta_L = sym.Eq(discreta_L,0)
resultado['discreta_L=0'] = discreta_L
        
# sustituye constantes en ecuación a iterar
discreta_L = discreta_L.subs([(Dx,dx),(Dy,dy),(L,L_k)])
discreta_L = redondea_coef(discreta_L)
resultado['discreta'] = discreta_L

# SALIDA
np.set_printoptions(precision=verdigitos)
algun_numero = [int,float,str,'Lambda L_k']
print('EDP Parabólica contínua a discreta')
for entrada in resultado:
    tipo = type(resultado[entrada])
    if tipo in algun_numero or entrada in algun_numero:
        print('',entrada,':',resultado[entrada])
    else:
        print('\n',entrada,':')
        sym.pprint(resultado[entrada])

EDP Parabólica [ contínua a discreta ] || sympy: [ explícito ] [ implícito ]

..


3. EDP Parabólica – Método explícito con Sympy

El desarrollo del método explícito para una ecuación diferencial parcial parabólica usando Sympy, requiere añadir instrucciones para procesar la expresión obtenida en el algoritmo anterior con los valores de i,j.

Se añaden las instrucciones para iterar la expresión discreta para cada i,j dentro de la matriz U creada para el ejercicio. Se aplican las condiciones iniciales a los bordes de la matriz, para luego proceder a iterar.

Como en las actividades del curso se requiere realizar al menos 3 iteraciones para las expresiones del algoritmo con papel y lápiz, solo se presentarán los resultados de las expresiones para la primera fila en j=0.

El algoritmo para la EDP del ejemplo muestra el siguiente resultado:

Método explícito EDP Parabólica
discreta_itera:
              U(i, j)   U(i - 1, j)   U(i + 1, j)
U(i, j + 1) = ─────── + ─────────── + ───────────
                 2           4             4     

 iterar i,j:
j:  0  ;  i:  1  ;  ecuacion: 0
          U(0, 0)   U(1, 0)   U(2, 0)
U(1, 1) = ─────── + ─────── + ───────
             4         2         4   
U(1, 1) = 33.75
j:  0  ;  i:  2  ;  ecuacion: 1
          U(1, 0)   U(2, 0)   U(3, 0)
U(2, 1) = ─────── + ─────── + ───────
             4         2         4   
U(2, 1) = 25.0
j:  0  ;  i:  3  ;  ecuacion: 2
          U(2, 0)   U(3, 0)   U(4, 0)
U(3, 1) = ─────── + ─────── + ───────
             4         2         4   
U(3, 1) = 25.0
j:  0  ;  i:  4  ;  ecuacion: 3
          U(3, 0)   U(4, 0)   U(5, 0)
U(4, 1) = ─────── + ─────── + ───────
             4         2         4   
U(4, 1) = 25.0
j:  0  ;  i:  5  ;  ecuacion: 4
          U(4, 0)   U(5, 0)   U(6, 0)
U(5, 1) = ─────── + ─────── + ───────
             4         2         4   
U(5, 1) = 25.0
j:  0  ;  i:  6  ;  ecuacion: 5
          U(5, 0)   U(6, 0)   U(7, 0)
U(6, 1) = ─────── + ─────── + ───────
             4         2         4   
U(6, 1) = 25.0
j:  0  ;  i:  7  ;  ecuacion: 6
          U(6, 0)   U(7, 0)   U(8, 0)
U(7, 1) = ─────── + ─────── + ───────
             4         2         4   
U(7, 1) = 25.0
j:  0  ;  i:  8  ;  ecuacion: 7
          U(7, 0)   U(8, 0)   U(9, 0)
U(8, 1) = ─────── + ─────── + ───────
             4         2         4   
U(8, 1) = 25.0
j:  0  ;  i:  9  ;  ecuacion: 8
          U(8, 0)   U(9, 0)   U(10, 0)
U(9, 1) = ─────── + ─────── + ────────
             4         2         4    
U(9, 1) = 28.75
... continua calculando

x: [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
t: [0.   0.01 0.02 0.03 0.04]
 j, U[i,j]
4 [60.   42.77 31.29 26.37 25.14 25.   25.06 25.59 27.7  32.62 40.  ]
3 [60.   40.86 29.38 25.55 25.   25.   25.   25.23 26.88 31.8  40.  ]
2 [60.   38.12 27.19 25.   25.   25.   25.   25.   25.94 30.62 40.  ]
1 [60.   33.75 25.   25.   25.   25.   25.   25.   25.   28.75 40.  ]
0 [60. 25. 25. 25. 25. 25. 25. 25. 25. 25. 40.]

Instrucciones en Python

# Método explícito EDP Parabólica ------------
# ITERAR para cada i,j dentro de U 
# x[i] , y[j]  valor en posición en cada eje
xi = np.arange(a,b+dx/2,dx)
yj = np.arange(y0,n_iteraciones*dy+dy,dy)
n = len(xi)
m = len(yj)
# Matriz U
u_xy = np.zeros(shape=(n,m),dtype = float)
u_xy = u_xy*np.nan # valor inicial dentro de u
# llena u con valores en fronteras
u_xy[:,0]   = fxc(xi)  # inferior  Tc
u_xy[0,:]   = fya(yj)  # izquierda Ta
u_xy[n-1,:] = fyb(yj)  # derecha   Tb
u0 = np.copy(u_xy)     # matriz u inicial

# busca el término no conocido
u_explicito = sym.solve(discreta_L,buscar)
u_explicito = sym.Eq(buscar,u_explicito[0])
resultado['discreta_itera'] = u_explicito

print('\nMétodo explícito EDP Parabólica')
print('discreta_itera:')
sym.pprint(u_explicito)

# iterando
print('\n iterar i,j:')
j_k = 0
for j_k in range(0,m-1,1):
    for i_k in range(1,n-1,1):
        eq_conteo = j_k*(n-2)+(i_k-1)
        discreta_ij = u_explicito.subs({i:i_k,j:j_k})
        valorij = 0 # calcula valor de nodo ij
        # separa cada término de suma
        term_suma = sym.Add.make_args(discreta_ij.rhs)
        for term_k in term_suma:
            term_ki = 1
            if term_k.is_Function:
                [i_f,j_f] = term_k.args
                term_ki = u_xy[i_f,j_f]
            elif term_k.is_Mul: # mas de un factor
                factor_Mul = sym.Mul.make_args(term_k)
                # separa cada factor de término 
                for factor_k in factor_Mul:
                    if factor_k.is_Function:
                        [i_f,j_f] = factor_k.args
                        term_ki = term_ki*u_xy[i_f,j_f]
                    else:
                        term_ki = term_ki*factor_k
            else:
                term_ki = term_k
            valorij = valorij + term_ki
        [i_f,j_f] = discreta_ij.lhs.args
        u_xy[i_f,j_f] = float(valorij) # actualiza matriz u
        # muestra las primeras m iteraciones
        if eq_conteo<(n-2):
            print('j: ',j_k,' ; ','i: ',i_k,
                  ' ; ','ecuacion:',eq_conteo)
            sym.pprint(discreta_ij)
            print(discreta_ij.lhs,'=',float(valorij))
print('... continua calculando')

# SALIDA
np.set_printoptions(precision=verdigitos)
print('\nx:',xi)
print('t:',yj)
print(' j, U[i,j]')
for j_k in range(m-1,-1,-1):
    print(j_k, (u_xy[:,j_k]))

EDP Parabólica [ contínua a discreta ] || sympy: [ explícito ] [ implícito ]