Métodos Numéricos Laboratorio 6 Semana: del 22 al 26 de Febrero UVG-mm2010, mm2022 Instrucciones: En sus cursos de matemática se le ha recomendado que ataque los problemas desde varios enfoques: analítico, gráco, y numérico. En este curso nos interesan particularmente los últimos dos enfoques. Responda las siguientes preguntas dejando constancia clara de su procedimiento; recuerde incluir grácas, código en Python, explicaciones que considere pertinentes. 1. Introducción Para ejemplicar el proceso iterativo, considere el siguiente sistema lineal: 12x0 + 3x1 − 5x2 = 1 x0 + 5x1 + 3x2 = 28 3x0 + 7x1 + 13x2 = 76 con aproximación inicial (0) (0) ~x(0) = [x(0) 0 , x1 , x2 ] = [0, 0, 0] En Python procedemos así: 1 In [387]: A = array([ [12.0, 3, -5], [1, 5, 3], [3, 7, 13] ]) 2 In [388]: A 3 Out[388]: 4 array([[ 12., 3., -5.], 5 [ 1., 5., 3.], 6 [ 3., 7., 13.]]) 7 In [389]: b = array([1.0, 28, 76]) 8 In [390]: x = zeros(3) 9 In [391]: n = A.shape[0] 10 In [392]: x 11 Out[392]: array([ 0., 12 In [393]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 13 Out[393]: array([ 0.08333333, 14 In [394]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 15 Out[394]: array([ 1.11923077, 16 In [395]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 17 Out[395]: array([ 0.73589744, 18 In [396]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 19 Out[396]: array([ 1.02361604, 20 In [397]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 21 Out[397]: array([ 0.92808679, 22 In [398]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 23 Out[398]: array([ 1.00383108, 24 In [399]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 25 Out[399]: array([ 0.98032779, 0., 0.]) 5.6 , 2.07564103, 3.68923077, 2.77069034, 3.1813833 , 2.9435679 , 3.04787733, 5.84615385]) 2.81153846]) 4.47021696]) 3.68982249]) 4.11802458]) 3.91892743]) 4.02950242]) 1 y 26 In [400]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 27 Out[400]: array([ 1.00032334, 28 In [401]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 29 Out[401]: array([ 0.9945916 , 30 In [402]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 31 Out[402]: array([ 0.99988778, 32 In [403]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 33 Out[403]: array([ 0.99850561, Observamos entonces que Nota: 3.01267955, 2.99667865, 3.00337006, converge a 3.97875964]) 4.00733839]) 3.99442064]) 4.00181432]) ~x = [1, 3, 4]. una sola línea de código puede ser bastante densa. Asegúrese de entender todos los conceptos que se están ecuación de recurrencia ; empleando en la de lista a 1 ~x(k) 2.98623299, e.g. array slicing, comprehensión de listas, producto punto (dot), conversión array: array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k]))/A[k,k] for k in arange(n) ]) 2. Problemas 1. A continuación se presenta una versión simplicada, pero funcional, del método de Gauss-Seidel. Agregue descriptivos comentarios que ayuden a explicar este algoritmo; por ejemplo: i←i+1 incrementa el contador de la iteración. Gauss-Seidel(A, b, x, imax ) 1 i←0 2 n ← rank(A) 3 4 5 while i < imax do for j ← 1 to n do xj ← A1 jj bj − Pn i=1 i6=j Aji xi i←i+1 6 7 return x 2. Se tiene el siguiente sistema de ecuaciones 4x + y + 2z = 4 3x + 5y + z = 7 x + y + 3z = 3 (a) Empleando la aproximación inicial Solución: ~x(0) = ~0, resuelva con el método de Jacobi. Siguiendo el ejemplo de la Introducción, podemos ejecutar la ecuación de recurrencia del método de Jacobi, paso a paso, en la terminal: 1 In [46]: A = array([ [4.0, 1, 2], [3,5,1], [1,1,3] ]) 2 b = array([4.0, 7, 3]) 3 4 n = A.shape[0] 2 5 x = zeros(n) 6 In [52]: x 7 Out[52]: array([ 0., 0., 0.]) 8 9 In [53]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 10 Out[53]: array([ 1. , 11 In [54]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 12 Out[54]: array([ 0.15, 13 ... 14 In [70]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 15 Out[70]: array([ 0.49741655, 16 In [71]: x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]); x 17 Out[71]: array([ 0.50190165, 1.4, 1. ]) 0.6 , 0.2 ]) 0.99723626, 0.49757858]) 1.00203435, 0.5017824 ]) 7.01765909, 3.00928318]) 0.01765909, 0.00928318]) 18 19 In [74]: dot(A,x) 20 Out[74]: array([ 4.01320572, 21 In [75]: dot(A,x) - b 22 Out[75]: array([ 0.01320572, ~x(k) converge a [0.5, 1.0, 0.5]. Noten también ~ equivalente a A~ x − b. Algunos libros denen el vector residuo Notamos que qué tan lejos estamos del valor de convergencia, pues el último comando ejecutado: dot(A,x) - b, como ~ r = ~b − A~x y es una forma de determinar esperamos que ~ r → ~0. Ahora bien, en lugar de iterar en la terminal (lo cual puede resultar bastante tedioso), es mejor encapsular esas ideas en una función como la siguiente: 1 def jacobi(A,b,error): 2 n = A.shape[0] 3 x = zeros(n) 4 iter = 0 5 res = b - dot(A,x) 6 while norm(res) > error: 7 x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]) 8 res = b - dot(A,x) 9 10 iter = iter + 1 return x, iter Por supuesto, para que la función anterior funcione asumimos que diagonal y tomamos la simplicación que (b) Empleando la aproximación inicial Solución: ~ (0) ~x(0) = ~0, x A es estrictamente dominante por la = ~0. resuelva con el método de Gauss-Seidel. Similar al inciso anterior, podemos ejecutar la ecuación de recurrencia en la terminal. Noten las similitudes y diferencias entre las ecuaciones de recurrencia para los métodos de Jacobi y Gauss-Seidel: 1 # Jacobi 2 x = array([ (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] for k in arange(n) ]) 3 4 # Gauss-Seidel 3 5 6 for k in arange(n): x[k] = (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] Mejor aún, también podemos crear un función: 1 def gauss_seidel(A,b,error): 2 n = A.shape[0] 3 x = zeros(n) 4 iter = 0 5 res = b - dot(A,x) 6 while norm(res) > error: 7 for k in arange(n): 8 9 10 11 x[k] = (b[k] - dot( A[k, arange(n)<>k], x[arange(n)<>k] ))/A[k,k] res = b - dot(A,x) iter = iter + 1 return x, iter Al iterar en la terminal, o al emplear la función anterior, nuevamente notamos que ~ x (k) converge a [0.5, 1.0, 0.5]. (c) ¾Cuántas iteraciones necesitó en los incisos anteriores para obtener convergencia en 2 cifras signicativas? Solución: Así como está redactada, esta pregunta es ambigua (perdón por éso). Lo más apropiado es utilizar la anterior denición para el residuo k~rk → 0. 1 ~r = ~b − A~x y considerar la norma k~rk. Claramente, esperamos que En Python: In [27]: run ~/docs/uvg/mm2010-met-num/2010/labs/lab06/lab06.py 2 3 In [28]: A = array([ [4.0, 1, 2], [3,5,1], [1,1,3] ]) 4 In [29]: b = array([4.0, 7, 3]) 5 In [30]: jacobi(A,b,0.001) 6 Out[30]: (array([ 0.49993463, 7 In [32]: gauss_seidel(A,b,0.001) 8 Out[32]: (array([ 0.50016 , 0.99993007, 0.999936, 0.49993873]), 30) 0.499968]), 6) Gauss-Seidel es el claro ganador con sólo 6 iteraciones versus 30 de Jacobi. radio espectral ρ de la matriz de iteración matriz de iteración no es la matriz A en A~ x = ~b. (d) Encuentre el en el método de Gauss-Seidel. Ayuda: Recuerde que la Solución: Lo primero que hay que tener claro es que la matriz de iteración en el método de Gauss-Seidel no es la matriz A. Al considerar la derivación del Método de Gauss-Seidel (Jacobi es similar) tomamos A=L+D+U donde L es la matriz triangular inferior, 4 D es la matriz diagonal y U es la matriz triangular superior. Tenemos entonces: A~x = ~b (L + D + U)~x = ~b (L + D)~x + U~x = ~b (L + D)~x = ~b − U~x ~x = (L + D)−1 ~b − U~x Esta última ecuación sugiere un esquema iterativo: ~x(k+1) = (L + D)−1 ~b − U~x(k) que puede interpretarse como la ecuación de recurrencia : ~x(k+1) = ~c − M~x(k) donde ~c = (L + D)−1~b M = (L + D)−1 U Noten entonces que el vector ~c es constante, y la matriz Seidel. Ahora en Python, noten los comandos 1 In [65]: A 2 Out[65]: 3 array([[ 4., 1., 2.], 4 [ 3., 5., 1.], 5 [ 1., 1., 3.]]) 6 In [66]: diag(A) 7 Out[66]: array([ 4., 8 In [67]: diagflat(diag(A)) 9 Out[67]: 5., 10 array([[ 4., 0., 0.], 11 [ 0., 5., 0.], 12 [ 0., 0., 3.]]) 13 In [68]: tril(A) 14 Out[68]: 15 array([[ 4., 0., 0.], 16 [ 3., 5., 0.], [ 1., 1., 3.]]) 17 18 In [69]: triu(A) 19 Out[69]: 20 array([[ 4., 1., 2.], 21 [ 0., 5., 1.], M es la matriz de iteración del método de Gauss- diag, diagflat, tril, triu: 3.]) 5 22 [ 0., 0., 3.]]) Tenemos entonces que D Con lo que calculamos M 1 In [73]: A 2 Out[73]: 3 array([[ 4., 1., 2.], 4 [ 3., 5., 1.], 5 [ 1., 1., 3.]]) diagflat(diag(A)), L + D In [74]: D = diagflat(diag(A)); D 7 Out[74]: 8 array([[ 4., 0., tril(A) y U es 9 [ 0., 5., 0.], [ 0., 0., 3.]]) 11 In [75]: U = triu(A) - D; U 12 Out[75]: 13 array([[ 0., 1., 2.], 14 [ 0., 0., 1.], 15 [ 0., 0., 0.]]) 16 In [76]: M = dot( inv(tril(A)), U); M 17 Out[76]: 18 array([[ 0. 19 [ 0. , -0.15 20 [ 0. , -0.03333333, -0.13333333]]) , 0.25 , 0.5 ], , -0.1 ], 21 In [78]: eigvals(M) 22 Out[78]: array([ 0. 23 In [80]: max(abs(eigvals(M))) 24 Out[80]: 0.20000000000000001 25 In [81]: max(abs(eigvals(M))) < 1 26 Out[81]: True , -0.2 , -0.08333333]) radio espectral ρ de la matriz de iteración para Gauss-Seidel es 0.2, que es menor que por lo que esperamos obtener convergencia sin importar el vector inicial interpolación polinomial triu(A) - diagflat(diag(A)). 0.], 10 1, es y sus eigenvalores: 6 Finalmente tenemos que el 3. La es ~x(0) . consiste en determinar el polinomio (único) de grado n−1 que pasa por n puntos. Estos polinomios tienen la forma: f(x) = a0 + a1 x + a2 x2 + · · · + an−1 xn−1 donde los ak sistema con son coecientes constantes. Una forma directa (trivial) de encontrar dichos coecientes es generar un n ecuaciones lineales a partir de los n puntos dados. Queremos entonces determinar el polinomio de cuarto grado f(x) = a0 + a1 x + a2 x2 + a3 x3 + a4 x4 que pasa por los siguientes cinco puntos: 6 Adicionalmente, determine e interprete el x y 200 0.746 250 0.675 300 0.616 400 0.525 500 0.457 número de acondicionamiento κ de la matriz A, κ(A) = kAk · kA−1 k Note que κ(A) = ∞ Solución: si A es una matriz singular. Empecemos planteando el sistema lineal con el cual obtendríamos los coecientes del polinomio inter- polante. La idea es sustituir los puntos dados: f(x) = a0 + a1 x + a2 x2 + a3 x3 + a4 x4 entonces, 0.746 = a0 + a1 200 + a2 2002 + a3 2003 + a4 2004 . . . 0.457 = a0 + a1 500 + a2 5002 + a3 5003 + a4 5004 en álgebra matricial: 1 200 2002 . . . 1 500 5002 Esta es una ··· ··· X~ a = ~y 2004 a0 0.746 . . . = . . . 4 500 a4 0.457 matriz de Vandermonde, en Python podemos usar el comando vander: 1 In [261]: x = array([200, 250, 300, 400, 500], dtype=long) 2 In [262]: V = vander(x) 3 In [263]: A = V.T[::-1].T 4 In [264]: A 5 Out[264]: 6 array([[ 1, 200, 40000, 7 [ 1, 250, 62500, 15625000, 3906250000], 8 [ 1, 300, 90000, 27000000, 8100000000], 9 [ 1, 400, 160000, 64000000, 25600000000], 10 [ 1, 500, 250000, 125000000, 62500000000]]) 8000000, 1600000000], Antes de tratar de encontrar el polinomio interpolante, podemos empezar a estudiar las características de la matriz, e.g. su número de acondicionamiento κ, el radio espectral ρ de esta matriz en el método de Gauss-Seidel. 7 Para encontrar el radio espectral de la matriz de iteración en Python: 1 x = array([200, 250, 300, 400, 500], dtype=long) 2 V = vander(x) 3 A = V.T[::-1].T 4 D = diagflat(diag(A)) 5 U = triu(A) - D 6 M = dot( inv(tril(A)), U) 7 ev = eigvals(M) 8 rho = max(abs(ev)) Donde ρ ≈ 0.9985 está peligrosamente cercano a 1 (recuerde que preferimos que ρ 1). Ya ésto no nos da muchas esperanzas para aplicar métodos iterativos en esta matriz. Otra característica que podríamos analizar es el número de acondicionamiento números (int, 1 long, float) κ. En la computadora vemos que la elección que tomemos para representar nos lleva a distintas respuestas: def kappa_interpol(typ): 2 x = array([200, 250, 300, 400, 500], dtype=typ) 3 V = vander(x) 4 A = V.T[::-1].T 5 kappa = norm(A)*norm(inv(A)) 6 return kappa 7 8 9 In [318]: kappa_interpol(int) Out[318]: nan 10 In [319]: kappa_interpol(long) 11 Out[319]: nan 12 In [320]: kappa_interpol(float) 13 Out[320]: 11711900877602.697 14 In [321]: kappa_interpol(float64) 15 Out[321]: 11711900877602.697 Vemos entonces que en esta matriz κ → ∞, es decir, esta matriz es casi singular. Cualquier intento de resolver este problema numéricamente es un ejercicio en la futilidad. 4. Dado el siguiente sistema: 2x − 6y − z = −38 −3x − y + 7z = −34 −8x + y − 2z = −20 Ayuda: De ser necesario, reordene las ecuaciones vergencia (ésto está relacionado con la idea de matriz de diagonal estrictamente dominante ). Resuelva empleando el método de Gauss-Seidel. Solución: 1 En Python: In [326]: A = array([ [2.0, -6, -1], [-3,-1,7], [-8,1,-2] ]); A 8 para obtener con- 2 Out[326]: 3 array([[ 2., -6., -1.], 4 [-3., -1., 5 [-8., 7.], 1., -2.]]) 6 In [327]: b = array([-38.0, -34, -20]); b 7 Out[327]: array([-38., -34., -20.]) 8 In [329]: gauss_seidel(A,b,0.01) 9 Out[329]: (array([ Inf, -Inf, -Inf]), 320) Lo anterior se debe a que la matriz no es estrictamente dominante por la diagonal. Al permutar las las tenemos: 1 In [332]: Ap = array([ [-8,1,-2], [2.0, -6, -1], [-3,-1,7] ]); Ap 2 Out[332]: 3 array([[-8., 4 1., -2.], [ 2., -6., -1.], 5 [-3., -1., 7.]]) 6 In [333]: bp = array([-20.0, -38, -34]); bp 7 Out[333]: array([-20., -38., -34.]) 8 In [334]: gauss_seidel(Ap,bp,0.01) 9 Out[334]: (array([ 4., Donde ~x converge a 8., -2.]), 5) [4, 8, −2] en sólo 5 iteraciones. 5. En algunos casos se puede resolver un sistema de ecuaciones no lineales empleando los métodos iterativos vistos en clase. Considere el sistema: x2 + 10x + 2y2 − 13 = 0 2x3 − y2 + 5y − 6 = 0 Para obtener un esquema iterativo reescribimos las ecuaciones anteriores: 13 − x2 − 2y2 10 6 − 2x3 + y2 y= 5 x= Resuelva para Solución: x, y empleando el método de Gauss-Seidel. Podemos empezar ganando intuición geométrica si gracamos estas funciones. En Cálculo 2 se tra- bajaron funciones de varias variables de la forma en el espacio o tomar Python usando f(x, y) : R2 → R. curvas (contornos) de nivel. z1 = lambda x,y: x**2 + 10*x + 2*y**2 - 13 2 z2 = lambda x,y: 2*x**3 - y**2 + 5*y - 6 3 4 x,y = mgrid[-12:3:100j, -6:6:100j] 5 cs1 = contour(x,y,z1(x,y), 1,colors=’k’) supercies Es precisamente con ésta última idea que las gracaremos en contour: 1 Para gracarlas podemos generar 9 6 x,y = mgrid[-2:5:100j, -5:5:100j] 7 cs2 = contour(x,y,z2(x,y), 1,colors=’k’) 8 clabel(cs1,fontsize=9) 9 clabel(cs2,fontsize=9) 10 axis(’equal’) 11 grid() Figura 1: Contornos para z=0 Tenemos entonces dos puntos de intersección. Veamos ahora la contraparte iterativa: 1 def itera_no_lineal(x, y, iter_max=50): 2 iter = 0 3 while iter < iter_max: 4 x = (13 - x**2 - 2*y**2)/10.0 5 y = (6 - 2*x**3 + y**2)/5.0 6 7 iter = iter + 1 return x, y 8 9 In [19]: itera_no_lineal(0,0) 10 Out[19]: (1.0000006066400808, 0.99999851029323739) 11 In [20]: itera_no_lineal(-5,0) 12 Out[20]: (0.99996359464080398, 1.0000893972793692) 13 In [21]: itera_no_lineal(-5,-2) 14 OverflowError: (34, ’Numerical result out of range’) Encontramos ya uno de los dos puntos de intersección (−5, −2) (1, 1). tenemos divergencia de la ecuación de recurrencia. Es interesante (y útil) encontrar la región en donde tenemos convergencia. En Python: 1 2 Noten también que al tomar la aproximación inicial def itera_decide(x, y, iter_max=50): iter = 0 10 R2 en 3 try: 4 while iter < iter_max: 5 x = (13 - x**2 - 2*y**2)/10.0 6 y = (6 - 2*x**3 + y**2)/5.0 7 iter = iter + 1 8 9 conv = 1 except: 10 11 conv = 0 return conv 12 13 x,y = mgrid[-5:5:100j, -5:5:100j] 14 z = array([itera_decide(float(x[i,j]), float(y[i,j])) for i in range(100) for j in range(100)]) 15 z.shape = (100,100) 16 matshow(z.T, cmap=cm.hot) 17 xticks( arange(0,100,10), arange(-5,5)) 18 yticks( arange(0,100,10), arange(5,-5,-1)) 19 grid() Figura 2: Región de convergencia Al comparar estas dos grácas vemos que el otro punto de intersección está peligrosamente cerca de la región de divergencia. Nuevamente los intentos numéricos no nos llevarán muy lejos. Sin embargo, grácamente podemos estimar que es 6. Muestre que A (−0.374, 2.88). es una matriz denida positiva 1 . Es decir, ~xT A~x > 0, ∀~x 6= 0, ~x ∈ Rn . −1 A= 4 7 1 −1 1 6 1 En inglés, 12 positive denite matrix. 11 4 Solución: Posiblemente la forma más sencilla de demostrar que rización empleando eigenvalores; si asumimos que ~x A denida positiva es es un eigenvector de A, es a través de la caracte- tenemos que, A~x = λ~x luego, al premultiplicar por ~xT , ~xT A~x = λ~xT ~x = λkxk2 por denición, si A es denida positiva satisface ~xT A~x > 0 entonces debe cumplirse que todos los eigenvalores son positivos, i.e. λ > 0. Obtengamos ahora los eigenvalores en Python: 1 In [291]: A = array([ [12.0, 4, -1], [4, 7, 1], [-1, 1, 6]]) 2 In [292]: eigvals(A) 3 Out[292]: array([ 14.23589531, 3.89774951, 6.86635518]) Dado que todos los eigenvalores son positivos entonces podemos concluir que 7. La matriz A de n×n satisface A4 = −1.6A2 − 0.64I, donde I lı́m An éste es un problema analítico, i.e. no necesita emplear la computadora. Solución: Empleando un poco de álgebra matricial, tenemos que: A4 = −1.6A2 − 0.64I A4 + 1.6A2 + 0.64I = 0 A4 + 2(0.8)A2 + (0.8)2 I = 0 (A2 + 0.8I)2 = 0 A2 + 0.8I = 0 A2 = −0.8I A3 = −0.8A A4 = −0.8A2 = (−0.8)2 I A5 = (−0.8)2 A 12 es denida positiva. es la matriz identidad. Determine n→∞ Ayuda: A en general, para k ∈ N, A2k = (−0.8)k I A2k+1 = (−0.8)k A por lo tanto, lı́m An = 0 n→∞ 13