Laboratorio 6 1. Introducción

Anuncio
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
Descargar