Bitacora1_Metodos
Bitacora1_Metodos
20 de septiembre de 2018
2
Índice general
2. Clase 2: Python 27
2.1. Ejecución, variables y objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.1. Ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.1.3. Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2. Operaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2.1. Operaciones aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2.2. Operaciones con Cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3. Expresiones lógicas y condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3.1. Expresiones lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3.2. Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.4. Listas, tuplas, conjuntos y diccionarios . . . . . . . . . . . . . . . . . . . . . . . . 30
2.4.1. Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3
2.4.2. Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.4.3. Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.4.4. Diccionarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.5. Métodos de iteración y bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.5.1. bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.5.2. Métodos de iteración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6. Funciones, clases y módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6.2. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6.3. Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.7.1. Aritmética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.7.2. Zodiaco chino . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.7.3. Cifrado de César . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.7.4. Contraseña . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.7.5. Número perfecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
2.7.6. Scrabble . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
2.7.7. Elementos químicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4
4.5. Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.6. Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.6.1. bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.6.2. bucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.7.1. Distancia en kilómetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.7.2. Zodiaco chino . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5
7.1.5. Importancia de los sistemas de ecuaciones lineales . . . . . . . . . . . . . 129
7.2. Solución de sistemas de ecuaciones lineales . . . . . . . . . . . . . . . . . . . . . . 129
7.2.1. Operaciones elementales entre filas . . . . . . . . . . . . . . . . . . . . . . 129
7.3. Métodos de solución de sistemas de ecuaciones lineales en IPython . . . . . . . . 132
7.3.1. Métodos directos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.3.2. Eliminación Gaussiana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
7.3.3. Descomposición LU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
7.3.4. Métodos indirectos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
7.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
7.4.1. Gauss-Jordan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
7.4.2. Hilbert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
8. Interpolación 151
8.1. Interpolación polinomial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
8.1.1. Método de Lagrange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
8.1.2. Método de Newton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
8.1.3. Método de Neville . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
8.2. Funciones Racionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.2.1. Función diagonal racional . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
8.3. Interpolación segmentaria cúbica . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
8.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.4.1. Lagrange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.4.2. Neville . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
6
11.Métodos de derivación 221
11.1. Métodos de diferencias finitas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
11.1.1. Diferencias finitas progresivas . . . . . . . . . . . . . . . . . . . . . . . . . 222
11.1.2. Diferencias finitas regresivas . . . . . . . . . . . . . . . . . . . . . . . . . . 226
11.1.3. Diferencias finitas centrales . . . . . . . . . . . . . . . . . . . . . . . . . . 229
11.2. Extrapolación Richardson . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
11.3. Derivadas por interpolación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
7
15.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
15.5.1. Condición 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
15.5.2. Animación 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
15.5.3. Condición 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
15.5.4. Animación 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
8
18.Procesamiento de imágenes 3 395
18.1. Histogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
18.1.1. Cómo interpretar el histograma de una imagen . . . . . . . . . . . . . . . 396
18.1.2. Cálculo de un histograma en OpenCV . . . . . . . . . . . . . . . . . . . . 397
18.1.3. Cálculo de un histograma con Numpy . . . . . . . . . . . . . . . . . . . . 401
18.1.4. Ecualización de un histograma con OpenCV . . . . . . . . . . . . . . . . . 402
18.1.5. CLAHE (Contrast Limited Adaptive Histogram Equalization) . . . . . . . 405
18.2. Filtrado en el dominio de la frecuencia . . . . . . . . . . . . . . . . . . . . . . . . 406
18.2.1. Filtros pasa-baja, pasa-alta y paso-banda . . . . . . . . . . . . . . . . . . 407
18.3. Comparación de plantillas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
18.4. Ejercicio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
9
10
1
1.1. Introducción
Un sistema operativo es un software de bajo nivel que programa tareas, localiza almacenamientos,
y y se ocupa de las interfaces de impresoras, unidades de disco, pantalla, el teclado, y el ratón.
Un sistema operativo tiene dos partes principales que son el kernel y los programas del
sistema [21]. En el kernel se localizan los recursos como la memoria incluida y el espacio en el
disco. Los programas del sistema incluyen controladores de dispositivo, bibliotecas, programas de
utilidad, conchas (intérpretes de comandos), secuencias de comandos y archivos de configuración,
programas de aplicación, servidores y documentación. Realizan tareas de mantenimiento de más
alto nivel, a menudo actuando como servidores de una relación de cliente / servidor [21].
Linux es un sistema operativo rápido y estable para computadoras personales y estaciones
de trabajo que ofrece servicios a nivel profesional de Internet [20]. El kernel de Linux fue
desarrollado por el finlandés Linus Torvalds, un estudiante de universitario, que utilizó Internet
para hacer que el código fuente estuviese disponible de forma gratuita [21]. Torvalds liberó la
versión Linux 0.01 en septiembre de 1991 [21].
Linux, que fue desarrollado a través de la cooperación de personas en todo el mundo, es un
producto de la Internet y es un sistema operativo libre [21]. En otras palabras, todo el código
fuente es libre, por lo que existe libertad para estudiarlo, redistribuirlo y modificarlo. Como
resultado, el código está disponible libre de costo para el software, fuente, documentación y el
soporte [21].
Existen numerosas distribuciones de Linux que han sido generadas por compañías y grupos
en diferentes formas. Algunas de las más populares son Red Hat, Ubuntu, Mepis, SUSE, Fedora
y Debian [20]. Todas las distribuciones usan el mismo kernel, a pesar de que en algunos casos
está configurado de forma diferente [20].
En años recientes Linux ha emergido como un sistema operativo poderosos e innovador.
Entre las razones de su popularidad se encuentran el desarrollo de aplicaciones que corren en
todas las versiones de UNIX, linux y otros sistemas operativos [21]. Además, la selección de estas
aplicaciones es amplia y existe una gran variedad de herramientas gráficas, de procesamiento,
redes, seguridad, administración y servidores Web [21]. Adicional a esto, la cantidad de software
disponible para usuarios, la portabilidad de Linux en computadores Apple, Compaq y sistemas
de 64-bit, entre otros, y el soporte de emuladores [21] hacen del sistema operativo una herramienta
valiosa.
Por lo tanto, resulta indispensable conocer algunas generalidades del sistema operativo,
entre las cuales es necesario conocer la terminal o shell. Con la terminal es posible manejar el
computador a través de un lenguaje de programación. Por lo tanto, este capítulo se centra en
la terminal y en los comandos básicos que se pueden implementar para manejar Linux. Así, el
11
capítulo inicia con un breve descripción de la terminal y de su relevancia.
1.2. La terminal
En Linux existe un intérprete de comandos denominado shell o terminal. La terminal actúa
como una interfaz entre el usuario y el sistema operativo [21]. Cuando se introduce un comando
en la pantalla, la terminal interpreta el comando y llama el programa que se desee. Las cuatro
terminales más populares son [21]:
bash (Bourne Again Shell), que es una versión mejorada de la terminal original Bourne
Shell (la terminal original de UNIX)
dash (Debian Almquist Shell), una versión más pequeña de bash, con menos funciones.
La mayoría de las secuencias de comandos shell de inicio llaman dash en lugar de bash
para acelerar el proceso de arranque.
tcsh (La terminal TC), es una versión mejorada de la terminal C, desarrollada como parte
de BSD UNIX.
zsh (La terminal Z), que incorpora características de numerosas terminales, incluyendo la
terminal Korn.
/home/usuario/
Cada uno de estos directorios se encuentra al interior del directorio de trabajo en el cual nos
encontramos.
12
1.3.2. Comandos mkdir, cd y rmdir
El comando mkdir permite crear directorios, si no existen aún. Para ello, se debe especificar
el nombre del directorio que se desea crear un espacio después de escribir el comando. Así,
escribir en la terminal de Linux el comando
mkdir Metodos
cd Metodos
Además, si se desea volver al directorio anterior se puede utilizar el mismo comando seguido
de un espacio y dos puntos ( cd ..). Si se desea volver a la carpeta del usuario se puede escribir
el comando sin adicionar los dos puntos (únicamente se escribe cd).
Por otra parte, el comando rmdir permite eliminar directorios que han sido creados. Para
ello, se escribe el comando, se deja un espacio y se escribe el nombre del directorio que se quiere
remover.
1.3.3. Comandos mv y rm
El comando mv permite cambiar la ubicación de un archivo a un directorio diferente. Para
ello, se debe especificar la ubicación original, el archivo y la ubicación a la que se desea mover.
Por ejemplo:
mv /home/user/Documents/archivo.sh /home/user/Documents/Metodos/
Con este comando es posible enviar el archivo archivo.sh, ubicado en el directorio Documents,
al directorio Metodos.
Por otro lado, el comando rm permite eliminar archivos y la forma en la que se implementa
es igual a la forma en que funciona el comando rmdir.
13
Por otro lado, el comando tar -cvf realiza la acción contraria que el comando tar -xvf, es
decir, con el comando tar -cvf se pueden comprimir archivos para obtener un formato .tar. Si
se desea comprimir un archivo a un formato .tar.gz se puede aplicar el comando tar -zcvf.
-c continua la descarga.
-p descargar todos los archivos que son necesarios para mostrar correctamente una determinada
página HTML, incluyendo imágenes.
-P indicamos en que carpeta queremos que descargue el/los archivos, el valor predefinido
es el directorio actual.
--random-wait espera un tiempo aleatorio entre descarga, para evitar entrar en la lista
de negra.
wget https://www.dropbox.com/s/nyumkp0kf0pdkfk/hipparcos.csv.tar.gz
wc notas.sh
14
35 210 1445
Con base en esto, el archivo notas.sh contiene 35 lineas, 210 palabras y 1445 caracteres.
Los comandos less y cat permiten visualizar un archivo sin realizar modificaciones. Los
dos comandos se implementan de la misma forma: se escribe el comando, se deja un espacio y
se escribe el archivo que se desea visualizar.
El comando anterior imprime las lineas del archivo notas.csv que contengan la palabra
alumno.
El comando awk se usa para manipular texto. Este comando comprueba cada línea de un
archivo, buscando patrones que coincidan con los dados en la línea de comando. La sintaxis de
este comando es:
El siguiente ejemplo aplica este comando para imprimir la primera columna de un archivo:
En el ejemplo anterior, el signo $ hace referencia a las columnas y el numero 1 indica que
se desea imprimir la primera columna del archivo .archivo.csv".
15
1.3.8. Comandos chmod, man, history y exit
El comando chmod permite cambiar los permisos de un archivo. Este comando tiene diversas
sintaxis y es necesario conocer la letra que genera cada tipo de permisos: También hay que saber
la letra que abrevia cada tipo de permiso:
Así, para obtener los permisos de ejecución de un archivo se debe anteponer un signo + a la
letra x:
chmod +x notas.sh
Este comando permite ejecutar el archivo notas.sh, ya que se modifican los permisos de
ejecución. Para ejecutar el archivo se antepone ./ al nombre del archivo.
./notas.sh
man comando
Con esto se despliega toda la información del comando, junto con las distintas sintaxis que
se pueden utilizar.
El comando history permite visualizar en la terminal los comandos que han sido utilizados
hasta el momento. Para ello, solo se debe escribir la palabra history en la terminal.
Por último, el comando exit permite salir de la terminal de un modo adecuado. Para
implementar este comando se escribe la palabra exit en la terminal
16
El resultado de este comando es:
print
arguments
on
separate
lines
Así, al adicionar \n se cambia de linea cada que finaliza una palabra. Otro ejemplo es el
siguiente:
printf "%d\n 23 45
Las estructuras condicionales permiten decidir si se realiza una acción o no. Esta decisión
se toma evaluando una expresión. Para ello, se debe utiliza la siguiente sintaxis:
if [<condicion>]
then
<accion si se cumple la condicion>
else
<accion si no se cumple la condicion>
fi
Por ejemplo, si se desea saber si un numero es mayor que 4, se puede utilizar el siguiente
comando:
17
1.4.2. Bucle for
Los bucles permiten repetir un conjunto de instrucciones todas las veces que se requiera.
Estas instrucciones se repetirán hasta que la condición indicada en el bucle se cumpla. Los bucles
for se basan en una o varias listas de elementos, que se asignan a una variable sucesivamente.
Su sintaxis es:
while [ <condición> ]
do
<accion a realizar>
done
En el siguiente ejemplo, se aplica el comando while para imprimir los numeros del 1 al 10:
i=0
while [ i -le 10 ]
do
echo "$i"
i=$(($i+1))
done
El resultado de este comando es el mismo que el resultado del comando utilizado con el bucle
for. Para obtener el resultado, se aplica una operación aritmética al interior del bucle while.
Una operación aritmética se realiza por medio de un operador. Se debe escribir al interior de
dos paréntesis y al inicio se agrega el signo $. Los operadores aritméticos son:
+ - adición y sustracción
! negación lógica
* / % multiplicación, división y residuo
<= >= <> comparación
18
== != igualdad y desigualdad
1.5. Ejercicios
1.5.1. Tabla de multiplicar
Con este ejercicio se pretende crear un script llamado punto1.sh en bash, que pregunte al
usuario un número y que genere una tabla de multiplicar con los primeros 10 múltiplos así:
2 × 1 = 2
2 × 2 = 4
2 × 3 = 6
2 × 4 = 8
2 × 5 = 10
2 × 6 = 12
2 × 7 = 14
2 × 8 = 16
2 × 9 = 18
2 × 10 = 20
Para solucionar este ejercicio se implementa el siguiente código:
read number
#Se crea una nueva variable con el número ingresado
n=$number
19
# Mientras que el numero sea menor que 20 o mayor que 30, se pregunta
nuevamente al usuario
¿Cuántas estrellas son del mismo tipo espectral que el Sol G2V?
Hacer un catálogo que contenga los rótulos de las columnas, y las filas correspondientes a
las estrellas de tipo G2V, este catálogo debe tener el nombre EstrellasG2V.csv
20
grep -c "G2V" hipparcos.csv
#Instala el comando curl. Para ello se debe ingresar la contraseña del usuario
sudo apt-get install curl
#Con una iteración desde 1 hasta 150 se descargan los codigos fuente de las url
en xkcd, ya que solo la última parte de las url cambia. En seguida se busca la
linea del codigo que contiene la URL de la imagen del comic con el comando grep.
Después se omite la primera parte de la linea para obtener solo la url que se
encuentra después de los dos puntos, para lo que se aplica el comando sed. Con
la url, se aplica el comando wget para obtener la imagen del comic.
1.6. Taller
1.6.1. Una pequeña araña
El arXiv es un repositorio de publicaciones científicas. En este ejericio se elije un tema y
escribe un script en bash llamado arxiv.sh que recibe una palabra clave y de regreso muestre
la cantidad y el título de los nuevos artículos (http://arxiv.org/list/[el_tema]/new) que
contienen la palabra clave.
Por ejemplo, si se eligiera como tema la mecánica cuántica con la palabra clave entanglement
el resultado al hacer ./arxiv.sh entanglement debe ser similar al que se presenta en la figura 1.1.
El codigo del script que permite solucionar el problema para el tema "physics.es el siguiente:
21
Figura 1.1: Intefaz del programa
# Espacio
printf "\n"
22
# iteración para crear las líneas punteadas de la interfaz
for ((x = 0; x < 40; x++))
do
printf %s =
done
# Espacio
printf "\n"
# Espacio
printf "\n"
#parametro que cuenta el numero de artículos que tienen la palabra. Para ello
se utiliza curl para descargar el codigo fuente. Posteriormente se utiliza
grep para encontras las lineas con la palabra especificada y, por último, se
cuentan el numero de lineas que contienen la palabra en el título del articulo
con el comando grep.
#Imprime los artículos que contienen la palabra. Para ello se utiliza curl para
descargar el codigo fuente. Después se utiliza dos veces el comando grep: una
para encontrar las lineas con la palabra y otra para encontrar solo las que la
contengan en el título del artículo. Posteriormente, con el comando sed se
cambia todo lo que se encuentra antes del título del artículo, cambiando todo
lo que se encuentra antes de un signo ">" por un espacio. Por último, se aplica
el comando sed nuevamente para anteponer un guión al comienzo de cada línea.
23
(a) Imprimir la cantidad de planetas incluidos en el catálogo. Usar awk, wc y aritmética con
doble paréntesis.
(b) Mostrar el nombre y la cantidad de planetas con una masa menor a una centésima de la
masa de Júpiter. Usar awk y wc.
(c) Determinar el planeta con el menor periodo orbital. Usar sort con las siguientes opciones
puede ser de utilidad sort --field-separator="," --key=6 -n.
#Punto a
#Punto b
#Imprime el Nombre de los planetas con una masa menor a una centesima de
la masa de Júpiter
#Punto c
24
fila de los resultados anteriores. Por último, se implementa el comando
awk para que se muestre unicamente el nombre del planeta con el periodo
menor.
25
26
2
Clase 2: Python
27
código:
python archivo.py
Con este comando se ejecuta el script que contiene los comandos de python.
2.1.2. Variables
Las variables proporcionan una manera de asociar nombres a objetos. Para ello se debe
seguir la siguiente sintaxis:
nombre = objeto
Por ejemplo, si se desea crear una nueva variable con nombre pi y con valor 3.1416, se utiliza:
pi = 3.1416
2.1.3. Objetos
Los objetos son toda la información que se presenta en Python y cada uno tiene una
identidad, un valor y un tipo. El tipo de objeto determina las operaciones que el objeto soporta
y también define los posibles valores para objetos de ese tipo. Los objetos se pueden dividir en
dos: objetos escalares y objetos no escalares.
Los objetos escalares son objetos no divisibles, es decir, números. Los diferentes tipos de
objetos escalares son;
Los objetos escalares son objetos divisibles que tienen una estructura interna. Los tipos de
objetos escalares son:
Listas
Tuplas
2.2. Operaciones
2.2.1. Operaciones aritméticas
Las operaciones aritméticas se llevan a cabo entre objetos escalares de tipo int y tipo float.
Estas operaciones son numerosas y se presentan a continuación:
28
- : Resta (20-5 sería 15)
a == b a es igual a b
a != b a es distinto de b
29
2.3.2. Condicionales
Un condicional evalúa una operación lógica, es decir una expresión que de como resultado
verdadero o falso (True o False), y ejecuta la pieza de código siguiente siempre y cuando el
resultado sea verdadero. La sintaxis para condicionales en Python es:
if <condicion>:
#dejar una indentación
codigo_a_ejecutar
else:
#dejar indentación
codigo_a_ejecutar
EL siguiente ejemplo permite observar la aplicación de los condicionales junto con las
expresiones lógicas:
Con este código se obtiene el mensaje Correcto en caso de que el usuario ingrese un número
mayor que cero. De lo contrario, se imprime el mensaje Incorrecto. El comando raw_input()
permite obtener el numero del usuario.
Para llamar el primer elemento de una lista se debe ingresar el numero cero entre corchetes
y para llamar el último elemento, se debe ingresar el numero total de elementos menos uno.
Asimismo, se pueden usar índices negativos para elegir elementos. Al ingresar el numero −1
entre los corchetes se obtiene el último elemento de la lista. Existen numerosos métodos que se
pueden aplicar por medio del uso de listas:
30
lista.pop([i]) Quita el ítem en la posición dada de la lista.
lista.index(x) Devuelve el índice en la lista del primer ítem cuyo valor sea x.
2.4.2. Tuplas
Una tupla consiste en un número de valores separados por comas. Las tuplas tienen múltiples
usos ya que son secuencias, igual que las cadenas, y se puede utilizar la misma notación de índices
que en las cadenas para obtener cada una de sus componentes, es decir, se especifica una posición
por medio de corchetes. El siguiente ejemplo implementa tuplas para agrupar un conjunto de
elementos:
Al aplicar el comando t[0] se obtiene 12345, que es el primer elemento de la tupla. Las
tuplas son inmutables, ya que no es posible las componentes individuales de una tupla. Pese a
esto, es posible crear tuplas que contienen objetos mutables como las listas.
2.4.3. Conjuntos
Un conjunto es una conjunto no ordenado de elementos no repetidos. Los usos básicos de
éstos incluyen verificación de pertenencia y eliminación de entradas duplicadas. Además, los
conjuntos soportan operaciones matemáticas como la unión, intersección y diferencia. El tipo
de datos que representa a los conjuntos se llama set. El tipo set es mutable: una vez que se ha
creado un conjunto, puede ser modificado.
Existen dos maneras principales de crear un conjunto. Una de ellas es usar un conjunto entre
llaves especificado:
set(’abracadabra’)
2.4.4. Diccionarios
Los diccionarios en Python son un tipo de estructuras de datos que permite guardar un
conjunto no ordenado de pares clave-valor, siendo las claves únicas dentro de un mismo diccionario
(es decir que no pueden existir dos elementos con una misma clave). De la misma forma que
con listas, es posible definir un diccionario directamente con los miembros que va a contener, o
31
bien inicializar el diccionario vacío y luego agregar los valores de a uno o de a muchos.
Para definirlo junto con los miembros que va a contener, se encierra el listado de valores
entre llaves, las parejas de clave y valor se separan con comas, y la clave y el valor se separan
con : .
Para declararlo vacío y luego ingresar los valores, se lo declara como un par de llaves sin
nada en medio, y luego se asignan valores directamente a los índices.
punto = {}
punto[’x’] = 2
for i in range(11):
print i
gallahad el puro
robin el valiente
Cuando se itera sobre una secuencia, se puede obtener el índice de posición junto a su valor
correspondiente usando la función enumerate():
32
for i, v in enumerate([’a’, ’b’, ’c’]):
print i, v
El resultado es:
0 a
1 b
2 c
Para iterar sobre dos o más secuencias al mismo tiempo, los valores pueden emparejarse con
la función zip():
Para iterar sobre una secuencia en orden inverso, se especifica primero la secuencia al derecho
y luego se llama a la función reversed(). Por ejemplo:
for i in reversed(xrange(1,10,2)):
print i
9
7
5
3
1
Para iterar sobre una secuencia ordenada, se utiliza la función sorted() la cual devuelve
una nueva lista ordenada dejando a la original intacta:
banana
manzana
naranja
pera
33
2.6. Funciones, clases y módulos
2.6.1. Funciones
En Python, la definición de funciones se realiza mediante la instrucción def más un nombre
de función descriptivo seguido de paréntesis de apertura y cierre con los parámetros necesarios
para cumplir la función al interior, separados por comas. Como toda estructura de control en
Python, la definición de la función finaliza con dos puntos y el algoritmo que la compone, irá
con indentación:
def funcion(parametro):
<algoritmo>
Una función, no es ejecutada hasta tanto no sea invocada. Para invocar una función,
simplemente se la llama por su nombre:
funcion()
Dos funciones integradas útiles para el manejo de listas son filter() y map().
La función filter(funcion,secuencia) devuelve una secuencia con aquellos ítems de la
secuencia para los cuales funcion(item) es verdadero. Esta función aplica también para strings
o tuplas. Esta función se implementa a continuación:
Con la función map(funcion, secuencia) se llama a funcion(item) por cada uno de los
ítems de la secuencia y se devuelve una lista de los valores retornados. Por ejemplo, para calcular
unos cubos:
2.6.2. Clases
Una declaración de una clase no es algo que uno pueda manipular directamente, y es necesario
instanciar un objeto de esa clase para así modificar los atributos que esta posea. Para instanciar
una clase en Python se debe asignar a una variable el nombre de la clase seguida de paréntesis:
class mascota:
numero_de_patas = 0
color = “marrón”
perro = mascota()
34
Las variables al interior de las clases se denominan atributos. Los métodos son funciones
que técnicamente se denominan métodos, y representan acciones propias que puede realizar el
objeto (y no otro). El primer parámetro de un método es un self:
class Objeto():
color = "verde"
tamanio = "grande"
aspecto = "feo"
def flotar(self):
pass
Las clases por sí mismas, no son más que modelos que sirven para crear objetos en concreto.
Una vez creado un objeto, es decir, una vez hecha la instancia de clase, es posible acceder a su
métodos y propiedades. Para ello, Python utiliza una sintaxis muy simple: el nombre del objeto,
seguido de punto y la propiedad o método al cuál se desea acceder:
objeto = MiClase()
print objeto.propiedad
objeto.otra_propiedad = "Nuevo valor"
variable = objeto.metodo()
print variable
print objeto.otro_metodo()
2.6.3. Módulos
Los módulos en Python son grupos de funciones alojadas dentro de un archivo .py. Con ellos
es posible organizar un grupo de funciones como si fueran un conjunto de herramientas las cuales
se pueden utilizar posteriormente. Los módulos son fáciles de crear, ya que son scripts de Python.
Se requiere definir una función y guardarla con la extensión .py en el mismo directorio donde
residen nuestros otros scripts. Para usar los módulos existen dos posibilidades. La primera es usar
la palabra import seguida de el nombre del archivo con extensión .py en el que se encuentra la
función. Por ejemplo, si se requiere importar un archivo llamado funciones se aplica la sintaxis:
import funciones
2.7. Ejercicios
2.7.1. Aritmética
Con este ejercicio se busca crear un programa llamado aritmetica.py que lea dos números
enteros,a y b, del usuario. El programa debe computar y visualizar la suma de a y b, la diferencia
35
cuando b se resta de a, el producto de a y b, el cociente cuando a se divide por b, el residuo
cuando a se divide por b, el resultado de log10 a y el resultado de ab . Para encontrar la función
log10 se implementa mediante el módulo math.
El código que permite resolver el ejercicio es el siguiente, que incluye todas las operaciones
aritméticas entre los números a y b:
#Se ingresan los numeros del usuario y se guardan como un string en la variable
lista
a,b= lista.split()
#Imprime la suma de a y b
print "a+b: %d"%(a+b)
#Imprime la resta de a y b
print "a-b: %d"%(a-b)
#Imprime la multiplicacion de a y b
print "axb: %d"%(a*b)
36
Cuadro 2.1: Zodiaco chino
Año Animal
2000 Dragon
2001 Serpiente
2002 Caballo
2003 Obeja
2004 Mono
2005 Gallo
2006 Perro
2007 Cerdo
2008 Rata
2009 Buey
2010 Tigre
2011 Liebre
programa llamado zodiaco.c que lea un año por parte del usuario y muestra el animal asociado
a ese año. Este ejercicio debe funcionar correctamente para cualquier año mayor que o igual a
cero, no solo los que se enumeran en el tabla.
El código que permite solucionar el ejercicio es:
if a>=0: #Si el numero ingresado es mayor que cero realiza las acciones
if a%12==0:
print "Mono" #Si el residuo de a dividido doce es cero, se imprime la palabra
#Mono
elif a%12==1:
print "Gallo" #Si el residuo de a dividio doce es 1, se imprime la palabra
#Gallo
elif a%12==2:
print "Perro" #Si el residuo de a dividio doce es 2, se imprime la palabra
#perro
elif a%12==3:
print "Cerdo" #Si el residuo de a dividio doce es 3, se imprime la palabra
#cerdo
elif a%12==4:
print "Rata" #Si el residuo de a dividio doce es 4, se imprime la palabra rata
elif a%12==5:
print "Buey" #Si el residuo de a dividio doce es 5, se imprime la palabra buey
37
elif a%12==6:
print "Tigre" #Si el residuo de a dividio doce es 6, se imprime la palabra
#tigre
elif a%12==7:
print "Liebre" #Si el residuo de a dividio doce es 7, se imprime la palabra
#liebre
elif a%12==8:
print "Dragon" #Si el residuo de a dividio doce es 8, se imprime la palabra
#dragon
elif a%12==9:
print "Serpiente" #Si el residuo de a dividio doce es 9, se imprime la palabra
#serpiente
elif a%12==10:
print "Caballo" #Si el residuo de a dividio doce es 10, se imprime la palabra
#caballo
else:
print "Obeja" #Si el residuo de a dividio doce es 11, se imprime la palabra
#obeja
else:
print "Invalid year" #Si el numero ingresado es menor que cero imprime el
#mensaje
38
# Lista que contiene las letras en minúsculas
minusculas=[’a’,’b’,’c’,’d’,’e’,’f’,’g’,’h’,’i’,’j’,’k’,’l’,’m’,’n’,’o’,’p’,
’q’,’r’,’s’,’t’,’u’,’v’,’w’,’x’,’y’,’z’]
palabra,esp= entrada.split()
# Se crea una variable para guardar las letras que forman la palabra en el
lenguaje encriptado
s=’’
39
la letra l
s=’’ # A la variable s se le asigna un string vacio
print "Caracter ll invalido" #Se imprime el mensaje
break # Se interrumpe la iteracion
else:
contadorl += 1 #Se suma uno al contador
40
del arreglo
2.7.4. Contraseña
Este ejercicio consiste en escribir una función que determine si una contraseña es buena.
Una buena contraseña es aquella que posee por lo menos 8 caracteres de longitud y contiene al
menos una letra mayúscula, al menos una letra minúscula, y al menos un número. La función
debe devolver True si la contraseña es buena. De lo contrario, debe devolver False. Por lo tanto,
se debe incluir un programa principal llamado contraseña.py que lea una contraseña del usuario
y que informe si es o no buena.
El código que permite solucionar el ejercicio es:
if contra(p)==True:
print "Good password" # Si el string p genera como resultado True en la
else:
41
print "Bad password" # De lo contrario, se imprime el mensaje "Bad password"
else:
return False #De lo contrario, se devuelve False
2.7.6. Scrabble
En el juego de ScrabbleTM, cada letra tiene puntos asociados. La puntuación total de una
palabra es la suma de las puntuaciones de sus letras. las letras más comunes tienen menos puntos
mientras que las letras menos comunes valen más puntos. Los puntos asociados con cada letra
se muestran en el cuadro 2.2.
Este ejercicio consiste en escribir un programa llamado scrabble.py que calcule y muestre la
puntuación de Scrabble para una palabra escogida por el usuario. Para ello, se debe crear un
diccionario que mapee de las letras a los valores. A continuación, se debe utilizar el diccionario
para calcular la puntuación.
El código que permite solucionar el ejercicio es:
42
Cuadro 2.2: Puntos Scrabble
Puntos Letras
1 A,E,I,L,N,O,R,S,T,U
2 D,G
3 B,C,M,P
4 F,H,V,W,Y
5 K
8 J,X
10 Q,Z
for letter in entrada: #por cada letra que se ingresa, se guarda la suma de puntajes
en la variable cont
cont += dic[letter]
print "score: %d"%cont # Se imprime el mensaje total
43
2.7.7. Elementos químicos
Otro juego que algunas personas juegan con los nombres de los elementos químicos implica
la construcción de una secuencia de elementos, donde cada elemento de la secuencia comienza
con la última letra de su predecesor. Por ejemplo, si una secuencia comienza con hidrógeno,
entonces el siguiente elemento debe ser un elemento que comienza con N, tal como níquel. El
siguiente elemento de níquel debe comenzar con L, tal como litio. La secuencia de elementos
que se construye no puede contener duplicados.
Este ejercicio consiste en escribir un programa tablaperiodica.py que lea el nombre de un
elemento por parte del usuario. El programa debe utilizar una función recursiva para encontrar
la secuencia más larga de elementos que comienza con el elemento introducido. Entonces se
debe mostrar la secuencia.El programa debe responder de una manera razonable si el usuario
no introduce un nombre de elemento válido.
Puede tomar el programa hasta dos minutos para encontrar la secuencia más larga para
algunos elementos. Como resultado, es posible que la implementación de elementos como el
molibdeno y el magnesio como sus casos de la primera prueba sea preferible. Cada uno tiene
una secuencia más larga que se encuentra a sólo 8 elementos de largo que el programa debe
buscar en una fracción de segundo.
Con base en lo anterior, el código que permite solucionar el ejercicio es:
elementos=[’hidrogeno’,’helio’,’litio’,’berilio’,’boro’,’carbono’,’nitrogeno’,
’oxigeno’,’fluor’,’neon’,’sodio’,’magnesio’,’aluminio’,’silicio’,’fosforo’,
’azufre’,’cloro’,’argon’,’potasio’,’calcio’,’escandio’,’titanio’,’vanadio’,
’cromo’,’manganeso’,’hierro’,’cobalto’,’niquel’,’cobre’,’zinc’,’galio’,
’germanio’,’arsenico’,’selenio’,’bromo’,’kripton’,’rubidio’,’estroncio’,’itrio’,
’zirconio’,’niobio’,’molibdeno’,’tecnecio’,’rutenio’,’rodio’,’paladio’,’plata’,
’cadmio’,’indio’,’estano’,’antimonio’,’telurio’,’yodo’,’xenon’,’cesio’,’bario’,
’lutecio’,’hafnio’,’tantalio’, ’wolframio’,’renio’,’osmio’,’iridio’,’platino’,
’oro’,’mercurio’,’talio’, ’plomo’,’bismuto’,’polonio’,’astato’,’radon’,’francio’,
’radio’,’laurencio’, ’rutherfordio’,’dubnio’,’seaborgio’,’bohrio’,’hassio’,
’meitnerio’, ’darmstadio’,’roentgenio’,’copernicio’,’ununtrio’,’flerovio’,
’unumpentio’, ’livermorio’,’ununseptio’,’ununoctio’,’lantano’,’cerio’,
’praseodimio’, ’neodimio’,’prometio’,’samario’,’europio’,’gadolinio’,’terbio’,
’disprosio’, ’holmio’,’erbio’,’tulio’,’iterbio’,’actinio’,’torio’,’protactinio’,
’uranio’, ’neptunio’,’plutonio’,’americio’,’curio’,’berkelio’,’californio’,
’einstenio’, ’fermio’,’mendelevio’,’nobelio’] # Lista de elementos
44
sec=[p] # Lista para guardar las palabras que se imprimiran
if cont==0:
sec.append(el)
return str(palabra) #Si la secuencia acaba, agrega la ultima palabra
a la lista de elementos que se imprimen y
retorna esta palabra
45
else:
print "Elemento invalido" #De lo contrario imprime el mensaje
46
3
IPython es un intérprete interactivo similar al que proporciona Python pero que incorpora
numerosas mejoras. Su desarrollo fue concebido como una herramienta personal para facilitar
el trabajo científico aplicado al análisis de datos y a la computación paralela. Este intérprete
permite ejecutar directamente código Python y depurarlo. Asimismo, es posible ejecutar cualquier
comando del sistema operativo o scripts escritos en otros lenguajes como Bash. La instalación
y uso es completamente gratuito, por lo que para el empleo de estos documentos no existe la
necesidad de adquirir una licencia.
Entre las mejores de IPython con respecto a Python se encuentran:
Además de estas mejoras, IPython cuenta con "Notebook", una interfaz en el formato de
la web que permite registrar las sesiones de trabajo en çuadernos compartirlos con otras
2
personas, convertidos incluso en otros formatos como HTML y PDF. En estos cuadernos se
pueden incluir bloques de código o comandos con el resultado de su ejecución, expresiones
matemáticas, fórmulas, gráficos, texto formateado y elementos multimedia.
Para ejecutar un cuaderno de IPython desde la consola se debe escribir ipython notebook
y en seguida se escribe el nombre del archivo con extensión .ipynb". Las librerías de IPython
más relevantes en el área de ciencias son:
47
Otras librerías ampliamente utilizadas son PyLab y Matplotlib, que proporciona un entorno
muy parecido a MATLAB, lo que facilita enormemente la computación científica con Python.
Para tratar cualquier ejercicio se puede implementar el entorno PyLab, el cual, una vez
ejecutado, carga en el sistema las funciones más utilizadas y permite escribir código de una
manera similar a MATLAB. Para que las figuras ejecutadas en el cuaderno aparezcan en el
mismo documento y no en una ventana aparte es necesario utilizar el comando inline. Así, se
escribe al comienzo del cuaderno:
%pylab inline
3.1. Numpy
Numpy es una extensión de Python, que le agrega mayor soporte para vectores y matrices,
constituyendo una biblioteca de funciones matemáticas de alto nivel para operar con esos
vectores o matrices. Numpy es un paquete fundamental para la computación científica con
Python y contiene:
Arreglos N-dimensionales.
Sofisticadas funciones.
Herramientas para la integración de C / C ++ y Fortran.
Álgebra lineal útil, transformada de Fourier, y capacidades de números aleatorios.
import numpy as np
Con esto, se importa todo numpy dentro del namespace np. Esto es una abreviatura de hacer
import numpy
Las funciones incluidas en la librería se implementan con el prefijo numpy o el np, es decir,
los dos nombres son equivalentes.
3.1.1. Arreglos
Para crear un arreglo con determinados valores lo más normal es generarlo a partir de una
lista. El siguiente ejemplo imprime un arreglo con cuatro elementos:
>>> a=np.array([1,2,3,4])
>>> print a
[1 2 3 4]
Con base en lo anterior, la creación de arreglos se lleva a cabo mediante la librería numpy:
np.array([<lista delimitada por comas>]). La lista se escribe al interior de corchetes y en
el ejemplo anterior el arreglo se asigna a una variable x. El arreglo creado en el ejemplo anterior
es de una dimensión. Con la clase array es posible crear arreglos de dos y tres dimensiones
mediante la siguiente sintaxis:
48
np.array([<Lista 1>],[<Lista 2>]) # 2D
np.array ([[<Lista 1>],[<Lista 2>]], [[<Lista 3>],[<Lista 4>]]) # 3D
>>> d=np.array([[1,2,3],[4,5,6]])
>>> print d
[[1 2 3]
[4 5 6]]
Para llamar un elemento de un arreglo se indica la posición del elemento que se desea obtener
al interior de corchetes. Así, si se desea obtener el primer elemento se utiliza el número cero,
como se presenta en el siguiente ejemplo:
>>> f=np.array([[[1,2,3],[3,4,5]],[[6,6],[7,7]]])
>>> print f[0] #n dimension, f filas, c colomnas
[[1, 2, 3] [3, 4, 5]]
Adicionalmente, los arreglos de numpy permiten cambiar el tipo de los elementos del arreglo,
implementando la palabra dtype después de la lista de elementos contenidos en el arreglo:
Con este código se establecen elementos de tipo complejo al interior del arreglo c y elementos
de tipo float en el arreglo d.
Adicionalmente, por medio de la librería numpy se pueden crear arreglos de ceros y de unos,
especificando el numero de dimensiones del arreglo, el número de filas y el número de columnas:
Por otro lado, los arreglos pueden ser creados por medio de la implementación de np.arange,
que permite obtener un arreglo de números que tienen un valor mínimo, un valor máximo y un
aumento específicos. En el siguiente ejemplo, se obtiene un arreglo de números que aumentan
cada 5 unidades y que tienen un valor mínimo de 10 y un valor máximo (los elementos son
estrictamente menores a este valor) de 30:
49
>>> a=np.arange( 10, 30, 5 ) #min, #max, #aumenta
>>> print a
[10 15 20 25]
Otro comando útil para crear arreglos unidimensionales es linspace, que requiere que se
especifique el límite inferior de los elementos, el límite superior y el numero de divisiones entre
ambos límites:
Por último, existen múltiples comandos que permiten conocer la información de un arreglo,
como se observa en el siguiente ejemplo:
>>> a = np.arange(15).reshape(3, 5)
>>> print a
>>> print a.shape #el tamaño del arreglo en cada dimensión, filas columnas
>>> print a.ndim #las dimensiones del arreglo
>>> print a.dtype.name #el tipo de variables dentro del arreglo
>>> print a.itemsize #El tamaño en bytes de cada elemento del arreglo
>>> print a.size #El número total de elementos de la matriz.
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
(3, 5)
2
int32
4
15
50
Con base en esto, las operaciones de suma, resta, multiplicación y división entre arreglos se
llevan a cabo elemento a elemento. Sin embargo, para arreglos en dos dimensiones es de gran
utilidad realizar operaciones como la multiplicación matricial. Para ello se puede implementar
el comando np.dot :
randn(d0, d1, ..., dn) Retorna elementos de una distribución estándar normal.
Así, para simplificar operaciones se escribe el operador que se quiere utilizar y en seguida se
escribe el símbolo =. Por otra parte, se pueden aplicar operaciones elementales sobre arreglos,
como:
51
print a.min() encuentra el mínimo del arreglo a
Cada una de estas funciones se puede aplicar para arreglos en una dimensión. Para arreglos
en más dimensiones se puede especificar una fila o columna y aplicar las funciones presentadas
anteriormente. Para entender mejor la implementación de estas funciones sobre arreglos bidimensionales,
se presenta el siguiente ejemplo:
>>> b = np.arange(12).reshape(3,4)
>>> print b
>>> print b.cumsum(axis=1) #suma cumulativa
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[ 0 1 3 6]
[ 4 9 15 22]
[ 8 17 27 38]]
print a[2] a[i] muestra el elemento que se encuentra en el indice i del arreglo a
print a[i:j:k] muestra los elementos del i al j cada k espacios del arreglo a
Para obtener los elementos de un arreglo separados, se puede implementar el bucle for de la
siguiente forma:
>>> b=[0,1,2,3]
>>> for element in b.flat:
>>> print(element)
0
1
2
3
Adicionalmente, se pueden implementar arreglos con índices para obtener los elementos de
los arreglos o se pueden implementar arreglos con booleanos. En el primer caso, se especifican los
índices que corresponden a las posiciones de los elementos en un arreglo. Estos índices pueden
ser un arreglo, a su vez, y el siguiente ejemplo permite evidenciar la implementación:
52
Figura 3.1: Imagen de Homero que se busca modificar
En el caso de arreglos con booleanos, es necesario que exista una condición para que se
aplique sobre un arreglo. Como resultado se obtienen únicamente los elementos que cumplen la
condición dada. Otra forma de implementar los booleanos es incluir el arreglo en la condición
dada, con lo que se obtiene un arreglo que imprime True en las posiciones del arreglo con
elementos que cumplen la condición. De lo contrario, aparece la palabra False:
>>> a = np.arange(12).reshape(3,4)
>>> b = a > 4
>>> print b
>>> print a[b]
[[False False False False]
[False True True True]
[ True True True True]]
[ 5 6 7 8 9 10 11]
3.2. Ejercicio
Con el ejercicio se busca obtener una parte de la imagen de Homero que se visualiza en la
figura 3.1.
El resultado que se desea obtener es un círculo que encierre la cara de Homero, exactamente
como aparece en la figura 3.2.
53
Figura 3.2: Imagen de Homero modificada
%pylab inline
54
3.3. Matplotlib
Matplotlib es una librería de Python que permite realizar gráficas 2D de excelente calidad.
Es multiplataforma y puede ser usada desde scripts o desde la consola de Python. Matplotlib
fue creado tomando como base a Matlab y se pueden exportar las gráficas en los formatos de
imágenes mas populares e incluso a formato latex para su inclusión en artículos científicos.
%pylab
Con este módulo importado, se puede generar gráficos que implementen funciones de esta
librería. Si adicionalmente se desea visualizar gráficas en el mismo cuaderno de IPython, se
puede adicionar la palabra inline al comando anterior:
%pylab inline
Sin embargo, las animaciones y widgets no se pueden visualizar al aplicar este comando
y, en su lugar, se debe escribir únicamente %pylab. Un tipo de gráficos que se puede realizar
utilizando esta librería son los gráficos de dispersión. Para ello se debe
55
Figura 3.3: Gráfico de dispersión
3.3.3. plt.plot
Para realizar una gráfica con una línea continua, se implementa plt.plot(<arreglo x> ,<arreglo y>).
Como en el caso de las gráficas de dispersión, se deben especificar las coordenadas en x y en y
en arreglos. Con el siguiente código se genera la misma gráfica del ejemplo anterior, pero con
una línea continua:
b: blue
g: green
r: red
c: cyan
56
Figura 3.4: Gráfica de función cuadrática
m: magenta
y: yellow
k: black
w: white
Así, para obtener una gráfica de color rojo se debe adicionar una coma y la letra correspondiente
entre comillas (’r’) en seguida de los arreglos con las coordenadas x y las coordenadas y al
interior de la función plt.plot().
Para cambiar el estilo de línea, se debe adicionar una coma en la función plt.plot y el
simbolo del estilo de línea o marcador que se desea implementar entre comillas, al igual que se
realiza para cambiar un color. Los marcadores y estilos de línea que se pueden utilizar son:
Estilos de Líneas:
-, Línea Sólida
:, Línea punteada
Marcadores:
+, Cruz
., Punto
o,Círculo
*, Estrellas
57
Figura 3.5: Gráfica de función cuadrática y función cúbica
p, Pentágonos
s, cuadrados
x, Tachados
D, Diamantes
h, Hexágonos
^, Triángulos
Con el siguiente código se generan dos funciones en una misma gráfica mediante el comando
plt.plot. Se pueden generar más gráficas adicionando este comando nuevamente con los
arreglos correspondientes. En este caso, los arreglos generan las coordenadas de una función
cuadrática y de una función cúbica con distintos estilos de línea y colores:
58
Figura 3.6: Gráfica de función cuadrática y función cúbica con rótulos, título y leyenda
la primera de estas funciones se adiciona un rótulo al eje x y con la segunda función se adiciona
un rótulo al eje y. Al igual que para los títulos, se debe escribir el rótulo entre los paréntesis de
la función y entre comillas.
Si se desea adicionar una leyenda en la gráfica, se puede implementar la función plt.legend().
En contraste con las funciones anteriores, no se adiciona texto al interior de los paréntesis. Sin
embargo, se debe adicionar texto en la función plt.plot(). Para ello, después de especificar
los arreglos, el color y el estilo de línea se debe adicionar el código label=’’ después de una
coma. Al interior de las comillas se escribe el texto que aparecerá en la leyenda de la gráfica.
El siguiente ejemplo implementa estas funciones en la gráfica 3.5 para mostrar la sintaxis
utilizada:
59
3.3.6. Otros tipos de gráficos
Para dibujar un histograma podemos hacer uso de plt.hist. Un histograma es un gráfico de
barras donde se representa la ocurrencia de datos (frecuencia) en intervalos definidos. Lo que
hace plt.hist es dibujar el histograma de un vector en función del número de intervalos (bins).
El siguiente código genera un histograma con números que siguen una distribución normal
aleatoria:
data = np.random.randn(1000)
plt.hist(data , bins=30, normed=True, color=’b’)
plt.show()
Otro tipo de diagrama que se puede crear gracias a la librería de Matplotlib es Box plot,
que es un diagrama donde se puede ver un resumen de una serie de forma rápida y sencilla. En
él se representa el primer cuartil y el tercer cuartil, que son los extremos de la caja, el valor de
la mediana (o segundo cuartil), que se representa mediante una línea dentro de la caja, y los
extremos de la serie que no se consideran anómalos, que son los valores extremos. Para ilustrar
este tipo de diagramas se presenta el siguiente ejemplo:
60
Figura 3.8: Diagrama boxplot
Para graficar arreglos de numpy como imágenes en matplotlib, ya sea mediante importación
o mediante la generación del arreglo, se utiliza la función imshow(). Al interior de los paréntesis
se escribe el nombre del arreglo que se desea visualizar como imagen. En el siguiente ejemplo
se visualiza un arreglo de cien filas y cien columnas con números aleatorios entre 0 y 1:
A = np.random.random((100, 100))
plt.imshow(A)
plt.savefig(’imshow.png’)
61
Figura 3.9: Gráfica de arreglo aleatorio con imshow
62
hablando, las líneas de corriente son líneas continuas cuyas tangentes en cada punto están
dadas por un campo vectorial. Cada línea, y por lo tanto las líneas de corriente, pueden ser
parametrizadas. En el siguiente ejemplo se genera el streamplot() que se observa en la figura
3.11:
Y, X = np.mgrid[-3:3:15j, -3:3:15j]
U = -1 - np.cos(X**2 + Y)
V = 1 + X - Y
speed = np.sqrt(U**2 + V**2)
plt.streamplot(X, Y, U, V, color=speed, cmap=cm.cool, linewidth=2, arrowstyle=’->’,
arrowsize=1.5)
plt.show()
La función np.mgrid() genera las coordenadas que se implementan para dibujar el campo
vectorial. Los arreglos X y Y son los puntos base de cada flecha y los arreglos U y V son los
puntos finales.
Otro tipo de gráficos que se pueden generar con matplolib son los gráficos de contorno. Para
ello, se implementa la función plt.contour(). Al interior de los paréntesis se deben escribir los
tres arreglos que generan la gráfica de contornos, como se presentan en el siguiente ejemplo:
with xkcd():
fig=figure(figsize=(10,10))
x=linspace(-1,1,100)
y=linspace(-1,1,100)
xg, yg = meshgrid(x,y)
contour(x,y,4*xg**3*yg-4*xg*yg**3)
63
La figura 3.12 presenta la gráfica de contornos resultante.
Con la función xkcd() se modifican los elementos del diagrama para que parezcan dibujados
a mano.
Adicionalmente, mediante la librería de matplotlib se pueden adicionar widgets. Los más
comunes son:
deslizadores
barras de progreso
cajas de texto
áreas de visualización
# Modificado de http://matplotlib.org/examples/widgets/slider_demo.html
from matplotlib.widgets import RadioButtons
64
%pylab
#%matplotlib osx No puede ser inline
rcdefaults() # No works with xkcd style
fig, ax = subplots()
subplots_adjust(left=0.25, bottom=0.25) #posición de la gráfica
t = arange(0.0, 1.0, 0.001)
a0 = 5 # Amplitud inicial
f0 = 3 # Frecuencia inicial
s = a0*sin(2*np.pi*f0*t) # Función a graficar
l, = plot(t,s, lw=2, color=’red’)
axis([0, 1, -10, 10]) # Fijar los ejes
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle() #dibuja en
button.on_clicked(reset)
radio.on_clicked(colorfunc)
show()
65
Figura 3.13: Gráfica de función sinusoidal con widgets
66
3.3.7. Animaciones
Para generar animaciones en IPython es necesario importar algunas funciones, adicionando
las siguiente líneas en el cuaderno de IPython:
Ahora se presentarán algunos ejemplos de animaciones que se pueden realizar por medio de
estas funciones.
Un primer ejemplo es la animación del movimiento de tres planetas que siguen trayectorias
que se encuentran en el archivo
%pylab
from matplotlib import animation
%matplotlib osx
fig=figure()
ax = fig.add_subplot(111, autoscale_on=False, xlim=(-1.5, 1.5), ylim=(-1.0, 1.0))
ax.grid(True) # Add grid
ax.set_aspect(’equal’)
completetrayectory = ax.plot(x1,y1,"--k",alpha=0.4)
title("Gravitational Choreography\n")
legend()
time_template = ’time = %.2f’
time_text = ax.text(0.05, 0.9, ’’, transform=ax.transAxes)
dt = 0.05
67
def animate(i):
if( i<10 ):
mini=0
else:
mini=i-10
thisx1 = x1[mini:i]
thisy1 = y1[mini:i]
thisx2 = x2[mini:i]
thisy2 = y2[mini:i]
thisx3 = x3[mini:i]
thisy3 = y3[mini:i]
trayectory1.set_data(thisx1, thisy1)
trayectory2.set_data(thisx2, thisy2)
trayectory3.set_data(thisx3, thisy3)
planet1.set_data([x1[i]],[y1[i]])
planet2.set_data([x2[i]],[y2[i]])
planet3.set_data([x3[i]],[y3[i]])
time_text.set_text(time_template%(i*dt))
return trayectory1, trayectory2, trayectory3, planet1, planet2, planet3, time_text
68
Figura 3.14: Interfaz de animación de la trayectoria de planetas
fig=figure()
tplot=imshow(placa,interpolation=’None’)
colorbar()
def animate(i):
global placa
tempplaca=copy(placa)
for i in range(1,side-1):
for j in range(1,side-1):
tempplaca[i,j]=0.25*(placa[i-1,j]+placa[i+1,j]+placa[i,j-1]+placa[i,j+1])
tplot.set_array(tempplaca)
placa=tempplaca
return placa,
69
Figura 3.15: Interfaz de animación de placa metálica
En el segundo caso, se debe crear un arreglo que puede ser modificado con un bucle, ya
sea un bucle while o un bucle for. Con la función animation.ArtistAnimation() se llama el
arreglo creado previamente:
70
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
while placa[side/2,side/2]<0.9:
tempplaca=copy(placa)
for i in range(1,side-1):
for j in range(1,side-1):
tempplaca[i,j]=0.25*(placa[i-1,j]+placa[i+1,j]+placa[i,j-1]+placa[i,j+1])
im1=ax1.imshow(tempplaca,interpolation=’None’,vmin=0,vmax=1)
ax2.set_xlabel("x")
ax2.set_ylabel("T")
ax2.set_xlim(1,side)
#ax2.plot(range(1,side+1),tempplaca[:,side/2],"ro")
im2,=ax2.plot([0,side],[0.9,0.9],"-k")
im3,=ax2.plot(range(1,side+1),tempplaca[:,side/2],"ro")
placa=tempplaca
ims.append([im1,im2,im3])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=False)
show(fig)
La interfaz del resultado generado con este código se observa en la figura 3.16.
El último ejemplo que se presentará es el de un conjunto de átomos que chocan con las
paredes y entre sí. Para ello, se implementa el siguiente código:
%matplotlib osx
rcdefaults()
from matplotlib import animation
71
num_atoms=361
fig=figure()
ax=fig.add_subplot(1,1,1,autoscale_on=False, xlim=(-0.1, 59), ylim=(-0.1, 41))
ax.grid(True)
ax.set_aspect(’equal’)
ims=[]
for i in range(len(x)/num_atoms):
thisx = x[num_atoms*i:num_atoms*i+num_atoms]
thisy = y[num_atoms*i:num_atoms*i+num_atoms]
im1,=ax.plot(thisx,thisy,"ok",ms=5)
im2=ax.text(5,38,"N="+str(i*50),horizontalalignment=’center’)
ims.append([im1,im2])
72
3.4. Ejercicios
3.4.1. Funciones
Este ejercicio consiste en graficar las siguientes funciones en el intervalo [0,2]
2
f (x) = sin2 (x − 2)e−x
f (x) = cos(x)
Adicional a las gráficas, se adicionan rótulos, leyenda y se guarda la función en un archivo
en formato .pdf.
El código que soluciona el ejercicio es el siguiente:
#Se generan 100 numeros entre 0 y 2, que son los valores en el eje x
x=linspace(0,2,100)
#Se grafica
plt.plot(x,y1,’b’,label=’$sin(x-2)^2 e^{-x^2}$’)
plt.plot(x,y2,’r’,label=’$cos(x)$’)
73
F(x) vs x
1.0 sin(x − 2) 2 e −x 2
cos(x)
0.5
F(x)
0.0
0.5
0.0 0.5 1.0 1.5 2.0
x
#tamaño de la figura
plt.figure(figsize=(10,10))
#Título general
plt.suptitle(’Curvas de lissajous’, fontsize=14)
#Funcion x
x= sin(t)
for i in range(6):
#FUncion y
y= sin((i+1)*t + i*pi/4)
#Grafica de x contra y
plot(x,y, label= ’Curva de lissajous %d’%i)
#Desface entre x y Y
desface=(i)*pi/4
74
#título de cada gráfica que incluye la razon entre la frecuencia en x
y la frecuencia en Y, junto con el desface
title(’Razon de frecuencias=%d, desface= %.2f’%(i+1,desface))
legend() #Leyenda
show()
#Importar modulos:
%pylab
from matplotlib import animation
%matplotlib osx
rcdefaults()
from mpl_toolkits.mplot3d import Axes3D
# Propiedades de la gráfica
fig = figure()
ax = fig.gca(projection=’3d’) # Grafica en 3D
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z") # leyenda de cada eje
# variable que indica el numero de veces que avanzan las partículas en total
t=linspace(0,200,1000)
#Primera posición
par1,=ax.plot(x,y,z,"ok",ms=5)
for j in range(len(t)):
for i in range(len(x)):
aleatorio1= random.random()
aleatorio2= random.random()
aleatorio3= random.random() #NUmeros aleatorios
75
Figura 3.19: Curvas de lissajous con distintas frecuencias y desfaces
76
if aleatorio1>0.5: #Si el numero aleatorio es mayor que 0.5 la particula
se mueve una posición hacia los numeros positivos
x[i] = x[i]+1
else: #Si el numero aleatorio es menor, se mueve hacia los
numeros negativos
x[i]= x[i]-1
if aleatorio2>0.5: #Si el numero aleatorio es mayor que 0.5 la particula
se mueve una posición hacia los numeros positivos
y[i]= y[i]+1
else: #Si el numero aleatorio es menor, se mueve hacia los
numeros negativos
y[i]= y[i]-1
if aleatorio3>0.5: #Si el numero aleatorio es mayor que 0.5 la particula
se mueve una posición hacia los numeros positivos
z[i]= z[i]+1
else: #Si el numero aleatorio es menor, se mueve hacia los
numeros negativos
z[i]= z[i]-1
par,=ax.plot(x,y,z,"ok",ms=5) #Se guardan las nuevas posiciones como imagenes
ims.append([par]) #Se agregan al arreglo de todas las posiciones
show(fig)
77
Figura 3.20: Interfaz de la animación de caminata aleatoria en 3D
78
4
#include <stdio.h>
int main(void)
{
return 0;
}
Para incluir las funciones estándar de C se escribe #include <stdio.h>. El código int main(void)
identifica la función main. Toda función inicia con un corchetes y todo programa debe contener
la función Main. Int es el tipo de variable que se devuelve y Void indica que no existen
parámetros de entrada. Al interior de los corchetes y con tabulación se escriben los comandos
que se desean llevar a cabo, es decir, es en esta sección donde se escriben las líneas de código.
Además, la función de return 0 es devolver el control al Sistema operativo. Cabe resaltar que
al final de cada línea de código debe aparecer un símbolo de punto y coma ; .
Si se desean adicionar comentarios en el script, se debe adicionar doble slash (//) si los
comentarios ocupan una sola línea del script. De lo contrario, se debe adicionar el símbolo /*
al inicio del comentario y el símbolo */ al final del comentario.
Para adicionar algunos caracteres especiales o espacios en variables tipo string existen las
secuencias de escape, que incluyen:
79
\n Adiciona un carácter en una nueva linea
\b Representa un backspace
\\ Inserta un backslash
4.2. Variables
Una variable es una pieza específica de memoria del computador. Cada Variable tiene un
nombre, que pueda recuperar el valor de la variable. Primero hay que declarar el tipo de variable
para determinar la memoria que se le va asignar a la variable. El siguiente ejemplo implementa
una variable llamada edad de tipo int:
#include <stdio.h>
int main(void)
{
int edad;
edad=29;
printf(“Mi edad es %d \n”, edad);
return 0;
}
80
Con este código se obtiene el mensaje Mi edad es 29. Las operaciones aritméticas básicas
que se pueden llevar a cabo entre variables son:
+ Adición
- Sustracción
/ División
* Multiplicación
% Residuo
short int entero corto con rango de valores entre -32768 y 32767
long int entero largo con rango de valores entre -2147438648 y 2147438648
long long int entero con rango de valores entre -9223372036854775808 a 9223372036854775807
unsigned int entero sin signo con rango de valores entre 0 y 4294967295
El siguiente código implementa cada una de estas variables para imprimirlas en un mensaje:
#include <stdio.h>
int main(void)
{
signed char a;
short int b;
int c;
long int d;
long long int e;
a=127;
b=32767;
c=2147438647;
d=2147438647L;
e=9223372036854775806LL;
81
4.2.2. Variables de tipo float
Los tipos de variables de tipo float más comunes en C son:
Con el siguiente código se generan estos tres tipos de variables float en un mensaje:
#include <stdio.h>
#include <stddef.h> //#include <string.h>
int main(void)
{
/*cadena de caracteres */
char *message="Este es su mensaje";
wchar_t *wmessage=L"Este es un mensaje super largo";
char s[81]="Hola mundo";
printf("%s\n",message);
printf("%ls\n",wmessage);
printf("%s\n",s);
return 0;
}
82
Es necesario agregar al código #include <stddef.h> //#include <string.h> para que
sea posible crear las variables tipo carácter. Con este código se obtiene:
Este es su mensaje
Este es un mensaje super largo
Hola mundo
#include <stdio.h>
#define PI 3.14159265f
int main(void)
{
float diametro, radio, perimetro,area;
radio=diametro/2.0;
perimetro=2.0*PI*radio;
area=PI*radio*radio;
83
ceil(x) Retorna el numero entero más pequeño que no es menor que x
cos(x) coseno de x
tan(x) tangente de x
#include <complex.h>
#include <stdio.h>
int main(void)
{
double complex cx = 1.0 + 3.0*I;
double complex cy = 1.0 - 4.0*I;
double complex sum= cx+cy;
double complex resta= cx-cy;
double complex multiplicacion= cx*cy;
double complex division= cx/cy;
double complex conjugado= conj(cx);
Con base en el ejemplo, se puede evidenciar que es necesario llamar la parte real y la parte
imaginaria por separado cuando se quiere imprimir el mensaje. Para ello, se escribe el nombre
de la variable al interior de los paréntesis del comando creal() para la parte real y al interior
de cimag() para la parte imaginaria. Con este código se imprime en la terminal
84
Operaciones con numeros complejos:
cx = 1.00+3.00i , cy = 1.00-4.00i
La suma cx + cy = 2.00-1.00i
La resta cx - cy = 0.00+7.00i
La multiplicacion cx * cy = 13.00-1.00i
La division cx /cy = -0.65+0.41i
El conjugado cx = 1.00-3.00i
4.5. Condicionales
Para llevar a cabo condicionales se implementa la siguiente sintaxis:
if(<condicion 1>)
<accion a realizar>
else if(<condicion 2>)
<accion a realizar>
else
<accion a realizar>
== != igual o no igual
|| .O"lógico
Para ejemplificar los condicionales, se presenta el siguiente código que permite saber si un
número es mayor, menor o igual a 10:
#include <stdio.h>
int main(void)
{
float numero;
printf("Ingrese un numero:\n");
scanf("%f",&numero);
if(numero>10.0)
printf("El numero %.2f es mayor que 10\n ",numero);
else if(numero<10.0)
85
printf("El numero %.2f es menor que 10\n ",numero);
else
printf("El numero que ingresaste es igual a 10\n");
return 0;
}
4.6. Bucles
4.6.1. bucle for
El bucle for se implementa de una forma muy similar a la que se implemente en la terminal
con los comando básicos de Linux. A continuación se presente un ejemplo:
#include <stdio.h>
int main(void)
{
int n,i,j;
for(n=1;n<=10;n++)
printf("%d\n",n);
for(i=3,j=1;j<=10;j++)
printf("%d x %d=%d\n",i,j,i*j);
return 0;
}
Como se observa, se realiza una iteración sobre los números que van desde n=1 hasta n<=10,
con n aumentando una unidad con cada iteración.
#include <stdio.h>
int main(void)
{
int i=1;
while(i<=10){
printf("%d\n",i);
i++;
}
return 0;
}
86
4.7. Ejercicios
4.7.1. Distancia en kilómetros
El objetivo de este ejercicio es escribir un programa distancia.c que calcule la distancia en
kilómetros entre dos pares de ciudades dada su longitud y latitud. Para solucionar el ejercicio
se implementa el siguiente código:
#include <stdio.h>
int main(void)
{
float lon1; //variable con la longitud del primer punto
float lat1; //variable con la latitud del primer punto
float lon2; //variable con la longitud del segundo punto
float lat2; //variable con la latitud del segundo punto
float r=6371; //Radio de la tierra
printf("Ingrese longitud 1: "); //Se pide la longitud 1
scanf("%f",&lon1); //Se guarda la longitud en lon1
printf("Ingrese latitud 1: "); //Se pide la latitud 1
scanf("%f",&lat1); //Se guarda la latitud en lat1
printf("Ingrese longitud 2: "); //Se pide la longitud 2
scanf("%f",&lon2); //Se guarda la longitud en lon2
printf("Ingrese latitud 2: "); //Se pide la latitud 2
scanf("%f",&lat2); //Se guarda la latitud en lat2
float x1=r*sin(lat1)*cos(lon1); //Coordenada x del punto 1
float x2=r*sin(lat2)*cos(lon2); //Coordenada x del punto 2
float y1=r*sin(lat1)*sin(lon1); //Coordenada y del punto 1
float y2=r*sin(lat2)*sin(lon2); //Coordenada y del punto 2
float z1=r*cos(lat1); //Coordenada z del punto 1
float z2=r*cos(lat2); //Coordenada z del punto 2
float dis= sqrt(pow(x1-x2,2)+pow(y1-y2,2)+pow(z1-z2,2)); //Distancia
printf("La distancia es %f\n",dis); //Imprime la distancia
return 0;
}
87
Cuadro 4.1: Zodiaco chino
Año Animal
2000 Dragon
2001 Serpiente
2002 Caballo
2003 Obeja
2004 Mono
2005 Gallo
2006 Perro
2007 Cerdo
2008 Rata
2009 Buey
2010 Tigre
2011 Liebre
88
printf("Buey\n");
89
90
5
5.1. Arrays
Los arreglos permiten almacenar vectores y matrices. Los arreglos unidimensionales sirven
para manejar vectores y los arreglos bidimensionales para matrices. Sin embargo, las matrices
también se pueden almacenar mediante arreglos unidimensionales. La palabra unidimensional
no indica que se trata de vectores en espacios de dimensión uno; indica que su manejo se hace
mediante un subíndice. Los arreglos bidimensionales, que son los arreglos multidimensionales
más comunes, se manejan mediante dos subíndices. Más adelante se presentarán los punteros,que
permiten almacenar matrices también.
El siguiente ejemplo implementa un arreglo para imprimir los números del 1 al 9:
#include <stdio.h>
int main(void)
{
int a[10],i;
for(i=0;i<=9;i++){
a[i]=i;
printf("%d\n",a[i]);
printf("a[%d] address: %p contiene:%d\n",i,&a[i],a[i]);
}
return 0;
}
91
sus elementos1 :
int main()
{
int numero[5] = /* Un array de 5 números enteros */
{200, 150, 100, -50, 300};
int suma; /* Un entero que será la suma */
return 0;
}
while
do... while
for
5.1.4. Posición
Para leer y/o modificar algun caracter se llama a la posisicon del arreglo declarado como se
muestra a continuaci]ón:
1
Tomado dehttp://www.nachocabanes.com/c/curso/cc05.php
92
int main()
{
char x [40]; /* Para guardar hasta 39 letras */
return 0;
}
Leer la cadena desde el inicio hasta encontrar el caracter nulo que maracara el final.
#include <stdio.h>
#include <string.h>
int main()
{
char texto[40];
return 0;
}
Al emplear “strlen”, o cualquier tipo de órdenes relacionadas con cadenas de texto que se debe
incluir la biblioteca incluir <string.h>, que es donde se definen todas ellas.
93
strcpy:Guarda en una cadena de texto un cierto valor con sus respectivos parametros
strcpy (destino, origen);
strncpy:Guarda en una cadena de texto un cierto valor con sus respectivos parametros,
pero asigna la cantidad de bytes del origen strcpy (destino, origen, n); .
strcat:Añade una cadena al final de otra (concatenarla).Sus parametros son strcat (destino,
origen); .
94
Si dos cadenas empiezan por la misma letra (o las mismas letras), se ordenan basándose
en la primera letra diferente.
De este modo el siguiente codigo dara un representacion general de la funcion del comando
strcmp
*buscar un string*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[]="The quick brown fox";
char c=’q’;
char *pGot_char=NULL;
pGot_char=strchr(str,c);
if(pGot_char != NULL)
printf("La palabra %c fue encontrada en %s\n", *pGot_char, str);
95
Función Valores
isalnum() (A - Z o a - z) o (0 - 9)
isalpha() (A - Z o a - z)
isascii() 0-127
isdigit() (0-9)
isgraph() cualquier valor menos ’ ’
islower() (a-z)
isupper() (A-Z)
isprint() Todos los valores
ispunct() Signos de puntuaion
isspace() espacio, tab, retorno de línea, cambio de línea, tab vertical, salto de página
isxdigit() (0 - 9, A - F, a - f)
Listas:En las que se puede añadir elementos, consultarlos o borrarlos en cualquier posición.
5.2.1. Punteros
En palabras basicas un puntero es una direccioón de memoria. Su formato de escritura inicia
declarando un tipo de dato asociado, posteriormente digita un astersico(*) entre el nombre y
el tipo de data.La nomenclatura descrita es similar a la forma de declarar el acceso a ficheros.
(FILE* fichero;).
Dicho lo anterior se explcara con un ejemplo el uso de punteros en donde se reservara espacio
para un número real de manera estática, y a su vez se declararan números reales de forma
dinámica. La idea del ejercicio es guardar una suma declarada como un puntero e imprimir el
resultado de dicha variable.
#include <stdio.h>
96
#include <stdlib.h>/* Estadio para usar comando malloc y darle valor a la memoria*/
int main() {
float n1; /* Variable estática */
float *n2, *suma; /* Variables Dinamicas */
return 0;
}
n1 :Al ser un float se inicializa dandole un valor. posteriormente se pide su valor con el
coamando scnaf(ïndicanod a su ves la dirección de memoria en donde se encuentra
n2 y suma Es un puntero de tipo float, por lo que requiere reservarle un espacio de memoria
con malloc (’memory alloction’) antes de empezar con su funcionamiento La manera en
que se guarda un valor en la dirección de memoria es simplemente declarando la variable
como en la anterior variable, previo de colocar junto a el un asterisco (*). Se pide su valor
requerido con el comando scanf, pero al ser puntero(dirección de memoria) no es necesario
unsar & (scanf(” %f”, n2);).
int *n;
n = (int *) malloc (sizeof(int));
*n = 10;
97
n+1;
Como bien se sabe, los punteros son una dirección de memoria, por lo que en el codigo al
aumentar el valor de n aumentara en 1 la posicion de memoria.Tengase en cuenta que no
se dijo que la nueva posición de n = 11. De modo que si hipoteticamnete la posición de
memoria fues igual a 100.000, n al ser otra posición distinta ,sera igual ahora 100.001.
#include <stdio.h>
int main() {
int n = 10;
printf("n es %d\n", n);
operacion(&n); /*al ser un puntero la entrada se coloca &*/
printf("nuevo n : %d\n", n);
return 0;
}
Comandos Función
malloc() asigna un número determinado de bytes.
realloc() aumenta o disminuye la longtud de la memoria especificada, reasignando un valor de ser necesa
calloc() Genera un número especificado de bytes, inicianizandolos a cero(0).
free libera el bloque de memoria especificada de nuevo al sistema
malloc ()
98
• malloc() no inicializa la memoria asignada
calloc ()
Es meneser recalcar los problemas comunes bajo el uso de asiganrle una memoria dinamica a
una variable. Dichos errores son:
Pérdidas de memoria: Es comun la ausencia del comando ’free’, lo que produce que la
memoria acumule datos para un solo uso.
Una variable declarada como un array de algun tipo actua como un pointer de algún
tipo.Cuando es usado por el mismo, apunta al primer elemnto del array
De esta manera se puede declarar un dao como un arreglo, para recorrerlo con punteros,
Para explicar dicha aceveración se mostrara el siguiente ejemplo.
#include <stdio.h>
int main() {
int datos[10];
int i;
/*varaibles y arrelgo declarao*/
for (i=0; i<10; i++)
datos[i] = i*5;
/*Se da un valor y una longiud nueva del arreglo*/
for (i=0; i<10; i++)
printf ("%d ", *(datos+i));
/*Manera de recorrer un arreglo usando punteros*/
return 0;
De la misma manera que se declaran arrays para guardar datos, se puede reservar espacio
para un arreglo declarado como puntero. Ejemplo: Reservar n punteros a enteros
int *datos[n];
99
Sin embargo el fijar una cantidad de datos especifca se esta perdiendo versatilidad en el uso de
una memoria dinámica; El uso de esta forma de declarar es típico cuando se declaran varios
arreglos en cadena. Un ejemplo que refleja lo explicado previamente es:
#include <stdio.h>
int main()
{
char *mensajeError[3]={"no encontrado",
"No se puede escribir",
"no datos"};
return 0;
}
#include <stdio.h>
int main() {
/* Primero definimos nuestro tipo de datos */
struct datosPersona {
char nombre[30];
char email[25];
int edad;
};
2
http://www.nachocabanes.com/c/curso/cc09h.php
100
/* Ahora a la dinámica */
persona2 = (struct datosPersona*)
malloc (sizeof(struct datosPersona));
strcpy(persona2->nombre, "Pedro");
strcpy(persona2->email, "[email protected]");
persona2->edad = 21;
return 0;
}
tipo nombre[expresion1][expresion2]...;
nombre[expresion1][expresion2]...
También se puede hacer referencia a los elementos de una matriz usando la técnica de punteros
pero el texto se hace demasiado críptico.
101
102
6
C: Condicionales y funciones
6.1. Condicionales
Los condicionales en C nos permiten controlar las secuencias de ejecución de los comandos
por medio de las comparaciones; con estas podemos decidir qué acciones ejecutar. Los condicionales
se estructuran usando los comandos if o else if y dentro de este se establecen una o varias
comparaciones, las cuales se deben hacer entre objetos del mismo tipo y usando los siguientes
operadores:
if (comparacion(es)){
Acciónes a realizar;
}
103
Si la acción ocupa una sola línea no es necesario usar los corchetes { }.
Un ejemplo del uso de un condicional es:
}
else if (a==b)
printf("a es igual a b");
else
printf("Ninguna condición se cumple");
En este caso si no se cumple la primera condición a>b && b<c se procede a realizar la segunda
comparación a==b. Si nada de lo anterior se cumple se imprime el mensaje "Ninguna condición
se cumple".
En c es posible escribir ifs anidados, con los cuales si se cumple la primera condición se ejecuta
un segundo condicional. Ejemplo:
if (c==b){
if(a>=10)
printf("c es igual a b y es mayor o igual que 10" );
else
printf("c es igual a b pero menor que 10");
}
else
printf("c no es igual a b");
104
Las comparaciones dentro de un condicional se pueden hacer entre objetos de tipo numérico
como int y también con objetos tipo char. Con los objetos tipos char se debe recordar que
cada carácter tiene un valor numérico, estos son algunos ejemplos:
CARÁCTER VALOR
A 65
B 66
P 80
Q 81
b 98
Debido a lo anterior se puede realiza comparaciones aritméticas entre caracteres. El siguiente
ejemplo es un programa en el cual se le pide al usuario escribir una letra y se comprobará si la
letra está en el rango de A a P o de Q a Z .
#include <stdio.h>{
char letra;
printf("escriba una letra en mayuscula:");
scanf("\%c",&letra);
}
En este código con el comando scanf se guarda la letra escrita por el usuario en la variable
letra y se compara el valor numérico de esta letra con el valor numérico de A, P y Z.
condición ? expresión1:expresión2;
Ejemplo:
En este ejemplo el valor de la variable valor depende del valor de la varible y. Si y es mayor
que 10 valor = 7*5 si es igual o menor que 10 valor = 7*3.
105
La estructura equivalente, de este proceso, con el comando if es:
if ( y>10)
valor= 7*5;
else
valor= 7*3;
printf("Alcanzarás tu meta de ahorro en \%d dia\%s.", dias, dias==1 ? " " : "s" );
1.
x=12
y= 11
z= x&y
106
El valor de cada variable en binario:
x= 1 1 0 0
y= 1 0 1 1
z=1000
2.
x=12
y= 11
z= x|y
x= 1 1 0 0
y= 1 0 1 1
z=1111
Todos los bits de z son 1 ya que en cada posición, ya sea de x o de y, se encuentra un 1.El
valor resultante de z sería 15.
2.
x=12
y= 11
z= x^y
x= 1 1 0 0
y= 1 0 1 1
z=0111
6.2. Funciones
Una función en c es un conjunto de líneas de código programadas para realizar una acción.
Este conjunto debe tener un nombre específico y debe estar contenido entre corchetes {}.Un
ejemplo de función es la función principal, la cual es requerida en cualquier programa a ejecutar.
int main(void){
Código
En las siguientes secciones se explicará cómo definir una función, qué es un parámetro y cómo
definirlo y el uso general de las funciones.
107
6.2.1. Definir una función
Al definir una función, en la primera línea, se debe escribir el tipo de variable que retornará
la función, el nombre de la función y los parámetros de entrada.Luego de haber definido estos
tres items sigue el cuerpo de la función el cual es escrito dentro de corchetes ({}) y a un "tab"de
la margen en la que está el nombre de la función. En el cuerpo de la función se define que
operaciones se realizan con los parámetros de entrada. Estructura de una función:
cuerpo de la función
return objeto;
}
Por ejemplo:
int c;
c=a+b;
return c;
}
En este ejemplo no es necesario definir el tipo de ambos parámetros ya que el tipo de objeto de
a y b es el mismo (int). Por lo tanto sólo se separa el nombre de los parámetros con una coma.
Tenga en cuenta que en cualquier función es necesario devolver un objeto del mismo tipo con el
que se definió la función. En este caso, en la cabeza de la función se define int suma(int a,b)
por lo cual la variable que se retorna debe ser de tipo int. Cabe aclarar, que los parámetros de
entrada no necesariamente deben ser del mismo tipo con el que se difinió lo función.
6.2.2. Parámetros
Un parámetro en una función es el valor que debe ser especificado al llamar la función.En
el cuerpo de la función los parámetros son los valores modificados o requeridos para realizar
una operación. En C no es necesario que todas las funciones tengan parámetros definidos; como
vimos en el ejemplo de la función principal, esta se declara con un parámetro void el cual indica
la ausencia de parámetros al definir la función.
Los parámetros definen qué valores son necesarios para ejecutar una función y por lo tanto
al llamar la función se debe ingresar, dentro de paréntesis, los argumentos sobre los cuales se
quiere aplicar la función, estos argumentos se deben ingresar en la misma cantidad de objetos
y del mismo tipo con el que fue definida la función el código. Ejemplo:
108
int cont=0;
int i=0;
int largo = strlen(mensaje);
for(i;i<largo; i++){
if(mensaje[i] == ’ ’)
cont++;
}
return cont;
}
char oracion[200];
printf("Escriba una oración: ");
fgets(oracion,200,stdin);
printf("La cantidad de espacios en la oración es: %d", espacios(oracion));
En este ejemplo, primero se define la función espacios y luego esta es llamada en la función
principal. La función espacios tiene como parámetro un arreglo tipo char. A este arreglo, dentro
de la función espacios se le cuentan los caracteres que son iguales a ’ ’ es decir un espacio. Esto
se hace por medio de un for, el cual recorre cada posición del arreglo, análizando el carácter de
esa posición determinada y cada carácter es comparadado, por medio de un if; si este es igual
a un espacio se le suma 1 a un contador (cont) inicializado en ceros. Esta función devuelve el
número de espacios en el arreglo. En la función main con el comando scanf se le pide al usuario
que escriba una oración, lo escrito por el usuario es guardado, por la función fgets la cual será
explicada en el siguiente capitulo, en el arreglo llamado oracion. Luego se imprime el mensaje
que indica la cantidad de espacios en la oración, en este mensaje se devuelve un número int el
cual es dado al llamar a la función espacios e ingresar como argumento dentro de la función
el arreglo oracion de la siguiente manera:
espacios(oración)
Si una función tiene como entrada de parámetros void, cuando esta sea llamada no se debe
ingresar ningún argumento dentro de los paréntesis.
La manera en la que fue estructurado el ejemplo anterior, primero la función espacios y luego
la función main puede ser cambiada por el orden contrario. Sin embargo, antes de la función
main debe ser declarada la función espacios sin el cuerpo de la función y sin el nombre del
parámetro de la siguiente manera:
/*Código de main*/
}
int espacios (char mensaje[]){
109
/*Código de espacios*/
numero = 3*numero;
prinf("Con la función numero= %d",numero);
return numero;
}
int main (void){
int numero = 5;
int multiplicado = multiplica(numero);
printf("En el main numero= %d y resultado = %d",numero, multiplicado );
return 0;
int *pnumero=3*(*pnumero);
printf("Con la función numero = %d", *pnumero);
return *pnumero;
}
int main(void){
int numero = 5;
int *pnum = №
int multiplicado = 0;
multiplicado = multiplica(pnum);
110
printf("En el main numero= %d y resultado = %d",numero, multiplicado );
return 0;
}
Lo anterior comprueba el uso de los punteros para cambiar el valor de una variable. La diferencia
de este ejemplo con el anterior es que el parámetro de entrada de la función multiplica es un
puntero y la función retorna un puntero. Ademas, para cambiar el valor global de la variable
es necesario indicar que el puntero es igual a la dirección de la variable como es es hecho en la
línea:
Esto funciona ya que al pasar el puntero como argumento también se pasa una copia de la
dirección de la variable y y el valor dado por la copia de la dirección es modificado por la
función; sin embargo la dirección continua siendo la misma que para la variable original.
int *funcion(void)
Así, se establece que la función retornará un puntero de tipo int. Para llamar este tipo de
funciones se omite el indicador de puntero (*). Es decir sólo se escribe el nombre de la función
y los argumentos de entrada requeridos. Ejemplo:
*deuda-=2000;
return deuda
}
int pago=3000;
int *cuentavieja = &pago;
int *cuentanueva = NULL;
cuentanueva = pagodeuda(cuentavieja)
printf("antes de pagar=%d, después de pagar = %d", *cuentavieja,*cuentanueva)
return 0;
111
El problema con el código anterior es que al inicializar el valor de cuentanueva con el valor
dado por la función pagodeuda con el argumento de entrada cuentavieja; el valor del puntero
cuentavieja será modificado por la función y por lo tanto al imprimir su valor el resultado será
el valor modificado, el cual es el mismo que el del puntero cuentanueva.Por lo cual también
hace del código anterior un claro ejemplo del uso de los punteros.
Variables locales
Las variables locales son aquellas que se declaran dentro de cualquier función, incluso dentro
de la función main esto quiere decir que se le asigna un lugar en la memoria automáticamente
durante el uso de la función, al salir de esta la memoria usada por la varible es liberada y por lo
tanto este tipo de variables también es considerada como variable automática.Por esta razón se
puede declarar este tipo de variable con la palabra auto previa al tipo de dato de la siguiente
manera:
Sin embargo, esta notación no es necesaria y este tipo de variables se pueden declarar de forma
ordinaria:
char letra;
Variables globlales
Este tipo de variables son declaradas fuera de cualquier función y su funcionalidad se da desde
el punto en que son declaradas y la finalización del programa. Esto permite usar una variable
en cualquier función sin necesidad de ser declarada dentro de esta; por lo tanto se puede evitar
la necesidad del uso de argumentos dentro de una función. Se debe tener en cuenta que el valor
original de la varible se mantiene hasta finalizar el programa, lo que significa que a pesar de
ser "modificadas"dentro de una función, su valor se conservará; ya que, como ha sido explicado
previamente, lo que se modificará será la copia de esta variable.
Para declarar una variable global se usa la misma sintaxis que en las variables locales. La
diferencia es que estas se declaran fuera de cualquier función. Por ejemplo:
int numero;
int funcion(void){
numero=3;
return numero;
}
Si una variable global es declarada dentro de una función es necesario escribir la palabra
reservada extern. Al usar esta palabra no se le puede dar un valor a una variable , este debe
ser asignado posteriormente. Además esta variable debe ser declarar posteriormente fuera de
112
las funciones; el tipo de dato de la variable debe coincidir con los valores asignados dentro de
las funciones. Ejemplo:
int suma(void);
int main(void){
extern numero;
numero=10;
printf("El número es: %d y la suma es:%d",numero,suma());
return 0;
}
int suma(void){
extern numero;
int suma = numero+1;
return suma;
}
int numero;
Variables estáticas Una variable estática puede ser local o globlal. Si es declarada localmente
se le asigna un almacenamiento permanente dentro de una función mediante un medio privado;
es decir que las variables existirán durante toda la ejecución del programa, a diferencia de las
variables dinámicas (todas las que no sean declaradas estáticas) en las que su existencia depende
de la existencia de la función. Si este tipo de variables es declarada globalmente sólo pueden ser
accedidas desde el fichero en el que son declaradas inicialmente; por lo tanto pueden declararse
variables con el mismo nombre en otros ficheros.Para declarar este tipo de variables se escribe
la palabra reservada static antes del tipo de dato de la siguiente manera:
Variables de registro
Al crear una variable como variable de registro, esta es guardada en un lugar de rápido acceso.
Este tipo de variable sólo pueden ser locales o un argumento de una función. Además para que
el valor sea guardado debe haber un registro disponible y por lo tanto sólo se pueden declarar
un número específico de variables de registro de una función. Si no hay un registro disponible C
guarda el valor de la variable como tipo autmática o local. Para declarar este tipo de variables
como locales o argumentos de funciones se usa la palabra reservada register. Ejemplos:
Como variable:
Como argumento:
funcion(register int numero){
...
113
}
6.3. Inputs/Outputs
6.4. Ejercicios
6.4.1. Pointers
Escribir un programa para calcular el promedio para un número arbitrario de valores flotantes
que se introducen desde el teclado. Almacenar todos los valores en la memoria que se asigna de
forma dinámica antes de calcular y mostrar el promedio. El usuario no debe estar obligado a
especificar por adelantado cuántos valores habrá.Código de solución:
#include <stdio.h>
#include <stdlib.h>
int main(void){
//Mensaje
printf("Escriba un numero entero positivo\n");
114
//guarda el valor dado por el usuario en la variable respuesta
scanf("%d", &respuesta);
//suma 1 a la variable i
i++;
//suma 1 a la variable n
n++;
//mensaje
printf("Escriba otro numero:\n");
//imprime el promedio
printf("el promedio de la suma de sus números es:
%f\n",promedio);
115
return 0;
usuario@usuario-desktop:~/carpeta$ cc punto1.c
usuario@usuario-desktop:~/carpeta$ ./a.out punto1.c
Escriba un numero entero positivo
4
Si desea digitar otro numero escriba 0 si no escriba 1
0
Escriba otro numero:
3
Si desea digitar otro numero escriba 0 si no escriba 1
1
el promedio de la suma de sus números es: 3.500000
6.4.2. Funciones
Definir una función que devolver a el número de palabras que contiene una cadena de
caracteres, la cadena de caracteres será un argumento de la función creada (Las palabras están
separadas por espacios o caracteres de puntuación. Supongamos que la cadena no contiene
comillas simples ni dobles, además no se aceptan caracteres con tildes). Definir una segunda
función que dividirá una cadena de caracteres en palabras y retornará el número de palabras,
la cadena de caracteres y el array de palabras serán argumentos de entrada.Definir una tercera
función que devolverá el número de letras de una palabra, la palabra será un argumento de
entrada. Utilice estas funciones para implementar un programa que lea un texto que se ingresará
por el teclado y que como salida, regrese todas las palabras del texto de forma ordenado de la
más corta a la más larga.Código de solución:
include <stdio.h>
#include <stddef.h>// para usar wchar_t
#include <string.h>//para usar srtlen
#include <stdlib.h>//para usar malloc
//contador de palabras
int cont=1;
//variable con la longitud del arreglo -1 ya que el el primer elemento
//se encuentra en posicion 0
int longi= strlen(str)-1;
116
//Da la posición de la última letra, -2 ya que se resta la posición 0
//y el corte del string ’\0’
int longult =strlen(str)-2;
//ultima letra
char ultim = str[longult];
return cont;
117
int a = (strlen(strin)-1);
//se omiten los espacios después de coma o punto avanzando una posición para
//llegar a una letra
else if (letra == ’,’ & sigletra ==’ ’ ){
palabra[l]=palabra[l+1];
}
else if (letra == ’.’ & sigletra ==’ ’ ){
palabra[l]=palabra[l+1];
}
//Si letra es igual a un signo de puntuación
else{
118
//Aumenta una posición el arreglo mensaje para guardar la siguiente palabra.
m++;
}
//Devuelve el número de palabras
return m;
}
i++;
}
/*función principal*/
int main (void){
119
cadena = getc(stdin);
return 0;
}
El código anterior no ordena las palabras según su cantidad de letras. Además no cuenta de
manera correcta la cantidad de letras en la primera palabra ingresada. El resultado al ejecutar
este código es:
usuario@usuario-desktop:~/carpeta$ cc punto2.c
usuario@usuario-desktop:~/carpeta$ ./a.out
escriba un mensaje con punto final:
Prueba punto dos.
cantidad de palabras con con 3
cantidad de palabras con sep 3
letras en Prueba es: 28
letras en punto
es: 5
letras en dos
es: 3
120
6.4.3. Lectura de archivos
El archivo P i_2500000.txt contiene cerca de 2.5 millones de dígitos del número en su
representación decimal. Escriba un programa en C ( el código fuente se debe llamar suma_digitos_pi.c)
que sume los primeros n dígitos de la representación decimal de pi. Después de compilado el
programa debe poder ejecutarse como:
./a.out n
Donde n representa el número de dígitos decimales para sumar. El programa debe parar su
ejecución y dar un mensaje de error si n >0 o si n >2500000. El programa debe funcionar de
manera correcta con valores hasta de n = 2500000. El valor final se debe imprimir en pantalla.
Código de solución:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
int bor;
//variable de iteración
long long int i;
char num[2500000]="";
121
//Se adiciona al string los numeros del usuario
for(i=1; i<argc; i++){
strcat(num, argv[i]);
}
// Abre el archivo
in=fopen(filename,"r");
//Imprime el mensaje
printf("Problemas al abrir el archivo %s\n",filename);
exit(1);
}
}
//Se imprime la suma
122
printf("La suma de los primeros %lld dígitos
de la representación decimal es %lld\n",n,suma);
}
//Se cierra el archivo.
fclose(in);
return 0;
}
usuario@usuario-desktop:~/carpeta$ cc suma_digitos_pi-2.c
usuario@usuario-desktop:~/carpeta$ ./a.out 5
La suma de los primeros 5 dígitos de la representación decimal es 20
123
124
7
Algebra lineal
a1 x1 + a2 x2 + a3 x3 + ... + an xn = b (7.1)
La ecuación lineal 7.1 es una ecuación con n incógnitas y el resultado es una constante
denominada C. Una ecuación lineal con dos incógnitas corresponde a una recta en un espacio
de dos dimensiones. Asimismo, una ecuación lineal con tres incógnitas representa un plano en
un espacio tridimensional [9].
Un sistema de ecuaciones lineales es un conjunto de ecuaciones que tienen la forma de la
ecuación lineal 7.1. Así, un sistema de ecuaciones se puede escribir:
125
a11 a12 ··· a1n x1 b1
a21
a22 ··· 2 b2
a2n x
. . = .
. . .
. . .
am1 am2 · · · amn xn bm
Para simplificar aún más la notación, se puede escribir el sistema de ecuaciones así:
Ax = b (7.2)
En la ecuación 7.2 se cumple lo siguiente:
a11 a12 ··· a1n x1 b1
a21
a22 ··· a2n
x2
b2
A=
..
x=
..
b=
..
. . .
am1 am2 · · · amn xn bm
Así, la matriz A contiene los coeficientes del sistema de ecuaciones lineales. El vector columna
x corresponde a las incógnitas del sistema de ecuaciones y el vector columna b corresponde
al conjunto de términos independientes del sistema. Para demostrar que la expresión 7.2 es
equivalente al sistema de ecuaciones original es necesario llevar a cabo el producto entre la
matriz A y el vector columna x, llegando a la siguiente equivalencia:
a11 a12 ··· a1n x1 a11 x1 + a12 x2 + ... + a1n xn
a21
a22 ··· a2n
2 a21 x1 + a22 x2 + ... + a2n xn
x
. . = .. (7.3)
. .
. . .
am1 am2 · · · amn xn am1 x1 + am2 x2 + ... + amn xn
Para demostrar esta equivalencia es necesario conocer los conceptos básicos relacionados con
el álgebra de matrices, en especial la multiplicación. Por esto, a continuación se presentará una
descripción breve del producto entre matrices y de algunas operaciones elementales.
126
Para la resta entre matrices se implementa el mismo método anterior, es decir, a cada
elemento de una matriz A se resta cada elemento de una matriz B, con lo que se obtiene una
matriz C que cumple:
bij = raij
Por ejemplo, si se desea hallar el resultado de la multiplicación entre dos y una matriz A se
multiplica cada elemento de la matriz por dos, con lo que se obtiene el siguiente resultado:
" #
3 4
A=
6 5
" # " #
3 4 6 8
2 =
6 5 12 10
Por otro lado, una matriz B es la matriz transpuesta de una matriz A si se cumple que los
elementos aij de la matriz A son los mismos elementos de las posiciones bji en la matriz B [9].
Un ejemplo de una matriz transpuesta B de una matriz original A es:
" # " #
2 3 2 4
A= B=
4 5 3 5
127
n
X
cmr = amk bkr (7.4)
k=1
Para llevar a cabo la multiplicación entre dos matrices es necesario que el número de
columnas de la primera matriz sea igual al numero de filas de la segunda matriz. De lo contrario
no se podría realizar el producto punto anterior. Ahora se presenta un ejemplo de multiplicación
entre una matriz de dimensiones 2 × 3 y una segunda matriz de dimensiones 3 × 2:
" # 3 1 " #
2 1 2 9 12
1 2 =
3 2 1 12 11
1 4
En el ejemplo anterior, el número 9 se ubica en la posición 1,1 de la matriz resultante. Por
lo tanto, es el resultado del producto punto entre la primera fila de elementos de la primera
matriz y la primera columna de elementos de la segunda matriz. Siguiendo la notación en la
expresión 7.4 se puede expresar lo anterior así:
3
X
c11 = a1k bk1 = (2, 1, 2) · (3, 1, 1) = (2 × 3) + (1 × 1) + (2 × 1) = 9
k=1
La matriz identidad es la matriz que al ser multiplicada por cualquier otra matriz A genera
como resultado la misma matriz A sin modificaciones. Por lo tanto, la matriz identidad es:
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
.
..
.
. .
.. ..
.
.
0 0 0 0 0 1
Como ya se ha presentado la multiplicación de matrices, es posible demostrar la igualdad
expuesta en la ecuación 7.3. El producto en este caso se realiza entre una matriz de dimensiones
m×n y una matriz de dimensiones n×1. Por lo tanto, el resultado es una matriz de dimensiones
m × 1 en la que cada elemento es el resultado del producto punto entre los elementos de la fila
m y la columna con las incógnitas del sistema. Por ejemplo, para obtener la primera ecuación
lineal, es decir, el primer elemento de la matriz resultante en la ecuación 7.3 se debe operar así:
128
r(A + B) = rA + rB Ley distributiva
Estas propiedades se cumplen para cualquier matriz o cualquier escalar relacionados con
operaciones elementales.
129
Para encontrar las soluciones del sistema es posible implementar operaciones entre filas.
Estas operaciones no modifican el sistema, sino que lo simplifican para eliminar coeficientes de
la matriz aumentada. Las operaciones que se pueden realizar entre filas (denotadas por la letra
f) son [9]:
Con las operaciones entre filas se busca llegar a la forma escalonada de la matriz aumentada.
Para ello, se debe obtener una matriz en la que el primer elemento diferente de cero en cualquier
fila aparece una columna a la derecha del primer elemento diferente de cero en la fila anterior.
Al primer elemento diferente de cero en una fila se le denomina pivote.
Para obtener la forma escalonada de una matriz es necesario seguir tres pasos [9]:
Si la primera columna de la matriz contiene solo ceros, se debe buscar la primera columna
que contenga algún elemento diferente de cero.
Usar intercambio entre columnas, si es necesario, para obtener un elemento diferente de
cero en la primera fila de la primera columna en la matriz, es decir, hasta conseguir un
pivote p. Para cada fila debajo que contenga un elemento diferente de cero r en la primera
columna, se debe adicionar -r/p veces la primera fila a esta fila para obtener un cero en
todos los elementos de la primera columna debajo del pivote.
Se repite el proceso para la matriz omitiendo la primera fila y la primera columna hasta
que se acaben las filas o las columnas de la matriz
El siguiente ejemplo explica paso a paso la reducción de una matriz a su forma escalonada.
La matriz que se desea reducir es:
2 −4 2
2 −4 3
4 −8 3
1
El primer paso para reducir la matriz anterior es multiplicar la primera fila por 2 con el fin
de obtener un pivote igual a 1:
1 −2 1
2 −4 3
4 −8 3
Ahora se adiciona -2 veces la fila 1 a la fila 2 y se adiciona -4 veces la fila 1 a la fila 3, con
lo que se obtiene:
1 −2 1
0 0 1
0 0 −1
Por último se suma la fila 2 a la fila 3, obteniendo así la forma escalonada de la matriz:
1 −2 1
0 0 1
0 0 0
130
La matriz obtenida contiene una fila de ceros. Esto sucede cuando el número de filas es
menor al número de columnas. Estas filas no tienen pivote y se ubican siempre por debajo de
las filas con pivotes en la forma escalonada de la matriz.
Las operaciones entre filas se realizan también en matrices aumentadas y esto permite hallar
las soluciones de un sistema de ecuaciones. En el siguiente ejemplo se resuelve el siguiente sistema
de ecuaciones:
x2 − 3x3 = −5
2x1 + 3x2 − x3 = 7
4x1 + 5x2 − 2x3 = 10
131
Por último, se resta a la fila 1 tres veces la fila 2 con el fin de obtener un cero encima del
pivote de la fila 2:
2 0 0 −2
0 1 0 4
0 0 1 3
La matriz en su forma escalonada permite concluir que:
2x1 = −2 x2 = 4 x3 = 3
El sistema Ax = b es inconsistente si la matriz aumentada tiene una fila con todas los
elementos iguales a cero a la izquierda de la partición y un elemento diferente de cero a
la derecha de la partición.
Si Ax = b es consistente y cada columna de la matriz aumentada contiene un pivote, el
sistema tiene una única solución
Si Ax = b es consistente y alguna columna de la matriz aumentada no presenta pivotes,
el sistema tiene infinitas soluciones.
Eliminación Gaussiana
Descomposición LU
Eliminación Gauss-Jordan
132
7.3.2. Eliminación Gaussiana
El método de la eliminación Gaussiana consiste en la reducción de las matrices para obtener
una matriz en la que los elementos por debajo de los pivotes son iguales a cero. A esta matriz se
le denomina una matriz triangular superior, ya que todos los elementos de la matriz por debajo
de la diagonal de pivotes son iguales a cero.
Para empezar, se tiene la siguiente matriz:
A11 A12 A13 · · · A1k · · · A1j · · · A1n |b1
0
A22 A23 · · · A2k · · · A2j · · · A2n |b2
0 0 A33 · · · A3k · · · A3j · · · A3n |b3
.. .. .. .. .. .. ..
. . . . . .|.
0 0 0 · · · Akk · · · Akj · · · Akn |b3
← f ila pivote
. .. .. .. .. .. ..
.. . . . . .|.
← f ila que se transf orma
0 0 0 ··· Aik ··· Aij ··· Ain |bi
.. .. .. .. .. .. ..
. . . . . .|.
0 0 0 · · · Ank · · · Anj · · · Ann |bn
Para eliminar el elemento Aik se debe restar a esta fila λ veces el elemento pivote en la
misma columna Akj
Esta última sustitución permite despejar la incógnita que se encuentra en la fila pivote. Como
se ha reducido la matriz a una matriz triangular superior, el elemento ubicado en la última fila
y la última columna de la matriz permite obtener la última incógnita simplemente dividiendo
el término constante de esta fila entre el coeficiente de la misma fila. Halla esta incógnita, se
puede implementar en la fila superior para obtener el valor de la penúltima incógnita del sistema.
Repitiendo este proceso se obtienen todas las soluciones del sistema.
El código que permite resolver un sistema de ecuaciones por el método Gaussiano en IPython
es:
import numpy as np
## Función que encuentra las soluciones del sistema ax=b por método Gaussiano
def gaussElimin(a,b):
#Numero de filas de matriz
n = len(b)
133
#Para valores de k entre 0 y el numero de columnas menos 1
for k in range(0,n-1):
lam = a [i,k]/a[k,k]
4 −2 1 11
A = −2 4 −2 b = −16
1 −2 4 17
En Ipython se pueden escribir matrices como arreglo en dos dimensiones. Si lo cree necesario
debe recurrir al capítulo 3. Para obtener el resultado de la reducción se llama la función anterior,
que como parámetros requiere la matriz anterior y el arreglo de términos constantes. En código
esto es:
a=np.array([[4.0,-2.0,1.0],[-2.0,4.0,-2.0],[1.0,-2.0,4.0]])
b=np.array([11.0,-16.0,17.0])
gaussElimin(a,b)
El resultado es un arreglo de numpy que contiene las soluciones del sistema de ecuaciones:
1
x = −2
3
134
7.3.3. Descomposición LU
El método de descomposición LU consiste en la descomposición de la matriz A de coeficientes
de un sistema de ecuaciones en dos matrices, denominadas L y U :
LU = A (7.6)
Las combinaciones de L y U para A son infinitas. Las restricciones sobre estas matrices,
hacen que hayan diferentes tipos de métodos para hacer estas descomposiciones:
Descomposición Doolittle
Descomposición Crout
Descomposición Choleski
Ax = b LU x = b
Ux = y (1) Ly = b (2)
2y1 = 28 y1 = 28/2 = 14
−y1 + 2y2 = −40 y2 = (−40 + y1 )/2 = (−40 + 14)/2 = −13
y1 − y2 + y3 = 33 y3 = (33 − y1 + y2 )/1 = 33 − 14 − 13 = 6
Se soluciona Ux = y:
135
2x3 = y3 x3 = y3 /2 = 6/2 = 3
4x2 − 3x3 = y2 x2 = (y2 + 3x3 )/4 = (−13 + 3(3))/4 = −1
4x1 − 3x2 + x3 = y1 x1 = (y1 − x3 + 3x2 )/4 = (14 − 3 + 3(−1))/4 = 2
Descomposición Dolittle
1 0 0 U11 U12 U13
L = L21 1 0 U = 0 U22 U23
L31 L32 1 0 0 U33
Realizando la multiplicación
U11 U12 U13
A = U11 L21 U12 L21 + U22 U13 L21 + U23
U11 L31 U12 L31 + U22 L32 U13 L31 + U23 L32 + U33
Los elementos fuera de la diagonal de L son los multiplicadores de las ecuaciones pivote
utilizados durante la eliminación Gaussina es decir, Lij = λij es el multiplicador que
eliminó Aij.
136
Es una práctica habitual para almacenar las dos matrices
U11 U12 U13
[L/U] = L21 U22 U23
L31 L32 U33
Por lo tanto, la solución por medio del método de descomposición Dolittle tiene como
solución para y las soluciones del sistema de ecuaciones Ly = b. Este sistema se puede resolver
implementando las mismas ecuaciones que despejan y halladas por medio del método de eliminación
Gaussiana:
k−1
X
yk = bk − Lkj yj , k = 2, 3, ..., n
j=1
Halladas las soluciones del sistema Ly = b se pueden encontrar las soluciones al sistema
Ux = y aplicando la misma fórmula anterior, pero ahora con uns sistema que tiene como
términos constantes las soluciones del sistema anterior:
n
!
X 1
xk = yk − Ukj xj , k = n − 1, n − 2, .., 1
j=k+1
Ukk
import numpy as np
#Funciones que resuelven el sistema de ecuaciones por descomposición Dolittle
#Función que descompone la matriz a, que entra como parámetro, en dos matrices:
L y U
def LUdecomp(a):
n = len(a) #Numero de elementos de a
for k in range(0,n-1):
for i in range(k+1,n):
if a[i,k] != 0.0: #Si el valor de la posición es distinto de cero
137
#Como parámetros entran la matriz que contiene L y U y los términos constantes b
def LUsolve(a,b):
n = len(a) #Numero de elementos de a
for k in range(1,n):
for k in range(n-2,-1,-1):
Descomposición Choleski
A = LLT
A11 A12 A13 L11 0 0 L11 L21 L31
A A A = L L 0 0 L22 L32
21 22 23 21 22
A31 A32 A33 L31 L32 L33 0 0 L33
138
A11 A12 A13 L211 L11 L21 L11 L31
A A A L L L2 + L2 L
= 21 31 + L22 L32
L
21 22 23 11 21 21 22
A31 A32 A33 L11 L31 L21 L31 + L22 L32 L231 + L232 + L233
Si i = j
v
u
u j−1
X
Ljj = tAjj − L2jk j = 2, 3, ..n
k=1
Si i 6= j
j−1
X
Lij = Aij − Lik Ljk j = 2, 3, .., n − 1 i = j + 1, j + 2, ..., n
k=1
Es posible escribir los elementos de L sobre la parte triangular inferior de A a medida que
se calculan. Los elementos sobre la diagonal principal de A permanecerán intactos.
En el siguiente ejemplo se encuentra la matriz L que multiplicada por su transpuesta genera
la matriz original.
4 −2 2 L211 L11 L21 L11 L31
2 2
−2 2 −4 = L11 L21 L21 + L22 L21 L31 + L22 L32
2 −4 11 L11 L31 L21 L31 + L22 L32 L231 + L232 + L233
√
L11 = 4=2
L21 = −2/L11 = −2/2 = −1
L31 = 2/L11 = 2/2 = 1
q √
L22 = 2 − L221 = 2 − 1 = 1
−4 − L21 L31 −4 − (−1)
L32 = = = −3
L22 1
q q
L33 = 11 − L231 − L232 = 11 − 1 − (−3)2 = 1
2 0 0
L = −1 1 0
1 −3 1
Para comprobar que esta matriz cumple con las restricciones dadas por el método de
descomposición Choleski, se multiplica por su transpuesta:
2 0 0 2 −1 1 4 −2 2
LLT = −1 1 0 0 1 −3 = −2 2 −4
1 −3 1 0 0 1 2 −4 11
139
El código que permite solucionar un sistema por medio de la descomposición Choleski es:
import numpy as np
import math
#import error
#Función que descompone la matriz a, que entra como parámetro, en las matrices L
y L transpuesta
def choleski(a):
n = len(a) #Numero de elementos de a
for k in range(n):
try:
#Se intenta encontrar la raíz para saber si los elementos de la
matriz son positivos
except ValueError:
#Se arroja un mensaje de error
for i in range(k+1,n):
Para implementar el código anterior sobre una matriz, se debe generar un arreglo de dos
dimensiones de numpy con los coeficientes de un sistema de ecuaciones, junto con otro arreglo
que contiene los términos constantes y se llaman las funciones anteriores. Por ejemplo, se desean
encontrar las soluciones del sistema Ax=b, con
140
4 −2 2 4
A = −2 2 −4 b = −4
2 −4 11 9
Para ello, se aplica el código anterior que encuentra la matriz L con la función choleski()
y encuentra las soluciones del sistema de ecuaciones con la función choleskiSol().
a=np.array([[4,-2,2],[-2,2,-4],[2,-4,11]]) # Matriz A
aorig=a.copy()
b=np.array([4,-4,9]) #Terminos constantes
L=choleski(a) #Matriz L, hallada con matriz A
x=choleskiSol(L,b) #Solución del sistema de ecuaciones
print x
Como resultado se obtiene el arreglo de numpy con las soluciones del sistema. En este caso
la solución es [1 1 1].
1. Es factible almacenar sólo los elementos distintos de cero en la matriz de coeficientes. Esto
hace posible tratar con matrices muy grandes. En muchos problemas, no hay necesidad
de almacenar la matriz de coeficientes en absoluto.
2. Los procedimientos iterativos son auto-corregibles, lo que significa que los errores de
redondeo (o incluso errores aritméticos) en un ciclo iterativo se corrigen en los ciclos
posteriores.
Una desventaja de los métodos indirectos es que en ocasiones la solución no converge. Sólo
convergerán las matrices que son diagonalmente dominantes. Es decir, matrices en las que se
cumple que:
X
|Aii | ≥ |Aij |
j6=i
Método Gauss-Seidel
141
Se tiene el sistema de ecuaciones Ax = b, que en notación escalar es equivalente a:
n
X
Aij xj = bi i = 1, 2, ..., n
j=1
Resolviendo para xi
n
1 X
xi = bi − Aij xj i = 1, 2, ..., n
Aii j6=i
Para que este método converja, se modifica la ecuación y se agrega el término ω, que es el
factor de relajación:
n
w X
xi ← bi − Aij xj + (1 − w)xi i = 1, 2, ..., n
Aii j6=i
2
wopt ≈ q
1+ 1 − (∆x(k+p) /∆x(k) )
Al añadir el factor de relajación ω, se llevan a cabo los siguientes pasos para encontrar la
solución de un sistema de ecuaciones:
2. Se calcula ∆x(k)
4. Se calcula ∆x(k+p)
5. Se computa wopt
142
import numpy as np
import math
2 −1 0 0 ··· 0 0 0 1 x1 0
−1 2 −1 0 ··· 0 0 0 1 x2 0
0 −1 2 −1 ··· 0 0 0 1 x3 0
. .. .. .. .. .. .. .. .. .
. = .
. . . . . . . . . .
· · · −1 2 xn−2 0
0 0 0 0 1 0
0 0 0 0 · · · 0 −1 2 1 xn−1 0
0 0 0 0 ··· 0 0 −1 2 xn 1
143
Para este ejemplo se cumple que n = 20, xi = −n/4 + i/2 i = 1, 2, ..., n
n = 20 #Numero de incógnitas
x = np.zeros(n) #Arreglo de n ceros
x,numIter,omega = gaussSeidel(iterEqs,x) #Se implementa método Gauss-Seidel
7.4. Ejercicios
7.4.1. Gauss-Jordan
Este ejercicio consiste en escribir el algoritmo para solucionar un sistema de ecuaciones
utilizando el método de Gauss-Jordan en ipython. En el notebook debe estar contenida la
explicación, un ejemplo y ejercicio resuelto con este método.
Para solucionar el ejercicio se aplica el siguiente cóodigo:
144
#Gauss Jordan
import numpy as np
lam = a [i,k]/a[k,k]
145
a[i,i:n] = a[i,i:n] - lam*a[k,i:n]
b[i] = b[i] - lam*b[k] #La resta se efectua tambien para las
constantes en b
Si se aplica este código para reducir una matriz por Gauss-Jordan, se debe escribir un arreglo
en dos dimensiones con la matriz que se desea reducir y, si es necesario, los términos constantes.
Por ejemplo, se desea reducir la siguiente matriz para solucionar el sistema de ecuaciones lineales:
4,0 −2,0 1,0
−2,0 4,0 −2,0
1,0 −3,0 4,0
El código que permite encontrar las soluciones del sistema por el método de Gauss-Jordan
debe llamar la función gaussJordan():
Con esto, se obtienen las soluciones del sistema de ecuaciones dado por la matriz y los
términos constantes. Para este caso, en el cuaderno de IPython se obtiene:
7.4.2. Hilbert
Un ejemplo bien conocido de una matriz singular es la Matriz Hilbert
1 1/2 1/3 · · ·
1/2 1/3 1/4 · · ·
A = 1/3 1/4 1/5 · · ·
.. .. .. ..
. . . .
n
X
bi = Aij
j=1
146
El programa debe tener como parámetro de entrada n. Mediante la ejecución del programa,
determinar el n más grande para el que la solución está dentro de seis cifras significativas de la
solución exacta.
Para resolver este ejercicio se implementa el siguiente código:
import numpy as np
b=np.zeros(n) #Se crea un arreglo de tamaño n para para guardar las constantes
147
la que se obtiene con el método de Gauss
Para encontrar el n a partir del cual aparecen seis cifras significativas de precisión, se
implementa la función anterior en IPython:
Ahora se procede aumentando el n para ver la forma en que cambian las soluciones de la
matriz Hilbert
148
Al aumentar n en otra unidad, se obtienen soluciones del sistema de ecuaciones con ocho
cifras decimales:
>>> #A partir de n=7 todas las soluciones presentan ocho cifras decimales
>>> n=7
>>> DolittleH(n)
149
150
8
Interpolación
Una función de interpolación es aquella que pasa a través de puntos dados como datos,
que se muestran comúnmente en tablas de valores o se toman directamente de una función
dada [17]. Por medio de la aproximación, la interpolación proporciona técnicas de obtención
de modelos funcionales simples que describen comportamientos complejos [2]. Su importancia
radica en la generación de métodos numéricos de derivación, integración y raíces de funciones
[2]. La interpolación de los datos se realiza mediante polinomios, funciones spline, funciones
racionales o series de Fourier, entre otras formas [17].
El problema que se busca resolver con una interpolación es estimar f(x) dados n + 1 puntos
(xi , yi ) con i = 0, 1, · · · , n. A diferencia de los ajustes de curvas, la interpolación se construye
se construye una curva a través de los puntos de datos. De este modo, hacemos la suposición
implícita de que los puntos de datos son exactos y distintos. En contraste, ajuste de curvas se
aplica a los datos que contienen ruido, por lo general causada por errores de medición. Con una
interpolación se desea encontrar una curva suave que se aproxime a los datos.
La resolución de problemas de interpolación es de gran utilidad cuando la procedencia de los
datos que se quieren interpolar es experimental. Conocida experimentalmente la relación entre
dos variables, es de interés encontrar el valor que tendría una variable al tomar condiciones no
experimentales. Pese a esto, la implementación de los métodos de interpolación no se limitan
únicamente a la parte experimental y se frecuente su aplicación para datos que proceden de
cálculos numéricos.
Adicionalmente, "los modelos de integración numérica se obtienen integrando fórmulas de
interpolación polinomial, y los modelos de diferenciación numérica se obtienen derivando las
interpolaciones polinomiales"[17]. Con base en su relevancia, este capítulo presenta algunos
aspectos de la interpolación polinómica y racional, junto con algunos ejercicios que requieren
su implementación para encontrar el valor de una función en algún punto específico. Para ello,
se presenta en un comienzo la interpolación polinomial por medio de los métodos de Lagrange,
Newton y Neville en IPython. Posteriormente, se describen las interpolaciones racional y segmentaria
cúbica. Por último, se presentan algunos ejercicios relacionados con los métodos de interpolación.
Cabe resaltar que los métodos implementados para interpolar datos se aplican computacionalmente
en cuadernos de IPython, lo cuál facilita las operaciones.
151
interpolaciones polinomiales que se ajustan a los mismos datos son matemáticamente idénticas
[17]". Ahora suponga que se dan N+1 pares de puntos como:
x0 x1 · · · xN
f0 f1 · · · fN
Los valores de f dependen de x (f(x)). El polinomio formado por estos puntos de grado N se
expresa en serie de potencias como [17]:
PN (x) = a0 + a1 x + · · · + aN xN
En la ecuación anterior, ai son los coeficientes del polinomio. Para los N+1 puntos se obtiene
un sistema de ecuaciones al evaluar cada x en el polinomio:
f0 = a0 + a1 x0 + · · · + aN x0 N
f0 = a0 + a1 x1 + · · · + aN x1 N
..
.
f0 = a0 + a1 x2 + · · · + aN x2 N
Por lo tanto, los métodos que se presentan a continuación son altamente aconsejables en la
búsqueda de la interpolación polinomial.
Al polinomio PN (x) resultante de la interpolación se le denomina Polinomio interpolante, a
cada xi se le denomina nodo de interpolación y a cada yi se le denomina valor interpolado [16]:
No se requiere que los datos estén igualmente espaciados ni en algún orden [16]
152
Si f es un polinomio de grado k ≤ n, el polinomio interpolante de f en n+1 puntos coincide
con f [16].
Si el grado de Pn es ≤ n podría pasar que tres puntos estén sobre una recta y así el
polinomio tendría grado cero o grado uno [16].
El error que se obtiene con una interpolación polinomial se define como [17]
Ahora se elige un valor η que satisface x0 < η < xN y se define una nueva función como [17]
Al unir las ecuaciones 8.1 y 8.2, la expresión anterior se puede reescribir así
pN +1 (ξ) = f N +1 (ξ)s(η)N ! = 0
1 N +1
s(ξ) = f (ξ)
N!
Al realizar un cambio de notación de η y ξ a x y ξ, respectivamente [17], se obtiene
1 N +1
s(x) = f (ξ)
N!
Finalmente, al reemplazar esta expresión en la ecuación 8.2 se obtiene el error de interpolación
de orden N :
1
e(x) = (x − x0 )(x − x1 ) · · · (x − xN )f N +1 (ξ), x0 < ξ < xN
N!
153
8.1.1. Método de Lagrange
Siempre es posible construir un polinomio único de grado n que pasa por n + 1 puntos de
datos distintos. Un medio para obtener este polinomio es la fórmula de Lagrange:
n
X
Pn (x) = yi li (x) (8.4)
i=0
donde n es el grado del polinomio, yi equivalen a f (xi ) y li son los polinomios de Lagrange,
que equivalen a:
n
x − x0 x − x1 x − xn Y x − xj
li (x) = · ··· = i = 0, · · · , n
xi − x0 xi − x1 xi − xn j=0 xi − xj
j6=i
Para presentar la idea básica que subyace en la fórmula de Lagrange, se debe considerar el
siguiente producto:
V0 (x) = (x − x1 )(x − x2 ) · · · (x − xN )
(x − x1 )(x − x2 ) · · · (x − xN )
V0 (x) =
(x0 − x1 )(x0 − x2 ) · · · (x0 − xN )
(x − x0 )(x − x1 ) · · · (x − xN )
Vi (x) =
(xi − x0 )(xi − x1 ) · · · (xi − xN )
154
(x − x1 )(x − x2 ) · · · (x − xN )
g(x) = f0
(x0 − x1 )(x0 − x2 ) · · · (x0 − xN )
(x − x0 )(x − x2 ) · · · (x − xN )
+ f1
(x1 − x0 )(x1 − x2 ) · · · (x1 − xN )
..
.
(x − x0 )(x − x1 ) · · · (x − xN −1 )
+ fN
(xN − x0 )(xN − x1 ) · · · (xN − xN −1 )
x0 = 1 f (x0 ) = 1
x1 = 2 f (x1 ) = 8
x2 = 3 f (x2 ) = 27
El polinomio interpolado se halla aplicando la fórmula de Lagrange, en la que los yi son los
f(x) de los datos anteriores.
155
x−2x−3 x−1x−3 x−1x−2
L(x) = 1 · +8· + 27 ·
1−2 1−3 2−1 2−3 3−1 3−2
Ya con esto se puede evaluar cualquier x entre este rango reemplazándolo en la expresión
anterior.
El código que permite implementar este método computacionalmente en IPython se presenta
en el ejercicio 2 al final del capítulo.
P0 (x) = a3
156
P3 (x) = a0 + (x − x0 )P2 (x)
Los coeficientes an son determinados forzando a los polinomios pasar por los puntos yi =
Pi (x) para i = 1, 2, · · · , n. Con estas simplificaciones, se obtienen las siguientes ecuaciones
y0 = a0
y1 = a0 + (x1 − x0 )a1
..
.
yn = a0 + (xn − x0 )a1 + · · · + (xn − x0 )(xn − x1 ) · · · (xn − xn−1 )an
yi −y0
∇yi = xi −x0 i = 1, 2, · · · , n
∇yi −∇y1
∇2 yi = xi −x1 i = 2, 3, · · · , n
∇2 yi −∇2 y2
∇3 yi = xi −x2 i = 3, 4, · · · , n
..
.
Al unir las expresiones de los coeficientes y las diferencias divididas, se obtiene la solución
a los coeficientes:
a0 = y0 a1 = ∇y1 a2 = ∇2 y2 an = ∇n yn
157
Los términos que aparecen en la diagonal de la tabla de diferencias divididas son los
coeficientes del polinomio. Para generar esta tabla en un cuaderno de IPython se implementa
el siguiente código:
a = yData.copy()
for k in range(1,n+1):
for i in range(k,n+1):
def evalPoly(a,xData,x):
for k in range(1,n+1):
def coeffts(xData,yData):
158
a[k:m] = (a[k:m] - a[k-1])/(xData[k:m] - xData[k-1])
Por ejemplo, si se desean interpolar los siguientes datos por el método de Newton:
import numpy as np
import math
# Datos en x y datos en y
xData = np.array([0.15,2.3,3.15,4.85,6.25,7.95])
yData = np.array([4.79867,4.49013,4.2243,3.47313,2.66674,1.51909])
for x in np.arange(0.0,8.1,0.5):
#Se encuentran las soluciones al sistema entre 0 y 8 cada 0.5 con el método
#de interpolación de Newton
y = evalPoly(a,xData,x)
Con el código anterior se imprime una tabla que en la primera columna presenta el valor de
x. En la segunda columna se imprime el valor en y del valor de la columna anterior generado
con el método de interpolación de Newton. En la última columna se imprime el valor exacto de
la función para el valor de x dado. Así, el resultado es
x yInterp yExact
-----------------------
159
0.0 4.80003 4.80000
0.5 4.78518 4.78520
1.0 4.74088 4.74090
1.5 4.66736 4.66738
2.0 4.56507 4.56507
2.5 4.43462 4.43462
3.0 4.27683 4.27683
3.5 4.09267 4.09267
4.0 3.88327 3.88328
4.5 3.64994 3.64995
5.0 3.39411 3.39411
5.5 3.11735 3.11735
6.0 2.82137 2.82137
6.5 2.50799 2.50799
7.0 2.17915 2.17915
7.5 1.83687 1.83688
8.0 1.48329 1.48328
Con base en este ejemplo se concluye que la interpolación por el método de Newton es
bastante aproximada al valor exacto. Como el valor exacto y el valor por interpolación difieren
hasta la cuarta cifra decimal, se puede afirmar que el método de interpolación de Newton se
puede aplicar sin obtener errores considerables.
P0 [xi ] = yi
..
.
(x − xi+k )Pk−1 [xi , xi+1 , · · · , xi+k−1 ] − (xi − x)Pk−1 [xi+1 , xi+2 , · · · , xi+k ]
Pk [xi , xi+1 , · · · , xi+k ] =
xi − xi+k
160
El algoritmo de Neville busca determinar el valor del polinomio interpolante para un valor
específico x. Con base en la fórmula recursiva anterior, el algoritmo de Neville genera una tabla
simétrica de valores de algunos polinomios interpolantes para un valor de x [22].
Se puede llevar a acabo el computo visualizando los datos en la siguiente tabla
P0 [xi ] P1 [xi , xi+1 ] P2 [xi , xi+1 , xi+2 ] P3 [xi , xi+1 , xi+2 , xi+3 ]
(x−xi+1 )P0 [xi ]−(xi −x)P0 [xi+1 ] (x−xi+2 )P1 [xi ,xi+1 ]−(xi −x)P1 [xi+1 ,xi+2 ]
xi yi xi −xi+1 xi −xi+2
x0 P0 [x0 ] = y0 P1 [x0 , x1 ] P2 [x0 , x1 , x2 ] P3 [x0 , x1 , x2 , x3 ]
x1 P0 [x1 ] = y1 P1 [x1 , x2 ] P2 [x1 , x2 , x3 ]
x2 P0 [x2 ] = y2 P1 [x2 , x3 ]
x3 P0 [x3 ] = y3
La primera columna de la tabla contiene los polinomios dados por las fórmulas anteriores. Las
columnas subsecuentes se llenan calculando las entradas recursivamente de las dos anteriores
en la columna anterior, siguiendo la fórmula de recursividad [22]. Por ejemplo, el elemento
P3 [x0 , x1 , x2 , x3 ] se puede encontrar implementando la fórmula de recursividad y las funciones
P2 [x0 , x1 , x2 ] y P2 [x1 , x2 , x3 ] de la columna anterior:
# Función que evalua el polinomio interpolante que pasa por los puntos d
#dos por medio del método de Neville
def neville(xData,yData,x):
m = len(xData) #Numero de puntos de datos
y = yData.copy()
for k in range(1,m):
161
return y[0] #Retorna el valor de y(x)
import numpy as np
import math
# Datos en x y en y
xData = np.array([4.0,3.9,3.8,3.7])
yData = np.array([-0.06604,-0.02724,0.01282,0.05383])
162
am+1 + am xi + · · · + a1 xi m − R(x)(bn+1 + bn xi + · · · + b1 xi n ) = 0
Ahora se presenta un ejemplo que demuestra la dificultad que existe al resolver este sistema
y, por lo tanto, la interpolación racional es más compleja que la interpolación polinomial [22].
Se tienen los siguientes datos:
x 0 1 2
y 1 2 2
a0 − b0 = 0
a0 + a1 − 2(b0 + b1 ) = 0
a0 + 2a1 − 2(b0 + 2b1 ) = 0
a0 = 0 b0 = 0 a1 = 2 b1 = 1
R(x) = 2
Esta solución no se cumple para el par de datos obtenidos con x = 0. Por lo tanto, la solución
obtenida no soluciona el sistema en su totalidad. Como solucionar el sistema implica incluir la
solución para el valor x = 0, se concluye que no existe solución al problema [22].
Con el ejemplo anterior se demuestra que un problema que implica una interpolación racional
no necesariamente tiene solución. Esto ocurre cuando la expresión racional no soluciona el
sistema de ecuaciones en su totalidad [22].
donde
163
x − xi R[xi+1 , xi+2 , · · · , xi+k ] − R[xi , xi+1 , · · · , xi+k−1 ]
S= 1− −1
x − xi+k R[xi+1 , xi+2 , · · · , xi+k ] − R[xi+1 , xi+2 , · · · , xi+k−1 ]
Se desea determinar determinar y(0,5). Tal función no es una buena candidata para la
interpolación polinómica, pero se puede representar fácilmente mediante una función racional.
import numpy as np
def rational(xData,yData,x):
for k in range(m-1):
164
for i in range(m-k-1):
#Se desea obtener una diferencia con los datos menor a 1x10^-9
return yData[i+k+1]
else:
c1 = r[i+1] - r[i]
c2 = r[i+1] - rOld[i+1]
c3 = (x - xData[i])/(x - xData[i+k+1])
r[i] = r[i+1] + c1/(c3*(1.0 - c1/c2) - 1.0)
rOld[i+1] = r[i+1]
Ahora se quiere interpolar los datos que se muestran y graficar los resultados, utilizando
tanto la interpolación polinómica como la interpolación de la función racional.
import numpy as np
import matplotlib.pyplot as plt
#Datos en x y en y
xData = np.array([0.1,0.2,0.5,0.6,0.8,1.2,1.5])
yData = np.array([-1.5342,-1.0811,-0.4445,-0.3085,-0.0868,0.2281,0.3824])
for i in range(n):
165
Figura 8.2: Comparación entre el método de interpolación de Neville y el método racional
y[i,0] = rational(xData,yData,x[i])
y[i,1] = neville(xData,yData,x[i])
166
Figura 8.3: Ilustración de la división por segmentos en la interpolación segmentaria cúbica
00 00
fi−1,i (xi ) = fi,i+1 (xi ) = ki
k0 = kn = 0
00
fi,i+1 (xi ) = ki li (x) + ki+1 li+1
donde se cumple
167
x − xi+1 x − xi
li (x) = li+1 (x) =
xi + xi+1 xi+1 − xi
00 ki (x − xi+1 ) − ki+1 (x − xi )
fi,i+1 (xi ) =
xi + xi+1
ki (x − xi+1 )3 − ki+1 (x − xi )3
fi,i+1 (x) = + A(x − xi+1 ) − B(x − xi ) (8.5)
6(xi − xi+1 )
ki (xi − xi+1 )3
+ A(xi − xi+1 ) = yi
6(xi − xi+1 )
yi ki
A= − (xi − xi+1 )
xi − xi+1 6
Similarmente con la condición fi,i+1 (xi+1 ) = yi+1 se cancela el término con el factor A y al
despejar B de la expresión resultante se obtiene
yi+1 ki+1
B= − (xi − xi+1 )
xi − xi+1 6
" # " #
ki (x − xi+1 )3 ki+1 (x − xi )3
fi,i+1 (x) = − (x − xi+1 )(xi − xi+1 ) − − (x − xi )(xi − xi+1 )
6 xi − xi+1 6 xi − xi+1
yi (x − xi+1 ) − yi+1 (x − xi )
+
xi − xi+1
168
Para hallar los ki , que corresponden a las curvaturas, se utiliza la condición de continuidad
para las primeras derivadas,
0 0
fi−1,i (xi ) = fi−1,i (xi )
y − yi
i−1 yi − yi+1
ki−1 (xi−1 − xi ) + 2ki (xi−1 − xi+1 ) + ki+1 (xi − xi+1 ) = 6 −
xi−1 − xi xi − xi+1
para i = 1, ..., n − 1
6
ki−1 + 4ki + ki+1 = (yi−1 − 2yi + yi+1 )
h2
import numpy as np
#Función que retorna los valores de las diagonales de una matriz A descompuesta
#en dos matrices: LU. c,d y e son las diagonales de A
def LUdecomp3(c,d,e):
n = len(d) #Longitud de d
169
#Función que soluciona el sistema Ax=b, donde c, d y e son los vectores
#retornados por la función LUdecomp3
def LUsolve3(c,d,e,b):
n = len(d) #Longitud de d
b[n-1] = b[n-1]/d[n-1]
# Retorna los valores de la curvatura. Para ello, como parámetros entran los
arreglos con los valores en x y en y.
def curvatures(xData,yData):
170
LUsolve3(c,d,e,k) #Se resuelve el sistema
def evalSpline(xData,yData,k,x):
def findSegment(xData,x):
iLeft = 0
iRight = len(xData)- 1
while 1:
if (iRight-iLeft) <= 1:
return iLeft
i =(iLeft + iRight)/2
if x < xData[i]:
iRight = i
else:
iLeft = i
Si se desea aplicar el código anterior, se deben crear los arreglos con los datos en x y los
datos en y de los pares de puntos que se desean interpolar mediante el método de segmentación
cúbica. Por ejemplo:
171
#Datos en x y en y
xData = np.array([1,2,3,4,5],float)
yData = np.array([0,1,0,1,0],float)
x=1.5
print("Para x=1.5, y",evalSpline(xData,yData,k,x))
x2=4.5
print("Para x=4.5, y",evalSpline(xData,yData,k,x2))
8.4. Ejercicios
8.4.1. Lagrange
El ejercicio consiste en hallar el cero de la función y(x) de los siguientes datos
Para ello, se debe implementar interpolación de Lagrange sobre los cuatro puntos más
cercanos. El siguiente código permite aplicar el método de Lagrange en IPython para cualquier
número de pares de datos:
#Función que evalua P(x), con x como parámetro, por medio de una interpolación
#polinómica de Lagrange de los arreglos xData y yData
def lagrange(xData,yData,x):
172
p=0 #Variable para guardar el polinomio evaluado en x
for i in range(n):
for j in range(n):
Con los valores dados en el ejercicio se debe crear un arreglo de numpy para los datos en x
y un arreglo de numpy para los datos en y. Así, si se desea interpolar un conjunto de datos, se
deben crear dos arreglos de numpy con los datos de la variable independiente y de la variable
dependiente. Posteriormente, se llama la función anterior y como parámetros entran los arreglos
creados en x y en y.
Como se desea encontrar el cero de la función y(x), el valor de y es igual a cero y se requiere
el valor de x. Sin embargo, la función lagrange() encuentra el valor de y(x) dado x. Por lo
tanto, se aplica esta función pero se intercambian los arreglos de x y y, como si la variable
independiente ahora fuese y y el resultado de la función será el valor de x para el cual y es igual
a cero:
2.3397343004255111
Con esto se encuentra el valor en x para el que y(x) es igual a cero. Este valor es 2.3397343004255111
8.4.2. Neville
La función y(x) del ejercicio anterior tiene un máximo en x=0.7692. Este ejercicio consiste
en calcular este máximo usando el método de Neville sobre los 4 puntos más cercanos. Para
resolver este ejercicio, se aplica el siguiente código descrito anteriormente:
#Neuville
173
import numpy as np
import math
def neville(xData,yData,x):
for k in range(1,m):
xData = np.array([0.0,0.5,1.0,1.5])
yData = np.array([1.8421,2.4694,2.4921,1.9047])
#Cero en x=0.7692
174
9
9.1. Introducción
El ajuste de curvas basa su teoría en encontrar la curva que posea una serie de puntos, y en
determinados casos que se ajuste a restriciones predeterminadas. En este capitulo se mencionara
metodos de interpolación de curvas, explicando distintos métodos computacionales que sirven
en el análisis de datos numericos en función de dicho fin.
y = mx + b
Dicho esto, es menester recordar que existe numerosas leyes fisicas en las que se sabe de antemano
que dos magnitudes x e y se relacionan a través de la anterior ecuación lineal. Donde las
constantes b (ordenada en el origen) y m (pendiente) dependen del tipo de sistema que se
estudia y, a menudo, son los parámetros que se pretende encontrar.
9.2.1. Ajuste de una linea recta por el metodo de los mínimos cuadrados
Dado un conjunto de parejas (xi , yi ) de datos experimentales, se es posible encontrar la
ecuación de la recta
f (x) = mx + b
que traza su trayectoria lo más cercano posible a los puntos experimentales, lo que por ende
provoca que dichos puntos estén repartidos de manera equitativa alrededor de la recta.[? ]
El procedimiento que sigue el ajuste por mínimos cuadrados permite obtener la pendiente m
y la ordenada b en el origen, correspondientes a la ecuación de recta que mejor se ajusta a
los n datos experimentales (xi , yi ).Las expresiones que permiten calcular la pendiente m y la
175
ordenada en el origen b son:[11]
b = y − mx
x = 1/nΣni=1 [xi ]
y = 1/nΣni=1 [yi ]
s
Σni=1 [d2i ]
εb ≈ [(1/n) + (x2 /D)]
n−2
En donde di = yi − mxi − b
176
Ejemplo[12]
Use el método de mínimos cuadrados para determinar la ecuación de la recta que mejor se
ajusta para los datos. Luego grafique la recta.
x 8 2 11 6 5 4 12 9 6 1
y 3 10 3 6 8 12 1 4 9 14
Solución
2.Calcule las medias de los valores de x y los valores de y, la suma de los cuadrados
de los valores de x, y la suma de cada valor de x multiplicado por su valor correspondiente
y.
x y xy x2
8 3 24 64
2 10 20 4
11 3 33 121
6 6 36 36
5 8 40 25
4 12 48 16
12 1 12 144
9 4 36 81
6 9 54 36
1 14 14 1
Σx = 64 Σy = 70 Σx y = 317 Σ2x = 528
177
3.Calcule la pendiente.
Σxy − ( (Σx)(Σy)
n )
m= 2
Σx2 − ( (Σx)
n )
317 − ( (64)(70)
10 )
m= 2
528 − ( (64)
10 )
m ≈ −1,1
Σxy − ( (Σx)(Σy)
n )
m= 2
Σx2 − ( (Σx)
n )
4.Calcule la intercepción en y.
Calcule la media de los valores de x y la media de los valores de y.
Σx
x̄ = = 64/10 = 6,4
n
Σy
ȳ = = 70/10 = 7,0
n
y = −1,1x + 14,0.
178
9.2.2. Interpolación Polinomial
En esta sección del libro se procedera a explicar el como se puede aproximar una función
mediante su polinomio de interpolación.
De esta manera la teoria comienza con dados n + 1 puntos de R2 (x0 , y0 ) , (x1 , y1 ), ...., (xn , yn )
en donde las absscisas son números diferentes, se busca un polinomio de grado menor o igual a
n de tal manera que :
Pn (xk ) = yk
k = 0, 2, ..., n
Este polinomio permitirá aproximar una función yk = f (xk ) de la cual no se onozca una formula
explícita o que tenga complejidda en procesos de derivación, integración, hallar raices o ceros
etc. El polinomio a su vez se usa como aproximación de la función y para aproximar valores de
la función en puntos intermedios de los valores conocidos xk .
179
a0 + a1 x0 + a2 x20 + ... + an xn0 = y0
a0 + a1 x1 + a2 x21 + ... + an xn1 = y1
2 n
a0 + a1 x2 + a2 x2 + ... + an x2 = y2
..
.
a0 + a1 xn + a2 x2n + ... + an xnn = yn
Ejemplo[8]
Sabiendo que:
f (x0 ) = 0
f (x1 ) = 1
f (x2 ) = 0
a0 + a1 −π −π 2
2 + a2 ( 2 ) = 0
a0 + a1 0 + a2 02 = 1
a0 + a1 π2 + a2 ( −π 2
2 ) =0
−π π2
1 2 4 a0 0
1 0 0 a1 = 1
π π2 a2 0
1 2 4
180
3. Se plantea la matriz aumentada para aplicar Gauss-Jordan, y se procede a resolver el sistema
−π π2
1 2 4 | 0
1 0 0 | 1
π π2
1 2 4 | 0
1 0 0 | 1
0 1 0 | 0
0 0 1 | − π42
a0 = 1
a1 = 0
4
a2 = − 2
π
De modo que:
4 2
P(x) = 1 − [( )x ]
π2
Dado que el objetivo de esta bitácora es resolver problemas matemáticos o físicos bajo el uso de
códigos en determinados lenguajes, se presentara a continuación un método para la resolución
de ajuste polinomial en Python.
181
Primera parte
## module gaussElimin
’’’ x = gaussElimin(a,b).
Solves [a]{b} = {x} by Gauss elimination.
’’’
import numpy as np
def gaussElimin(a,b):
n = len(b)
# Elimination Phase
for k in range(0,n-1):
for i in range(k+1,n):
if a[i,k] != 0.0:
lam = a [i,k]/a[k,k]
a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n]
b[i] = b[i] - lam*b[k]
# Back substitution
for k in range(n-1,-1,-1):
b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]
return b
Segunda parte
## module error
’’’ err(string).
Prints ’string’ and terminates program.
’’’
import sys
def err(string):
print(string)
input(’Press return to exit’)
sys.exit(0)
Tercera parte
## module polyFit
’’’ c = polyFit(xData,yData,m).
Returns coefficients of the polynomial
p(x) = c[0] + c[1]x + c[2]x^2 +...+ c[m]x^m
that fits the specified data in the least
squares sense.
sigma = stdDev(c,xData,yData).
Computes the std. deviation between p(x)
182
and the data.
’’’
import numpy as np
import math
def polyFit(xData,yData,m):
a = np.zeros((m+1,m+1))
b = np.zeros(m+1)
s = np.zeros(2*m+1)
for i in range(len(xData)):
temp = yData[i]
for j in range(m+1):
b[j] = b[j] + temp
temp = temp*xData[i]
temp = 1.0
for j in range(2*m+1):
s[j] = s[j] + temp
temp = temp*xData[i]
for i in range(m+1):
for j in range(m+1):
a[i,j] = s[i+j]
return gaussElimin(a,b)
def stdDev(c,xData,yData):
def evalPoly(c,x):
m = len(c) - 1
p = c[m]
for j in range(m):
p = p*x + c[m-j-1]
return p
n = len(xData) - 1
m = len(c) - 1
sigma = 0.0
for i in range(n+1):
p = evalPoly(c,xData[i])
sigma = sigma + (yData[i] - p)**2
sigma = math.sqrt(sigma/(n - m))
return sigma
Cuarta parte
## module plotPoly
’’’ plotPoly(xData,yData,coeff,xlab=’x’,ylab=’y’)
Plots data points and the fitting
polynomial defined by its coefficient
183
array coeff = [a0, a1. ...]
xlab and ylab are optional axis labels
’’’
import numpy as np
import matplotlib.pyplot as plt
def plotPoly(xData,yData,coeff,xlab=’x’,ylab=’y’):
m = len(coeff)
x1 = min(xData)
x2 = max(xData)
dx = (x2 - x1)/20.0
x = np.arange(x1,x2 + dx/10.0,dx)
y = np.zeros((len(x)))*1.0
for i in range(m):
y = y + coeff[i]*x**i
plt.plot(xData,yData,’o’,x,y,’-’)
plt.xlabel(xlab); plt.ylabel(ylab)
plt.grid (True)
plt.show()
Ejemplo
Escriba un programa que ajuste los datos a un polinomio de grado m arbitrario. Utilice el
programa para determinar m que mejor se adapte a estos datos, en el sentido de mínimos
cuadrados.
#!/usr/bin/python
## example3_12
import numpy as np
xData = np.array([-0.04,0.93,1.95,2.90,3.83,5.0,5.98,7.05,8.21,9.08,10.09])
yData = np.array([-8.66,-6.44,-4.36,-3.27,-0.88,0.87,3.31,4.63,6.19,7.4,8.85])
m = np.array([1,2,3,4])
for i in range(4):
coeff = polyFit(xData,yData,m[i])
print(m[i])
print("Coefficients are:\n",coeff)
print("Std. deviation =",stdDev(coeff,xData,yData))
184
Respuesta
1
(’Coefficients are:’, array([-7.94533287, 1.72860425])) (’Std. deviation =’, 0.5112788367370911)
2
(’Coefficients are:’, array([-8.57005662, 2.15121691, -0.04197119])) (’Std. deviation =’, 0.3109920728551077)
3
(’Coefficients are:’, array([ -8.46603423e+00, 1.98104441e+00, 2.88447008e-03, -2.98524686e-03]))
(’Std. deviation =’, 0.3194817915675318)
4
(’Coefficients are:’, array([ -8.45673473e+00, 1.94596071e+00, 2.06138060e-02,-5.82026909e-03,
1.41151619e-04])) (’Std. deviation =’, 0.34485841047940374)
%pylab inline
y=-8.57+2.1512*xData-0.0419*xData**2
plt.plot(xData,y)
plt.scatter(xData,yData)
plt.show()
185
9.2.3. Regresión Lineal
9.3. Ejercicios
9.3.1. Ejercicio 1
Primera Parte
186
Segunda Parte
%pylab inline
import numpy as np
import matplotlib.pyplot as plt
xData = np.array([-250,-200,-100,0.0,100,300])
yData = np.array([0.0163,0.318,0.699,0.870,0.941,1.04])
#Datos en x & y
x = np.arange(-250,500,10)
#Defino el rango en x, y su desplazamiento
n = len(x)
# Longitud del arreglo del rango
y = np.zeros((n,2))
# Arreglo de cero que define que sus datos iran de n a 2
for i in range(n):
y[i,0] = rational(xData,yData,x[i])
y[i,1] = neville(xData,yData,x[i])
9.3.2. Ejercicio 2
Usando los datos :
187
Grafique las función resultante usando interpolación polinomial en el intervalo de x= 0 a x= 1.
Primera Parte
def neville(xData,yData,x):
m = len(xData)
# number of data points
y = yData.copy()
for k in range(1,m):
y[0:m-k] = ((x - xData[k:m])*y[0:m-k] +(xData[0:m-k] - x)*y[1:m-k+1])/(xData[0:m
return y[0]
#procreimiento Nevile
Segunda Parte
import numpy as np
import math
import matplotlib.pyplot as plt
xData = np.array([0.0,0.0204,0.1055,0.241,0.582,0.712,0.981])
yData = np.array([0.385,1.04,1.79,2.63,4.39,4.99,5.27])
#Datos en x & y
x = np.arange(0,1.0,0.0002)
# rango de 0 a 1 con un intervalo de 0.0002
n = len(x)
#longitud de datos guardados en n
y = np.zeros((n))
# Creo una serie de ceros para guardar los datos de la funcio neville
for i in range(n):
#El for es para que en la cantidad de datos de x de las
#datos de la interpolacion por nevile
y[i] = neville(xData,yData,x[i])
# Guardo los datos en y
plt.plot(xData,yData,’o’,x,y[:],’-’)
#defino que graficar y los simbolos caracteristicos
plt.xlabel(’x’)
plt.ylabel(’y’)
#ombre ejes
plt.legend((’Data’,’Neville’),loc = 0)
#loc = 0 deine el lugar de la leyenda
plt.show()
#imprimo grafica
188
Resultado
9.3.3. Ejercicio 3
La tabla muestra los coeficientes de arrastre CD de una esfera como función de número de
Reynolds Re.Use la interpolación segmentaria cúbica para encontrar CD en Re = 5, 50 500 y
5000. Use escala log-log
Primera Parte
import numpy as np
def LUdecomp3(c,d,e):
n = len(d)
for k in range(1,n):
lam = c[k-1]/d[k-1]
d[k] = d[k] - lam*e[k-1]
c[k-1] = lam
return c,d,e
def LUsolve3(c,d,e,b):
n = len(d)
for k in range(1,n):
b[k] = b[k] - c[k-1]*b[k-1]
189
b[n-1] = b[n-1]/d[n-1]
for k in range(n-2,-1,-1):
b[k] = (b[k] - e[k]*b[k+1])/d[k]
return b
# module cubicSpline
#k = curvatures(xData,yData).
#Devuelve las curva de interpolacion cubica a sus interseccione
#y = evalSpline(xData,yData,k,x).
#Evalua las interpolacione de x, la curvas se computan con las\
#funcion curvaturas
def curvatures(xData,yData):
n = len(xData) - 1
c = np.zeros(n)
d = np.ones(n+1)
e = np.zeros(n)
k = np.zeros(n+1)
c[0:n-1] = xData[0:n-1] - xData[1:n]
d[1:n] = 2.0*(xData[0:n-1] - xData[2:n+1])
e[1:n] = xData[1:n] - xData[2:n+1]
k[1:n] =6.0*(yData[0:n-1] - yData[1:n]) \
/(xData[0:n-1] - xData[1:n]) \
-6.0*(yData[1:n] - yData[2:n+1])\
/(xData[1:n] - xData[2:n+1])
LUdecomp3(c,d,e)
LUsolve3(c,d,e,k)
return k
def evalSpline(xData,yData,k,x):
def findSegment(xData,x):
iLeft = 0
iRight = len(xData)- 1
while 1:
if (iRight-iLeft) <= 1: return iLeft
i =(iLeft + iRight)/2
if x < xData[i]: iRight = i
else: iLeft = i
i = findSegment(xData,x)
h = xData[i] - xData[i+1]
y = ((x - xData[i+1])**3/h - (x - xData[i+1])*h)*k[i]/6.0 \
- ((x - xData[i])**3/h - (x - xData[i])*h)*k[i+1]/6.0 \
+ (yData[i]*(x - xData[i+1]) \
- yData[i+1]*(x - xData[i]))/h
return y
Segunda Parte
%pylab inline
190
import numpy as np
from matplotlib import pyplot
#importo paquetes
xData = np.array([0.2,2,20,200,2000,200000],float)
yData = np.array([103,13.9,2.72,0.800,0.401,0.433],float)
#datos x y y
x= np.array([5,50,500,5000])
# Hago un arreglo para guardar los datos que necesite
plog = np.log10(x)
# guardo en la variable un arreglo de los logaritmos
#de los datos de x
leng = len (plog)
#Defino la variable siendo la longitud de los datos log
curv = curvatures(log10(xData),log10(yData))
#defino la varible, como la uncion con parametros de entrada logaritmicos
a = np.zeros((leng))
b = np.zeros((leng))
#hago un arreglo en ceros para guardar datos
print(" Re------cD------log(Re)--------log(cD)")
print("-----------------------------------------------------------")
for i in range (leng):
a[i] = evalSpline(log10(xData),log10(yData),curv,plog[i])
b[i] = (10**a[i])
#Gaurdo en las variables creadas previamente las funciones y sus parametros
print("{0}~~~{1}~~~{2}~~~{3}".format(x[i],b[i],plog[i],a[i]) )
# Imprimo los datos segun las posiciones
Imprime
Re——cD——log(Re)——–log(cD)
———————————————————–
5 6.909512479172732 0.6989700043360189 0.83944740553998
50 1.5841904210326676 1.6989700043360187 0.19980738295631154
500 0.5661955375766144 2.6989700043360187 -0.24703355781353212
5000 0.360433711482771 3.6989700043360187 -0.44317459608851395
9.3.4. Ejercicio 4
Resuelva el problema 3 usando interpolación polinomial intersectando los 4 puntos más
cercanos. No use escala logarítmica. Comente los resultados.
a5x = np.array([0.2,2,20,200])
b5y = np.array([103,13.9,2.72,0.800])
c50x = np.array([2,20,200,2000])
d50y = np.array([13.9,2.72,0.800,0.401])
191
e500x = np.array([20,200,2000,20000])
f500y = np.array([2.72,0.800,0.401,0.433])
g5000x = np.array([20,200,2000,20000])
h5000y = np.array([2.72,0.800,0.401,0.433])
#Defino los rango para x y y de cada numero segun lo estipulado den el taller
print(" Re cD ")
print("-----------------------")
print("5" "....",neville(a5x,b5y,5))
print("50" "...",neville(c50x,d50y,50))
print("500" "..",neville(e500x,f500y,500))
print("5000" ".",neville(g5000x,h5000y,5000))
#imprimo los datos para la interpolacion segun neville
Imprime
Re cD
———————–
5.... -96.3862601996
50... -11.1410046713
500.. -1.58345588012
5000. 56.8062612916
Para comparar los datos log de neville e interpolacion cubica se presentara el siguiente
ejemplo:
a5x = np.array([0.2,2,20,200])
b5y = np.array([103,13.9,2.72,0.800])
c50x = np.array([2,20,200,2000])
d50y = np.array([13.9,2.72,0.800,0.401])
e500x = np.array([20,200,2000,20000])
f500y = np.array([2.72,0.800,0.401,0.433])
g5000x = np.array([20,200,2000,20000])
h5000y = np.array([2.72,0.800,0.401,0.433])
#Defino los rango para x y y de cada numero segun lo estipulado den el taller
print(" Re cD ")
print("-----------------------")
print("5" "....",neville(log10(a5x),log10(b5y),log10(5)))
print("50" "...",neville(log(c50x),log10(d50y),log10(50)))
print("500" "..",neville(log10(e500x),log10(f500y),log10(500)))
print("5000" ".",neville(log10(g5000x),log10(h5000y),log10(5000)))
192
Imprime
Re cD
———————–
5.... 0.840893967971
50... 0.815278708576
500.. -0.249686256629
5000. -0.429192311228
Podemos observar que los números son bastante diferentes, de esta manera podemos ver que el
método neville se aleja a los datos expresados por el método de interpolación cúbica.
9.3.5. Ejercicio 5
Determine a y b tal que
f (x) = axb
Primera Parte
193
return f3,f2
Segunda Parte
x = np.array([0.5,1.0,1.5,2.0,2.5])
y = np.array([0.49,1.60,3.36,6.44,10.16])
print (minimos(x,y))
Imprime
(1.7031397871561753, 1.8817501813721569)
9.3.6. Ejercicio 6
La regresión lineal se puede generalizar a datos que dependen de dos o más variables. Si la
variable dependiente es z y las variables independientes son x y y, los datos a ajustar son:
x1 y1 z1
x2 y2 z2
.. .. ..
. . .
xn yn zn
n Σxi Σyi
Σxi Σx2i Σyi xi
Σyi Σyi xi Σyi 2
194
Use la interpolación lineal multivariable para determinar la función: f (x, y) = a + bx + cy
195
x y z
0 0 1,42
0 1 1,85
1 0 0.78
2 0 0.18
2 0 0.60
2 2 1.05
import numpy as np
def mtx(x,y,z):
n = len(x)
#Defino longitud para definir
sumx = np.sum(x)#ayuda a formar matriz A
sumy = np.sum(y)#ayuda a formar matriz A
sumz = np.sum(z)#Ayuda a hallar matriz B
#defino variables que sumen los datos de cada conjunto de entradas
xx = np.sum(x*x)#ayuda a formar matriz A
yy = np.sum(y*y)#ayuda a formar matriz A
xy = np.sum(x*y)#ayuda a formar matriz A
zx = np.sum(z*x)#Ayuda a hallar matriz B
zy = np.sum(z*y)#Ayuda a hallar matriz B
a = np.array([[n,sumx,sumy],[sumx,xx,xy],[sumy,xy,xx]]) #matriz A
b = np.array ([sumz,zx,zy]) #Vector b
return a,b
import numpy as np
def gaussJordan(a,b):
n = len(b)
# Eliminacion mtx triangular arriba
for k in range(0,n-1): # Rango columna d 0 a2
for i in range(k+1,n): #rango fila de 1 a 2
if a[i,k] != 0.0: # Si son 0 el resultado seguiria siendo 0
lam = a [i,k]/a[k,k]#condicon cte
a[i,k:n] = a[i,k:n] - lam*a[k,k:n] #Deja fila i fija, y varia en K
b[i] = b[i] - lam*b[k] # Modifica valores de b
# Eliminacion mtx triangular abajo
for k in range(n-1,0,-1):# Rango columna de 2 a 1; el -1 lo leera de 1 a 2
for i in range(k-1,-1,-1):# Rango columna de 1 a 2; el -1 lo leera de izq-der.No si
if a[i,k] != 0.0:
lam = a [i,k]/a[k,k]
a[i,0:n] = a[i,0:n] - lam*a[k,0:n]
196
b[i] = b[i] - lam*b[k]# Modifica valores de b
for k in range (0,n):
b[k]=b[k]/a[k,k] #divide b sobre el numero de la fila respectiva
a[k,k]=a[k,k]/a[k,k] # se divide el elemnto entre si para obener el valor de la diag
return b
Segunda Parte
x = np.array([0,0,1,2,2,2])
y = np.array([0,0.1,0,0,1,2])
z = np.array([1.42,1.85,0.78,0.18,0.60,1.05])
a,b=mtx(x,y,z)
pos = gaussJordan(a,b)
print("para un f(x,y)=a+b+cy")
print("a=",pos[0],"b=",pos[1],"c=",pos[2])
Resultado
197
198
10
Búsqueda de raíces
Hallar las raíces de una función equivale a hallar los valores de x para los cuales
f (x) = 0
Al hallar las raíces de una función también se pueden determinar los máximos y los mínimos
de una función.Para hallar las raíces de polinomios de grado 1 o 2 existen métodos algebraicos
sencillos; sin embargo, para polinomios de grados mayores o funciones no polinómicas es necesario
modelar métodos más complejos que permitan hallar el valor exacto o aproximado de los de
las raíces de la función.En este capítulo se explicarán matemáticamente algunos métodos que
permiten hallar raíces y su modelo en Numpy.
Dónde f(a) tiene signo negativo y f(b) tiene signo positivo. Sin embargo este es uno de los
1
Imagen obtenida en Obtenida de: http://www.sc.ehu.es/sbweb/energias-renovables/MATLAB/numerico/raices/raices.jpg
199
problemas más significativos de este método ya que esta suposición no siempre se cumple.
Para encontrar las raíces con este método se determina un intervalo y un valor de incremento
x. Se inicia en un extremo del intervalo y se avanza según el incremento evaluando la función
buscando un cambio de signo de la siguiente manera:[5]
Con este método es de especial importancia saber elegir x ya que si se elige un incremento
con un valor muy pequeño es probable que la búsqueda tome demasiado tiempo y si se elige
un incremento muy grande es posible que las raices muy cercanas sean tomadas como una
sola. Otro problema con este método es que sólo aplica para funciones continuas en todos los
números reales y por lo tanto con funciones que presentan singularidades se confunden los polos
con raices por ejemplo la función tan(x) que cambia de signo en x = 1/2npi(n = 1, 3, 5..) y sin
embargo estos puntos no son raíces pues la función no los cruza.
Modelo Numpy
200
Primero se define la función rootsearch con parámetros de entrada:
f: función.
a: límite inferior de intervalo.
b: límite superior de intervalo.
dx: Magnitud de incremento.
La variable f1 toma el valor de la función (f) en a, la variable x2 es el valor de a más el incremento
y el total de esto es evaluado en la función y guardado en la variable f2. Luego se crea un loop
con while el cual se realiza mientras que el signo de f1 y f2 sea el mismo. Dentro de este while
se establece un if con la condición de que x1 sea mayor o igual a b; si esto se cumple dentro
del while significa que nunca hubo un cambio de signo y por lo tanto no se retorna ningún
valor. Dentro de este while , si no se cumple el condicional, x1 toma el valor de x2 (a+dx) y
x2 es igual a x1+ dx; estas dos cambios de valor en las variables representan el avance en la
función,luego estos dos valores son evaluados en la función. Si la función evaluada en x1 y en
x2 tiene diferentes signos el programa saldrá del while y retornará los valores en los cuales la
función cambia de signo, es decir el intervalo en el que se encuentra una raíz.
función cambia de signo, es decir el intervalo en el que se encuentra una raíz. Sin embargo,
la búsqueda de raíces no está completa ya que sólo se tiene el intervalo en el que se encuentra.
Por lo tanto para completar el proceso y definir la función es necesario realizar el siguiente
código (usando de ejemplo la función x3 + x2 + 1):
def f(x):
return x^3+ x^2 +1
x1 = 0.0
x2 = 1.0
for i in range(4):
dx = (x2 - x1)/10.0
x1,x2 = rootsearch(f,x1,x2,dx)
x = (x1 + x2)/2.0
print("La raíz de f(x)= x^3+x^2+1 es x =%.4f"%(x))
Primero se define la función con parámetro de entrada un valor x. Luego, se definen los límites
del intervalo (x1 y x2).El recorrido for se ejecuta en un rango de 0 a 4, el cual puede ser cambiado
pero siempre debe ser debe ser grande ya que en este recorrido se va disminuyendo el valor del
incremento dx al dividir la diferencia de los extremos del intervalo sobre 10 y determina el
número de dígitos del resultado, en este caso 4 dígitos. En este mismo recorrido x1 y x2 van
tomando los valores del intervalo resultante de la función rootsearch y por lo tanto el valor de
dx en cada recorrido va disminuyendo, lo cual va proporcionando mayor precisión hasta que i
tome el valor de 3. Al finalizar el recorrido la variable x toma el valor del promedio entre x1 y
x2, los cuales tendrán un valor muy cercano al cero debido al aumento de precisión por cada
recorrido y por lo tanto x tendrá el valor casi exacto de la raíz.Sin embargo, con este código sólo
es posible hallar una raíz y por lo tanto es importante ser cuidadosos al elegir el intervaloen la
función rootsearch.
201
10.0.2. Método de bisección
Este método usa el mismo principio que el método de búsqueda incremental basado en la
aplicación con varias iteraciones del teorema de Bolzano en el cual se establece que si f este
definida en un intervalo cerrado [a,b] tal que f (a)f (b) < 0 entonces f debe tener un cero en
]a,b[. Lo que diferencia este método a la búsqueda incremental es que usa un valor c = (a + b)/2
y compara los extremos con este valor intermedio:
si f (a)f (c) < 0 entonces f tiene una raíz en ]a,c[ y por lo tanto se hace b = c.
si f (a)f (c) > 0 entonces f (c)f (b) < 0 lo cual indica una raíz en ]c,b[ y por lo tanto se
hace c = a.
De este modo se va reduciendo el intervalo a la mitad por cada iteración hasta que el tamaño
del intervalo [a,b] sea menor a una tolerancia establecida.
Con este método también es sencillo saber el número de iteraciones necesarias para hallar las
raíces. Este número depende del tamaño del intervalo original o x y de la tolerancia definida ε.
Entonces :
ln(∆x/ε)
n=
ln(2)
Este método es más preciso que la búsqueda incremental, sin embargo posee problemas
similares con las raíces tangenciales al eje x (ya que no hay cambio de signo en los extremos)
y con las singularidades de algunas funciones.
import sys
def err(string):
print(string)
input(’Press return to exit’)
sys.exit(0)
import math
202
from numpy import sign
def bisection(f,x1,x2,switch=1,tol=1.0e-9):
f1 = f(x1)
if f1 == 0.0:
return x1
f2 = f(x2)
if f2 == 0.0:
return x2
if sign(f1) == sign(f2):
err(’Root is not bracketed’)
n = int(math.ceil(math.log(abs(x2 - x1)/tol)/math.log(2.0)))
for i in range(n):
x3 = 0.5*(x1 + x2)
f3 = f(x3)
if (switch == 1) and (abs(f3) > abs(f1)) and (abs(f3) > abs(f2)):
return None
if f3 == 0.0:
return x3
if sign(f2)!= sign(f3):
x1 = x3
f1 = f3
else:
x2 = x3
f2 = f3
return (x1 + x2)/2.0
import sys
def err(string):
print(string)
input(’Press return to exit’)
sys.exit(0)
No hace parte del método en sí. Es una función creada para imprimir el string que entra
como parámetro y terminar el programa.
Al definir la función bisection se establecen como parámetros:
f: Función.
203
switch=1: Esto es un comando que al ser igual a 1 retorna una raíz y si es igual a
null indica que f (x) aumenta sobre la bisección y por lo tanto esto asegura que la
función converja.
tol=1.0e-9: Es el valor mínimo que debe tomar x2-x1; es decir la longitud del intervalo
final.
Las primeras líneas de código dentro de la función se aseguran que la raíz no sea alguno de
los valores establecidos como límites del intervalo y si lo es retorna el valor correspondiente.Además,
también se asegura que en los extremos del intervalo si haya un cambio de signo; si no
lo hay utiliza la función +err+ para imprimir el mensaje que indica que la raíz no se
encuentra en este intervalo y se sale del programa. Esto representa uno de los problemas
de este método,el de no identificar raíces tangentes al eje x (es decir dobles). Estas líneas
de código son:
if f1 == 0.0:
return x1
f2 = f(x2)
if f2 == 0.0:
return x2
if sign(f1) == sign(f2):
err(’Root is not bracketed’)
if sign(f2)!= sign(f3):
x1 = x3
f1 = f3
204
Si el condicional anterior no se cumple, esto significa que f1 y f3 tienen signos opuestos y
por lo tanto se reduce a la mitad el intervalo estableciendo f3 como límite superior.
Al finalizar este recorrido el intervalo será lo suficientemente pequeño para cumplir con
el valor de tolerancia establecido y por lo tanto se retorna el promedio de la suma de los
límites del intervalo, siendo este valor una raíz de la función.
Para aplicar este código sólo es necesario definir la función a la que se requiere hallar
las raíces antes de llamar a la función .
En análisis numerico el método de Ridder es una aplicación para encontrar una raíz bajo
el uso de un logaritmo basado en el método de falsa posción y el uso de una función
exponencial, otorgando la posibilidad de aproximar la raíz a una función f (x).
De este modo al multiplicar dicha expresión con la función a buscar, la función tendra los
puntos en linea recta. Gráficamente la expresion seria:
G(x) = f (x)e( (x − x1 ) ∗ Q)
g1 = f1
g2 = f2 e( 2hQ)
g3 = f3 e( hQ)
205
En donde h = (x2x1)/2, De modo que el requerimiento para que se cumple la linea
recta como lo se;ala la imagén es que
(g1 + g2 )
g3 =
2
La interpolación lineal basados en los puntos (x1 , g1 ) y (x3 , g3 ) permite probar que para
la raiz improvisada:
x3 x1 x3 x1
x4 = x3 g3 = x3 f3 e( hQ) (
g3 g1 f3 e hQ) − f1
Las cuales usando el cambio de variable respectivo para g1 , g2 , g3 y e( hQ) lo cual resulta
tras un poco de algebra en :
f3
x4 = x3 ± (x3 − x1 ) q
f32 − f1 f2
Puede verse que para un correcto resultado la parte positiba correspondera af1 − f2 > 0
f (xi )
x( i + 1) = xi −
f 0 (xi )
206
1
ex =
x
e( xi ) − 1/xi
x( i + 1) = xi −
e( xi ) − 1/x2j
x0 = 1,0
e1 − 1/1
x1 = 1 − = 0,53788284
e1 − 1/12
ex1 − 1/x1
x2 = x1 − = 0,56627701
ex1 − 1/x21
x3 = 0,56714258
x4 = 0,56714329
x5 = 0,56714329
En otras palabras la fórmula del cálculo de la raíz se puede deducir a partir de una
expansión en series de Taylor
207
Si xi+1 es una raíz, fx+1 = 0
f (xi )
xi+1 = xi −
f 0 (xi )
def newtonRaphson(f,df,a,b,tol=1.0e-9):
fa = f(a)
if fa == 0.0: return a
fb = f(b)
if fb == 0.0: return b
if sign(fa) == sign(fb): err(’Root is not bracketed’)
x = 0.5*(a + b)
for i in range(30):
fx = f(x)
if fx == 0.0: return x
# Tighten the brackets on the root
if sign(fa) != sign(fx): b = x
else: a = x
# Try a Newton-Raphson step
dfx = df(x)
# If division by zero, push x out of bounds
try: dx = -fx/dfx
except ZeroDivisionError: dx = b - a
x = x + dx
# If the result is outside the brackets, use bisection
if (b - x)*(x - a) < 0.0:
dx = 0.5*(b - a)
x = a + dx
# Check for convergence
if abs(dx) < tol*max(abs(b),1.0): return x
print(’Too many iterations in Newton-Raphson’)
208
A modo de ejemplo se presentara las raíces para una función f(x):
def f(x):
return x**4 - 6.4*x**3 + 6.45*x**2 + 20.538*x - 31.752
def df(x):
return 4.0*x**3 - 19.2*x**2 + 12.9*x + 20.538
Solicite la función
raiz=newtonRaphson(f,df,3.0,5.0,tol=1.0e-9)
print (’la raiz para la funcion definida es :’ ,raiz)
Gráfica
209
10.1. Ejercicios
Comience importando los siguientes paquetes para todos los ejercicios que se mostraran
y explicaran a continuacioń.
%pylab inline
import numpy as np
import math
10.1.1. Ejercicio 1
donde (R, θ) son las coordenadas polares del satélite y C, , y α son constantes ( es
conocida como la excentricidad de la orbita). Si el satélite fue observado en las siguientes
tres posiciones determine el R más pequeño en la trayectoria y su correspondiente θ.
θ -30 0 30
R(km) 6870 6728 6615
Primera Parte
def gaussJordan(a,b):
n = len(b)
# Eliminacion mtx triangular arriba
for k in range(0,n-1): # Rango columna d 0 a2
for i in range(k+1,n): #rango fila de 1 a 2
if a[i,k] != 0.0: # Si son 0 el resultado seguiria siendo 0
lam = a [i,k]/a[k,k]#condicon cte
a[i,k:n] = a[i,k:n] - lam*a[k,k:n] #Deja fila i fija, y varia en K
b[i] = b[i] - lam*b[k] # Modifica valores de b
# Eliminacion mtx triangular abajo
for k in range(n-1,0,-1):# Rango columna de 2 a 1; el -1 lo leera de 1 a 2
for i in range(k-1,-1,-1):# Rango columna de 1 a 2; el -1 lo leera de izq-der.
if a[i,k] != 0.0:
lam = a [i,k]/a[k,k]
a[i,0:n] = a[i,0:n] - lam*a[k,0:n]
b[i] = b[i] - lam*b[k]# Modifica valores de b
for k in range (0,n):
210
b[k]=b[k]/a[k,k]
#divide b sobre el numero de la fila respectiva
a[k,k]=a[k,k]/a[k,k]
# se divide el elemento entre si para obener el valor de la diagonal
return b
def newtonRaphson2(f,x,tol=1.0e-9):
def jacobian(f,x):
h = 1.0e-4
n = len(x)
jac = np.zeros((n,n))
f0 = f(x)
for i in range(n):
temp = x[i]
x[i] = temp + h
f1 = f(x)
x[i] = temp
jac[:,i] = (f1 - f0)/h #f1-f0 es un vector
return jac,f0
for i in range(30):
jac,f0 = jacobian(f,x)
if math.sqrt(np.dot(f0,f0)/len(x)) < tol:
return x
dx = gaussJordan(jac,-f0)
x = x + dx
if math.sqrt(np.dot(dx,dx)) < tol*max(max(abs(x)),1.0): return x
print(’Too many iterations’)
Segunda Parte
def fun(pos):
n=len(pos)
eq=np.zeros(n)
#Matriz con 3 ecuaciones
eq[0]= -6870 +(pos[0]/(1+(pos[1]*sin((-pi/6)+pos[2]))))
eq[1]= -6728 +(pos[0]/(1+(pos[1]*sin(pos[2]))))
eq[2]= -6615 +(pos[0]/(1+(pos[1]*sin((pi/6)+pos[2]))))
return eq
211
# 0 = c/(1-esin(theta+alfa))
#creo una funcion que busque tras la creacion de un
# arreglo de ceros, guardar en el datos en respectivas posiciones
# el resutado de la funcion para determinadas condiciones.
cond = np.array([6000,1,pi])
#Se definen las condiciones de entrada para la funcion
c,e,a=newtonRaphson2(fun,cond)
#defino constantes
t = (pi/2)-a
R=c/(1+(e*sin(t+a)))
#defino Funcionces
Imprime
10.1.2. Ejercicio 2
Primera Parte
## module error
’’’ err(string).
Prints ’string’ and terminates program.
’’’
import sys
def err(string):
212
print(string)
input(’Press return to exit’)
sys.exit(0)
## module bisection
’’’ root = bisection(f,x1,x2,switch=0,tol=1.0e-9).
Finds a root of f(x) = 0 by bisection.
The root must be bracketed in (x1,x2).
Setting switch = 1 returns root = None if
f(x) increases upon bisection.
’’’
import math
from numpy import sign
def bisection(f,x1,x2,switch=1,tol=1.0e-9):
#tol=cte epsilon
#switch = converge a 1
#f,x1,x2 =variables
f1 = f(x1)
#Definen variables como la funcion evaluada en variable
if f1 == 0.0:
return x1
f2 = f(x2)
if f2 == 0.0:
return x2
if sign(f1) == sign(f2):
err(’Root is not bracketed’)
n = int(math.ceil(math.log(abs(x2 - x1)/tol)/math.log(2.0)))
#Return the ceiling of x as a float,
#the smallest integer value greater than or equal to x.
for i in range(n):
x3 = 0.5*(x1 + x2)
f3 = f(x3)
if (switch == 1) and (abs(f3) > abs(f1)) and (abs(f3) > abs(f2)):
return None
if f3 == 0.0:
return x3
if sign(f2)!= sign(f3):
x1 = x3
f1 = f3
else:
x2 = x3
f2 = f3
return (x1 + x2)/2.0
213
Segunda Parte
def fun(x):
return x**3 - 75
#Defino la funcion algebraica f(x)
bis = bisection(fun, 0.0, 20.0, tol = 1.0e-4)
# Defino una variable que me llame la operacion bisection
#que con la funcion definida , los rangos a analizar, y la cte estipulada
#permitira sacar la raiz de dicha funcion
print("La raíz de f(x)= x^3-75 es x =%.4f"%(bis))
#imprimo colicando 4 digtos de respuesta para el resultado de la funcion bis
x=linspace(-20,20,1000)
y=fun(x)
plot(x,y)
Imprime
10.1.3. Ejercicio 3
Primera Parte
def rootsearch(f,a,b,dx):
x1 = a
f1 = f(a)
x2 = a + dx
f2 = f(x2)
while sign(f1) == sign(f2):
if x1 >= b:
return None,None
x1 = x2
f1 = f2
x2 = x1 + dx
214
f2 = f(x2)
else:
return x1,x2
#Metodo para encontrar raices comparando la funcion con 2 valores
Segunda Parte
def f(x):
return (x**4)+(0.9*(x**3))-(2.3*(x**2))+(3.6*x)-25.2
#defino Funcion
a,b,dx = (-4, 3, 0.1)
#Defino rango y puntos de busqueda (raices)
print("The roots are:")
while True:
x1,x2 = rootsearch(f,a,b,dx)
if x1 != None:
a = x2
root = bisection(f,x1,x2,1)
if root != None:
print(root)
else:
print("\nDone")
break
x=linspace(-6,3,1000)
y=f(x)
plot(x,y)
Imprime
10.1.4. Ejercicio 4
215
Primera Parte
import math
from numpy import sign
def ridder(f,a,b,tol=1.0e-9):
fa = f(a)
if fa == 0.0: return a
fb = f(b)
if fb == 0.0: return b
if sign(fa) == sign(fb): err(’Root is not bracketed’)
for i in range(30):
# Compute the improved root x from Ridder’s formula
c = 0.5*(a + b); fc = f(c)
s = math.sqrt(fc**2 - fa*fb)
if s == 0.0: return None
dx = (c - a)*fc/s
if (fa - fb) < 0.0: dx = -dx
x = c + dx; fx = f(x)
# Test for convergence
if i > 0:
if abs(x - xOld) < tol*max(abs(x),1.0): return x
xOld = x
# Re-bracket the root as tightly as possible
if sign(fc) == sign(fx):
if sign(fa)!= sign(fx): b = x; fb = fx
else: a = x; fa = fx
else:
a = c; b = x; fa = fc; fb = fx
return None
print(’Too many iterations’)
216
Segunda Parte
def f(x):
a = x*(sin(x))+(3*(cos(x))) - x
return a
#defino funcion a trabajar
x=linspace(-6,6,1000)
y=f(x)
plot(x,y)
#grafico la funcion en un rango definido de -6,6
Imprime
Tercera Parte
print("root =",ridder(f,-6,-4))
print("root =",ridder(f,-4,0))
print("root =",ridder(f,0,6))
#Imprimo las raices llamando la funcion definida
#e introduciendo los rangos respectivos usando el metodo ridder
Imprime
10.1.5. Ejercicio 5
Las frecuencias naturales de una viga estan relacionadas con las raíces βi de la ecuación de
frecuencias:
f (β) = cosh(β)cos(β) + 1 = 0
217
Donde
mL3
βi4 = (2πfi )2
EI
Determine las dos frecuencias mas bajas de una viga de acero de 0.9m de largo, con una sección
transversal rectangular 25 mm de ancho y 2.5mm de alto. La densidad de masa de acero es
7850kg/m3 y E = 200GPa.
Primera Parte
def fun(x):
return cosh(x)*cos(x)+1
#Defino la funcion algebraica f(x) correspondientes a las frecuencias
Segunda Parte
Fíjese que se uso el método de bisección usado en el punto 3.
def funcmin(B):
den = 7845 #densidad
E = 200e9 #cte
b = 0.025 #long.Base
h = 0.0025#altura
l = 0.9#long.Viga
vol = b*h*l#volume
masa = den*vol#masa
I = (1/3)*(masa*(l**2)) #momento de inercias
frec = sqrt(((B**4)*(E*I))/((masa*(l**3))*((2*pi)**2)))
return frec
# defino funcion para determinar frecuencias
218
x=linspace(-6,6,1000)
y=fun(x)
plot(x,y)
#grafica de funcion
#Se observan puntos de defleccion, para ver raices
Imprime
219
220
11
Métodos de derivación
f (x+h)−f (x)
f 0 (x) = lı́mh→0 h
Se aplica la serie de expansión de Taylor de f (x), la cual tiene hasta la k-ésima derivada definida
en el intervalo (a, b) alrededor del punto xi contenido en el intervalo. De esta manera se hallan
las diferencias de manera sistemática y se encuentra fácilmente el termino del error. La serie
resultante será:
(x−x0 ) 0 (x−x0 )2 00 (x−x0 )k (k)
f (x) = f (x0 ) + 1! f (x) + 2! f (x) + ..... + k! f (x)
f 00 (ξ)h2
f (x0 + h) = f (x0 ) + hf 0 (x0 ) + (11.1)
2
x0 < ξ < x 0 + h
f (x0 + h) − f (x0 )
f 0 (x0 ) = − Oh (11.2)
h
f 00 (ξ)
Donde O = 2 , es el error de truncamiento.
221
11.1.1. Diferencias finitas progresivas
Primer caso:
En el primer caso se usa la expresión matemática de la derivada:
f (x+h)−f (x)
f 0 (x) = lı́mh→0 h
La cual se convierte en la ecuación 11.2. Sin embargo, el error de este caso es muy grande ya
que es de orden h.
La ventaja que tiene este primer caso sobre el siguiente, es que para hallar esta derivada sólo
son necesarios dos puntos, en el que se busca la derivada y el siguiente, y por lo tanto es posible
hallar la derivada en el primer punto.
222
Figura 11.2: Primer caso progresivo
De acuerdo a la tabla anterior, donde cada número representa el coeficiente del término correspondiente,
la expresión de la cuarta derivada será:
El siguiente ejemplo muestra la aplicación de lo anterior en numpy. Los datos iniciales son los
arregloa de los valores de x y de f (x) y el valor 2.36, en el cual que se desea hallar la derivada.
Este punto en el arreglo será x0
def derivadaprog1(datx,daty):
primera = (-daty[0]+daty[1])/(abs(datx[1]-datx[0]))
return primera
x=np.array([2.36,2.37,2.38,2.39])
y= np.array([0.85866,0.86289,0.86710,0.87129])
a = derivadaprog1(x,y)
print ’primera derivada en 2.36 es:’,a
#resultado
#primera derivada en 2.36 es: 0.423
En el ejemplo anterior se usa el valor absoluto de h para evitar cambios de signo por diferencias
negativas. Además se puede ver que a medida que se suma un h ,dentro de la función, se avanza
una posición en el arreglo de x; es decir que si x es x0 , x + 3h será, en notación de arreglo, x[3].
Segundo caso:
Otro método para hallar el valor de f 0 (x) es usando la fórmula de los tres puntos. La deducción
de esta fórmula se hace a partir del polinomio de Taylor escrito en la introducción y despejando
223
f 0 (x0 ):
x0 , x1 = x0 + h y x2 = x0 + 2h
Para hallar la segunda derivada de f (x0 ) se realizan los polinomios de Taylor para x1 y x2
f 00 (x0 )h2
f (x0 + h) = f (x0 ) + f 0 (x0 )h + (11.4)
2
f 00 (x0 )(2h)2
f (x0 + 2h) = f (x0 ) + f 0 (x0 )2h + (11.5)
2
Despejando f 00 (x0 )
Esta ultima ecuación es la ecuación de los tres puntos.En esta ecuación, el error resultante
es de orden h2 y por lo tanto proporciona un valor más exacto.
El procedimiento anterior se puede realizar para la k-ésima derivada. Teniendo en cuenta que
al aumentar el grado de derivada se debe usar un punto más. Es decir que si en el caso de la
primera derivada se necesitan 3 puntos, para hallar la segunda derivada se necesitan 4. Por lo
tanto, las ecuaciones hasta la cuarta derivada están representadas en la siguiente tabla:
224
Figura 11.3: Segundo caso progresivo
Al igual que en la figura 11.2 los números indican los coeficientes de cada término. La diferencia
con el caso anterior es que se necesitan los dos siguientes valores al valor en el cual se desea
hallar la derivada.
El caso anterior, para la primera y segunda derivada en el punto 2,36, en Numpy es:
def derivadafinita2(datx,daty):
primera = ( -3*daty[0]+4*daty[1]-daty[2])/(2*abs(datx[1]-datx[0]))
segunda = ( 2*daty[0]-5*daty[1]+4*daty[2]-daty[3])/(abs(datx[1]-datx[0])**2)
return primera,segunda
x=np.array([2.36,2.37,2.38,2.39])
y= np.array([0.85866,0.86289,0.86710,0.87129])
a , b= derivadafinita2(x,y)
#resultado
#primera derivada en 2.36 es: 0.424 | segunda derivada en 2.36 es: -0.2
De nuevo, se toma el valor absoluto de h para no modificar el valor resultante en el caso de una
diferencia negativa.
Las diferencias finitas progresivas son fáciles de computar y al usar la fórmula de los tres
puntos se halla un valor preciso de la derivada. Sin embargo, con este método, al usar el primer
caso no se puede hallar la derivada en el último punto, ya que al ser este punto xi no existe el
valor xi+1 con el cual se pueda computar la derivada. Lo mismo sucede en el segundo caso, con
la diferencia de que, al necesitar dos puntos siguientes a xi , no se puede computar la derivada
desde el penúltimo punto; pues xi+2 no existe.
225
Debido a lo anterior, se debe establecer otro método para hallar las derivadas de los últimos
puntos y para esto se usa el método de diferencias finitas regresivas.
f (x) − f (x − h)
f 0 (x) = (11.9)
h
Este tipo de diferencias se solucionan del mismo modo que las diferencias progresivas pero
realizando los procedimientos según la ecuación 11.9 y usando los puntos x1 = x0 − h y
x2 = x0 − 2h. Por lo tanto la ecuación,del segundo caso, es decir de los tres puntos para
diferencias finitas regresivas será:
226
La tabla, hasta la cuarta derivada, en el primer caso será:
En la tabla anterior los números son lo coeficientes de cada término y h es la distancia entre
los puntos xi y xi−1 .En este caso para hallar la primera derivada en el punto xi es necesario el
punto anterior xi−1 .
En la anterior ecuación se necesitan tres puntos anteriores a xi para hallar la cuarta derivada.
Por lo tanto a aumentarle un grado a la derivada aumenta en 1 los puntos anteriores requeridos.
En el siguiente ejemplo, se hallará la primera derivada en el punto 2.39, con el primer caso de
diferencias finitas regresivas (el punto 2.39 se encuentra en la última posición del arreglo):
def regresivo1(datx,daty):
n=len(datx)
primera = ( daty[i]-daty[i-1])/(abs(datx[i]-datx[i-1]))
return primera
x=np.array([2.36,2.37,2.38,2.39])
y= np.array([0.85866,0.86289,0.86710,0.87129])
a=regresivo1(x,y)
print ’primera derivada en 2.39 es:’,a
227
#Resultado:
#primera derivada en 2.39 es: 0.419
El segundo caso, en el que se usa la ecuación de los tres puntos , es más exacto debido a que
el orden de error es de h2 . En este caso si se desea hallar la primera derivada en el punto xi se
requieren los puntos hasta xi−2 . A medida que se aumenta en 1 el grado de la derivada se le
suma -1 a i.
La siguiente tabla muestra hasta la cuarta derivada con el segundo caso de diferencias finitas
regresivas:
def regresivo2(datx,daty):
n=len(datx)
i=n-1
segunda=( 2*daty[i]-5*daty[i-1]+4*daty[i-2]-daty[i-3])/(abs(datx[i]-datx[i-1])**2)
return segunda
x=np.array([2.36,2.37,2.38,2.39])
y= np.array([0.85866,0.86289,0.86710,0.87129])
228
a=regresivo2(x,y)
print ’segunda derivada en 2.39 es:’,a
#Resultado
#segunda derivada en 2.39 es: -0.199999999992
Este tipo de diferencias se da al sumar la expresión del primer caso de las diferencias finitas
progresivas y regresivas:
f (x + h) − f (x − h)
f 0 (x) = + Oh2 (11.11)
2h
Esta ecuación al tener un error de orden h2 es más precisa que las dos ecuaciones que la
componen. El orden del error también causa una rápida convergencia a cero.
Para usar esta ecuación se necesitan tres puntos: x, x + h y x − h; los cuales escritos de modo
iterativo son xi , xi+1 y xi−1 . Por lo tanto para hallar la primera derivada de un punto, se debe
conocer el punto siguiente y el anterior a este.
229
Para hallar la ecuación de la segunda derivada es necesario utilizar la expansión de Taylor en
los puntos adyacentes a xi . En el punto x + h esta expansión se da en la ecuacion 11.4 y en el
punto x − h sería:
f 00 (x)h2
f (x − h) = f (x) − f 0 (x)h + (11.12)
2
Despejando f 00 (x) :
f (x + h) − 2f (x) + f (x − h)
f 00 (x) = + Oh2 (11.14)
h2
Para hallar la tercera y cuarta derivada es necesario agregar más puntos y por esta razón se usa
x + 2h y x − 2h.
Para hallar la tercera derivada se multiplica por dos la resta entre 11.4 y 11.12 y a esto se le
resta la ecuación 11.17. De lo anterior se despeja f 000 (x) y da como resultado:
f (x + 2h) − 2f (x + h) + 2f (x − h) − f (x − 2h)
f 000 (x) = + Oh2 (11.19)
2h3
Para hallar la cuarta derivada se multiplica 11.13 por dos y se resta 11.18; dando como resultado:
230
Figura 11.8: Derivadas finitas centradas
#Función que recibe como parámetro una función, el punto en el que se quiere
#derivar y h
def derivada2centrada(f,x,h):
segunda= ((f(x-h)-2*f(x)+f(x+h))/(h**2))
return segunda
import math
#Función a derivar
def f(x):
f= (math.e)**(-x)
return f
a = derivada2centrada(f,1,0.64)
print ’la segunda derivada de e^(-x) con x=1 es:’,a
#Resultado
#la segunda derivada de e^(-x) con x=1 es: 0.380609096726
Al usar estos métodos hay que tener en cuenta que al realizar la expansión de Taylor se descartan
algunos términos, los cuales afectan la magnitud del error en el procedimiento (en este caso,
los términos omitidos fueron denominados como "O").El valor del error depende del valor de
h; si este valor tiene una magnitud muy pequeña los valores f (x), f (x + h), f (x + 2h) y así
sucesivamente, serán aproximadamente iguales lo cual causaría perdida de dígitos. Sin embargo,
h no puede ser grande, pues el error de truncamiento sería excesivo. Para mitigar el exceso de
error se calcula un h óptimo. En el caso del ejemplo anterior los valores de h se relacionan con
el resultado de la siguiente manera:
231
Figura 11.9: Resultado de e(−x) según h.
La cual equivale a:
f (x+h)−f (x−h) f 000 (x)h2 f (2n+1) (x)h2n (f (2n+3) (α1 )+f (2n+3) (α2 ))h2n+2
2h = f 0 (x) + 3! + ... + (2n+1)! + 2(2n+3)!
Despejando f 0 (x)
f (x+h)−f (x−h) Pn
f 0 (x) = 2h + i=1 ki h
2i + kn+1 (h)h2n+2
232
para k = 1, 2, 3..
f (x+h)−f (x−h)
En esta expresión el término D1 (h) = c . Ahora se toma el término h como h/2
y se tiene:
c1 h2k c1 h2k+2
f 0 (x) = Dk (h/2) + + (11.22)
4k 42k+2
4k Dk (h/2) − Dk (h)
f 0 (x) = (11.23)
4k − 1
4k Dk (h/2)−Dk (h)
Ahora, denotando Dk+1 (h) = 4k −1
:
" # " #
ki (x − xi+1 )3 ki+1 (x − xi )3
fi,i+1 (x) = − (x − xi+1 )(xi − xi+1 ) − − (x − xi )(xi − xi+1 )
6 xi − xi+1 6 xi − xi+1
yi (x − xi+1 ) − yi+1 (x − xi )
+
xi − xi+1
233
La segunda derivada es:
" # " #
00 (x − xi+1 ) (x − xi )
fi,i+1 (x) = ki − ki+1
xi − xi+1 xi − xi+1
compute f 0 (2) y f 00 (2) usando segmentación cúbica sobre los tres puntos más cercanos.
Solución:
En este ejemplo es necesario aclarar qué significa "los tres puntos más cercanos". Esto, quiere
decir que se toman los tres puntos más cercanos a 2 en la tabla dada. Para realizar la solución
se debe copiar el método de segmentación cúbica agregandole a la función evalSpline las
expresiones para la primera y segunda derivada en y1 y y2.
import numpy as np
def LUdecomp3(c,d,e):
n = len(d)
for k in range(1,n):
lam = c[k-1]/d[k-1]
d[k] = d[k] - lam*e[k-1]
c[k-1] = lam
return c,d,e
def LUsolve3(c,d,e,b):
n = len(d)
for k in range(1,n):
b[k] = b[k] - c[k-1]*b[k-1]
b[n-1] = b[n-1]/d[n-1]
for k in range(n-2,-1,-1):
b[k] = (b[k] - e[k]*b[k+1])/d[k]
return b
# module cubicSpline
’’’ k = curvatures(xData,yData).
Returns the curvatures of cubic spline at its knots.
y = evalSpline(xData,yData,k,x).
234
Evaluates cubic spline at x. The curvatures k can be
computed with the function ’curvatures’.
’’’
def curvatures(xData,yData):
n = len(xData) - 1
c = np.zeros(n)
d = np.ones(n+1)
e = np.zeros(n)
k = np.zeros(n+1)
c[0:n-1] = xData[0:n-1] - xData[1:n]
d[1:n] = 2.0*(xData[0:n-1] - xData[2:n+1])
e[1:n] = xData[1:n] - xData[2:n+1]
k[1:n] =6.0*(yData[0:n-1] - yData[1:n]) \
/(xData[0:n-1] - xData[1:n]) \
-6.0*(yData[1:n] - yData[2:n+1]) \
/(xData[1:n] - xData[2:n+1])
LUdecomp3(c,d,e)
LUsolve3(c,d,e,k)
return k
def evalSpline(xData,yData,k,x):
def findSegment(xData,x):
iLeft = 0
iRight = len(xData)- 1
while 1:
if (iRight-iLeft) <= 1: return iLeft
i =(iLeft + iRight)/2
if x < xData[i]: iRight = i
else: iLeft = i
i = findSegment(xData,x)
h = xData[i] - xData[i+1]
y = ((x - xData[i+1])**3/h - (x - xData[i+1])*h)*k[i]/6.0 \
- ((x - xData[i])**3/h - (x - xData[i])*h)*k[i+1]/6.0 \
+ (yData[i]*(x - xData[i+1]) \
- yData[i+1]*(x - xData[i]))/h
y1=(3*(x - xData[i+1])**2/h - h)*k[i]/6.0 \
- (3*(x - xData[i])**2/h - h)*k[i+1]/6.0 \
+ (yData[i]- yData[i+1])/h
y2=((x - xData[i+1])/h )*k[i] \
- ((x - xData[i])/h )*k[i+1]
return y,y1,y2ç
xData = np.array([1.5,1.9,2.1,2.4,2.6,3.1])
yData = np.array([1.0628,1.3961,1.5432,1.7349,1.8423, 2.0397])
k=curvatures(xData,yData)
235
derivadas=evalSpline(xData,yData,k,2)
print "f(2)",derivadas[0]
print "f’(2)",derivadas[1]
print "f’’()",derivadas[2]
#Resultado
f(2) 1.47165821123
f’(2) 0.735096652412
f’’(2) -0.401642246214
236
12
Métodos de integración
12.1. Introducción
Los métodos de integración numérica permiten resolver integrales definidas mediante el uso
de una tabla o en forma analítica [17]. Por lo tanto, por medio de estos métodos se resuelven
problemas que no pueden ser resueltos de forma analítica y, incluso cuando la integral se puede
resolver de forma analítica, la integración numérica puede ser más eficiente, ahorrando tiempo
y esfuerzo para conocer el valor numérico de la integral [17].
Por lo tanto, en este capítulo se busca solucionar el problema general dado por
Z b
f (x)dx
a
donde f (x) es una función real, que puede estar dada en forma analítica o mediante una tabla
[17].
Para algunas funciones simples f (x), la integral indefinida es
Z
f (x)dx = F (X)
F (x) = f (x)
La integral definida entre los límites a y b se puede hallar mediante la resta entre la función
F (x) evaluada en el límite b y la función F (x) evaluada en el límite a
Z b
f (x)dx = F (b) − F (a)
a
Sin embargo, los métodos que se describen en este capítulo encuentran la solución a las integrales
definidas usando aproximaciones mediante sumas finitas correspondientes a algunas particiones
del intervalo de integración [a, b] [22].
Los métodos de integración numérica se pueden dividir en dos grupos: las fórmulas de Newton-Cotes
y Cuadratura de Gauss. Fórmulas de Newton-Cotes se caracterizan por abscisas igualmente
espaciados e incluyen métodos bien conocidos, tales como la regla del trapecio y la regla de
237
Simpson. Son más útiles si f (x) ya se ha computado a intervalos iguales o puede ser calculada
a bajo costo. Debido a que las fórmulas de Newton se basan en la interpolación local, sólo
requieren un ajuste por tramos a un polinomio. En la Cuadratura de Gauss las ubicaciones de
las abscisas se eligen para producir la mejor precisión posible. Debido a que la cuadratura de
Gauss requiere un menor número de evaluaciones del integrando para un nivel dado de precisión,
es muy útil en los casos en que f (x) es costosa de evaluar. Otra ventaja de cuadratura de Gauss
es su capacidad para manejar singularidades integrables, que permite evaluar expresiones tales
como
Z1
g(x)
√ dx
1 − x2
0
Mediante la regla del trapecio, se busca encontrar el valor de una integral definida en un intervalo
[a, b] de una función f (x) por medio del área que existe debajo de la curva formada por esta
función. El área de un rectángulo es el producto de la longitud de la base por la altura. El área
de un triángulo es la mitad de la longitud de su base por la altura. El área de un polígono se
encuentra dividiéndolo en triángulos y sumando las áreas encerradas por cada uno de ellos [? ].
Sin embargo, el área de una región curvada no es fácil de encontrar.
Conocido el problema, el método de integración de la regla del trapecio busca obtener una
aproximación al valor del área que existe bajo la curva de la función f (x). Para ello, se elige
como alternativa el área de un trapecio, que es igual a la suma de las bases por la altura, dividido
por dos. Por ejemplo, si se tiene el trapecio de la figura 12.1, el área bajo la curva (equivalente
a la integral) de la función y = f (x) en el intervalo [a, b] está dada por
Z b
b−a
I= f (x)dx = [f (a) + f (b)]
a 2
238
Figura 12.1: Regla del trapecio [17]
h
Ii = [f (xi ) + f (xi+1 )] (12.1)
2
Para obtener el área que existe entre la función f (x) y el eje x en la figura 12.2, se suma el área
que encierra cada trapecio:
h h h
I = [f (x0 ) + f (x1 )] + [f (x1 ) + f (x2 )] + · · · + [f (xn−1 ) + f (xn )]
2 2 2
n−1
X h
I= Ii = [f (x0 ) + 2f (x1 ) + 2f (x2 ) + · · · + f (xn )]
i=0
2
Sea Ik la integral evaluada con la regla del trapecio usando 2k−1 paneles. Tenga en cuenta que
si k se incrementa en uno, se duplica el número de paneles. Usando la notación
H =b−a
donde H es el tamaño del intervalo en el cual se desea integrar la función f (x). Si se divide el
intervalo [a, b] en un solo trapecio, como en la figura 12.1, entonces se cumple:
k=1
H
I1 = f (a) + f (b)
2
239
Figura 12.2: División de una función f (x) en trapecios
Esta expresión es equivalente a la ecuación 12.1 para un trapecio. Para obtener una mejor
aproximación del área bajo la curva de una función es útil dividir el área en numerosos trapecios.
Si el área se divide en dos trapecios, entonces se cumple
k=2
h iH 1 H
I2 = f (a) + 2f (a + H/2) + f (b) = I1 + f (a + H/2)
4 2 2
En la expresión anterior, el intervalo H se divide sobre cuatro debido a que primero se divide
este intervalo entre dos para obtener el área de los trapecios y se divide entre dos nuevamente
porque el área bajo la curva se está aproximando por dos trapecios. Por lo tanto, si el área se
quiere aproximar por tres trapecios, H se divide entre 23 = 8:
k=3
h iH 1 h iH
I3 = f (a)+2f (a+H/4)+2f (a+H/2)+2f (a+3H/4)+f (b) = I2 + f (a+H/2)+f (a+3H/4)
8 2 4
Con base en lo anterior, la suma del área de los trapecios es igual a la integral de la función en
el intervalo dado y la fórmula se puede generalizar para k particiones al dividir el intervalo:
H
2k−1
tamaño del intervalo
Con este factor, la integral es igual a:
k−2
1 H 2X h (2i − 1)H i
Ik = Ik−1 + k−1 f a+
2 2 i=1
2k−1
Para implementar la regla del trapecio en IPython es necesario implementar el siguiente código:
#Función que encuentra la integral de una función por medio de la regla del
#trapecio.
240
#Iold integral en k-1 iteraciones, (integral anterior) realizada con 2^(k-1)
#paneles
def trapezoid(f,a,b,Iold,k):
else:
n = 2**(k -2 ) # Numero de nuevos puntos
h = (b - a)/n # Nuevo intervalo
x = a + h/2.0 # localización del nuevo trapecio
sum = 0.0 #Se guarda la suma de áreas
y si se desea obtener una exactitud de seis decimales, junto con el numero de paneles necesarios
para obtener el resultado se puede implementar en el cuaderno de IPython el siguiente código:
241
Inew = trapezoid(f,0.0,math.pi,Iold,k) #Se llama la función, k empieza en 1
if (k > 1) and (abs(Inew - Iold)) < 1.0e-6: #Al menos tiene que existir una
#iteración.
#Si la diferencia entre la integral vieja y la nueva es menor a 1x10^-6
Iold = Inew
El resultado del código anterior es el valor de la integral y el número de paneles necesarios para
aplicar la regla del trapecio:
Integral= -0.894832
nPanels = 32768
En la figura 12.1 se observa que entre el trapecio y la función f (x) existe un área que no está
sombreada. Esta área permite cuantificar el error que se obtiene al aplicar la regla del trapecio,
que se define como [17]
Z b
b−a
E= f (x)dx − [f (a) + f (b)] (12.2)
a 2
El primer término de la ecuación anterior es la integral exacta y el segundo término es la integral
que se obtiene con la regla del trapecio. Para desarrollar esta ecuación se supone que la función
f es analítica en a ≤ x ≤ b. Una función analítica en un punto x0 es aquella que es infinitamente
derivable en un entorno de dicho punto y la serie de Taylor en este punto converge
∞
X
n=0
n! (x-x0 )n
Una función infinitamente derivable es aquella que cumple que todas sus derivadas son continuas.
Expuesto esto, se escribe el desarrollo de Taylor para f (x) en torno a x̄ = (a + b)/2 como [17]
z 2 00
f (x) = f (x̄) + zf 0 (x̄) + f (x̄) + · · ·
2
con z = x − x̄.
242
El primer término de la ecuación 12.2 se puede escribir como [17]
Z h/2 " #
z2
Z b
f (x)dx = 0
f (x̄) + zf (x̄) + f 00 (x̄) + · · · dz
a −h/2 2
en “donde z = −h/2 para x = a y z = h/2 para x = b” [17]. Al resolver la integral anterior, los
términos con potencias impares de z se anulan y el resultado es
Z b
1 3 00
f (x)dx = hf (x̄) + h f + ··· (12.3)
a 24
Ahora se aplica el desarrollo de Taylor para el segundo término de la ecuación 12.2, con lo que
se obtiene
" 2 2 #
b−a h h 1 h 00 h 1 h 00
[f (a)+f (b)] = f (x̄) − f (x̄) + f (x̄) − · · · + f (x̄) + f (x̄) + f (x̄) − · · ·
2 2 2 2 2 2 2 2
1
= hf (x̄) + h3 f 00 (x̄) + · · · (12.4)
8
Al sustituir las ecuaciones 12.3 y 12.4 en la ecuación 12.2 se obtiene
Z b
b−a
E= f (x)dx − [f (a) + f (b)]
a 2
1 3 00
E'− h f (x̄) (12.5)
12
La ecuación 12.5 se cumple cuando f (x) es analítica en el intervalo [a, b]. “El error de la regla
extendida del trapecio es la suma de los errores en todos los intervalos. Supongamos que se
aplica la regla del trapecio a un intervalo [a, b], el cual se divide en N intervalos mediante los
N + 1 puntos x0 , x1 , x2 , · · · , xN , donde x0 = a y xN = b” [17]. El error para cada intervalo es
el que se presenta en la ecuación 12.2. Por lo tanto, para todos los trapecios el error está dado
por [17]
N
1 (b − a)3 X
E'− f 00 (x̄i ) (12.6)
12 N 3 i=1
N
f¯00 = f 00 (x̄i )/N
X
i=1
se puede obtener
N
f¯00 N = f 00 (x̄i )
X
i=1
243
1
E'− (b − a)h2 f¯00 (12.7)
12
La ecuación 12.7 muestra que el error generado al implementar la regla del trapecio en integrales
es proporcional a h2 para el intervalor [a, b] [17].
Ri,1 = Ii
Como se expuso anteriormente en este capítulo, el error en estas integrales es del orden del
intervalo al cuadrado h2 . Por lo tanto, el error va aumentando y los errores con los intervalos h
y 2h se pueden escribir como [17]
E = c1 h2 + c2 h4 + · · ·
donde
b−a
h=
2i−1
La integración Romberg empieza con R1,1 = I1 y R2,1 = I2 que son igual a las calculadas con la
regla del trapecio. El valor de estas integrales se corrigen al usar la extrapolación Richardson.
Usando p = 2 se tiene
22 R2,1 − R1,1 4 1
R2,2 = = R2,1 − R1,1 (12.8)
22 − 1 3 3
Los elementos 1,1 y 2,1 de la matriz R son las áreas de los trapecios. Al operar estos valores
de acuerdo a la fórmula anterior, se obtiene el elemento 2,2 de la matriz R. Al implementar la
corrección según la extrapolación Richardson se aumenta la precisión cuadráticamente.
La matriz en la que se van almacenando los resultados es
" #
R1,1
R2,1 R2,2
Para encontrar el elemento R3,1 = I3 , se aplica nuevamente la ecuación 12.8, pero los valores
de R2,1 y de R1,1 se reemplazan por los valores de R3,1 y R2,1 , respectivamente:
244
4 1
R3,2 = R3,1 − R2,1
3 3
Los elementos de la 2da columna tiene un error de E = c2 h4 que puede ser eliminada por
extrapolación Richardson
24 R3,2 − R2,2 16 1
R3,3 = 4
= R3,2 − R2,2
2 −1 15 15
Este valor tiene un error del orden O(h6 ). La matriz resultante se visualiza a continuación:
R1,1
R2,1 R2,2
R3,1 R3,2 R3,3
La primera columna de la matriz R, es decir, los elementos Ri,1 tienen un error de h2 . Los
elementos de la segunda columna tienen un error de h4 y los elementos de la tercera de h6 .
El último término de la diagonal de la matriz R es la medida más aproximada de la integral.
Este proceso se realiza hasta que la diferencia de dos diagonales es lo suficientemente pequeña.
Por lo tanto, si se encuentran los valores de los elementos en la tercera columna de la matriz,
es decir, los elementos Ri,3 , se reemplazan los valores en la matriz así:
R10 = R3,3
0
R2 = R3,2
R30 = R3,1
import numpy as np
245
#Se retorna la integral y el numero de paneles usados
def romberg(f,a,b,tol=1.0e-6):
def richardson(r,k):
for j in range(k-1,0,-1):
const = 4.0**(k-j)
r[j] = (const*r[j+1] - r[j])/(const - 1.0)
r = np.zeros(21)
r[1] = trapezoid(f,a,b,0.0,1) #Se calculan las integrales por
#regla del trapecio
r_old = r[1]
for k in range(2,21):
r[k] = trapezoid(f,a,b,r[k-1],k)
r = richardson(r,k) #Nuevo valor es la corrección de los
#dos elementos en la matriz
r_old = r[1]
Z √π
2x2 cos(x2 )
0
Para ello, se crea la función que se desea integrar en IPython y se llama la función romberg()
con los límites de integración correspondientes:
246
#Se importa el módulo para utilizar funciones trigonométricas y el numero
#pi
import math
def f(x):
return 2.0*(x**2)*math.cos(x**2)
I,n = romberg(f,0,math.sqrt(math.pi))
print("Integral =",I)
print("numEvals =",n)
n
X
I= wi f (xi )
i=0
247
estan espaciados uniformemente en (a, b). En la integración gaussiana los nodos y los pesos se
eligen de modo que se obtiene la integral exacta.
Zb Zb n
X
f (x)dx = w(x)Pm (x)dx = Wi Pm (xi ) m ≤ 2n + 1
a a i=0
Z 1 X
f (x)dx = nωi f (xi )
−1 i=0
Los pesos wi y abscisas nodales xi están dados por la tabla presentada en la figura 12.4.
248
Figura 12.4: Cuadraturas de Gauss-Legendre
Zb Z1
b−a b−a a + b
f (x)dx = f x+
2 2 2
a −1
donde se realizó un cambio de variable para obtener los límites de integración correspondientes.
Si n = 2, según la figura 12.4 la solución a la integral se obtiene multiplicando los pesos por la
función evaluada
√ en los valores
√ de las abscisas nodales. Así, el resultado se obtiene al evaluar la
función en 3/3 y en − 3/3 (los pesos ωi son iguales a 1):
Zb " √ ! √ !#
b−a b−a 3 a+b b−a 3 a+b
f (x)dx = f + +f − +
2 2 3 2 2 3 2
a
Como se afirmó anteriormente, existen otras cuadraturas análogas a las cuadraturas de Gauss-Legendre.
Por ejemplo, las cuadraturas de Gauss-Hermite son adecuadas para
Z ∞
2
e−x f (x)dx
−∞
Z ∞ n
2
e−x f (x)dx ≈
X
Ai f (xi ) (12.10)
−∞ i=0
249
En la ecuación 12.10 los Ai son los pesos y las raíces del polinomio de Hermite de orden n
son las raíces xi . La figura 12.5 presenta algunos valores de los pesos y de las raíces según las
cuadraturas de Gauss-Hermite.
Por otro lado, las cuadraturas de Gauss-Laguerre son adecuadas para [17]
Z ∞
e−x f (x)dx
0
Z ∞ n
e−x f (x)dx ≈
X
Ai f (xi ) (12.11)
0 i=0
En la ecuación 12.11 los Ai son los pesos y las raíces del polinomio de Laguerre de orden n
son las raíces xi . La figura 12.6 presenta algunos valores de los pesos y de las raíces según las
cuadraturas de Gauss-Laguerre.
Por último, las cuadraturas de Gauss con singularidad logarítmica son adecuadas para
Z 1
f (x)ln(x)dx
0
Z 1 n
X
f (x)ln(x)dx ≈ − Ai f (xi ) (12.12)
0 i=0
En la ecuación 12.12 los Ai son los pesos y las raíces del polinomio de orden n son las raíces
xi . La figura 12.7 presenta algunos valores de los pesos y de las raíces según las cuadraturas de
Gauss con singularidad logarítmica.
La figura 12.8 resume los polinomios que se pueden implementar de acuerdo al intervalo de
integración para aplicar el método de integración por cuadraturas de Gauss. Adicionalmente,
se presentan los pesos que se utilizan en cada cuadratura.
250
Figura 12.6: Cuadraturas de Gauss-Laguerre
251
Figura 12.8: Polinomios ortogonales aplicados de acuerdo a los pesos ω y a los intervalos
12.4. Ejercicios
12.4.1. Análisis geométrico
Este ejercicio consiste en un análisis geométrico de la unión que se muestra en la figura 15.25.
252
Para solucionar el ejercicio es necesario aplicar la regla de la cadena, ya que no se conoce el
valor de dβ
dt :
dβ dβ dθ
=
dt dθ dt
dβ
Ya se conoce el valor de dθ
dt , por lo que solo falta calcular el valor de dθ . Para ello, se implementa
el método de derivación por interpolación segmentaria cúbica expuesto en el capítulo 11.
El código en IPython que permite solucionar este problema es:
def LUdecomp3(c,d,e):
n = len(d) #Longitud de d
for k in range(1,n): #Para k entre 1 y n, excluyendo a n
lam = c[k-1]/d[k-1] #Se halla lambda
d[k] = d[k] - lam*e[k-1] #Se resta para hallar d
c[k-1] = lam #El valor de c es igual a lambda
return c,d,e #Se retornan las diagonales
def LUsolve3(c,d,e,b):
n = len(d) #Longitud de d
for k in range(1,n): #Para k entre 1 y n, excluyendo a n
b[k] = b[k] - c[k-1]*b[k-1] #Se operan los valores de b
b[n-1] = b[n-1]/d[n-1]
for k in range(n-2,-1,-1): #Para k entre 0 y n-2
b[k] = (b[k] - e[k]*b[k+1])/d[k] #Se hallan las soluciones
return b #Se retornan las soluciones
def curvatures(xData,yData):
253
c[0:n-1] = xData[0:n-1] - xData[1:n] #Se modifica el arreglo c
d[1:n] = 2.0*(xData[0:n-1] - xData[2:n+1]) #Se modifica el arreglo d
e[1:n] = xData[1:n] - xData[2:n+1] #Se modifica el arreglo e
k[1:n] =6.0*(yData[0:n-1] - yData[1:n]) \
/(xData[0:n-1] - xData[1:n]) \
-6.0*(yData[1:n] - yData[2:n+1]) \
/(xData[1:n] - xData[2:n+1]) #Se hallan las curvaturas
LUdecomp3(c,d,e) #Se aplica la función para encontrar los valores
#de las diagonales
LUsolve3(c,d,e,k) #Se resuelve el sistema
return k #Se retornan las curvaturas
def evalSpline(xData,yData,k,x):
254
Ahora lo único que resta es imprimir las derivadas:
#Para theta=0
derivadas=evalSpline(theta,beta,k,0)
print ’derivada de beta con respecto al tiempo en theta=0 es:’,
derivadas[1]
#Para theta=30
derivadas=evalSpline(theta,beta,k,30)
print ’derivada de beta con respecto al tiempo en theta=30 es:’,
derivadas[1]
#Para theta=60
derivadas=evalSpline(theta,beta,k,60)
print ’derivada de beta con respecto al tiempo en theta=60 es:’,
derivadas[1]
#Para theta=90
derivadas=evalSpline(theta,beta,k,90)
print ’derivada de beta con respecto al tiempo en theta=90 es:’,
derivadas[1]
#Para theta=120
derivadas=evalSpline(theta,beta,k,120)
print ’derivada de beta con respecto al tiempo en theta=120 es:’,
derivadas[1]
#Para theta=150
derivadas=evalSpline(theta,beta,k,150)
print ’derivada de beta con respecto al tiempo en theta=150 es:’,
derivadas[1]
255
derivada de beta con respecto al tiempo en theta=90 es: -0.722854864434
derivada de beta con respecto al tiempo en theta=120 es: -1.0220414673
derivada de beta con respecto al tiempo en theta=150 es: -1.18997926635
dσ
= a + bσ
d
0 0,5 1,0 1,5 2,0 2,5 3,0 3,5 4,0 4,5 5,0
σ 0 0,252 0,531 0,840 1,184 1,558 1,975 2,444 2,943 3,5 4,115
Lo que se desea encontrar al resolver el ejercicio son los parámetros a y b por medio de una
regresión lineal. Para ello, es necesario encontrar los valores de dσ
d para todos los valores de σ.
Halladas las derivadas, se puede implementar el ajuste de curvas para una ecuación lineal por el
método de mínimos cuadrados expuesto en el capítulo 9. En este caso, la variable independiente
sería el estrés σ y la variable dependiente sería el módulo tangencial dσ
d .
Con base en esto, el código que permite resolver el ejercicio es:
for i in tension:
#Se encuentran las derivadas
derivadas=evalSpline(tension,estres,k,i)
#Aumenta el contador
cont+=1
256
#Regresion lineal por mínimos cuadrados
def mincuadrados(xData,yData):
xprom= np.mean(xData)
yprom= np.mean(yData)
#Constante a
a=(yprom*sum(xData*xData) - xprom*sum(xData*yData))/(sum(xData*xData)
- len(xData)*(xprom*xprom))
#Constante b
b=(sum(xData*yData) - xprom*sum(yData) )/(sum(xData*xData)
- len(xData)*(xprom*xprom))
Con este código se imprimen como resultado las constantes a y b en el cuaderno de IPython
a es 0.490695390223
b es 0.191575142246
def trapezoid(f,a,b,Iold,k):
257
Inew = (f(a) + f(b))*(b - a)/2.0
else:
n = 2**(k -2 ) # Número de nuevos puntos
h = (b - a)/n # intervalo entre puntos
x = a + h/2.0 # coordenada del nuevo primer punto
sum = 0.0
def romberg(f,a,b,tol=1.0e-6):
r = np.zeros(21)
r[1] = trapezoid(f,a,b,0.0,1) #Se calculan las integrales por el
#trapecio
r_old = r[1]
for k in range(2,21):
r[k] = trapezoid(f,a,b,r[k-1],k)
r = richardson(r,k) #Nuevo valor es la corrección de esos dos
#elementos
if abs(r[1]-r_old) < tol*max(abs(r[1]),1.0): #Valor de
#convergencia
#es 1x10^-6
return r[1],2**(k-1)
r_old = r[1]
import math
258
#Función que se desea integrar
def f(x):
Con este código se obtiene el valor de la integral y el número de evaluaciones que se realizó por
medio del método de integración Romberg:
Se desea encontrar el valor de h(15) h(30) y h(45) y comparar estos valores con h(0) = π/2.
Para ello, se escribe el siguiente código en IPython:
def h15(x):
return 1/math.sqrt(1-((math.sin(math.pi/24)**2)*(math.sin(x)**2)))
def h30(x):
return 1/math.sqrt(1-((math.sin(math.pi/12)**2)*(math.sin(x)**2)))
259
#Función h evaluada en theta=45
def h45(x):
return 1/math.sqrt(1-((math.sin(math.pi/8)**2)*(math.sin(x)**2)))
#h evaluada en theta=0
h0=math.pi/2
Para comparar estos valores con h(0), se divide cada uno de los valores obtenidos entre el valor
h(0) = π/2:
print "h(15)/h(0)=",I15/h0
print "h(30)/h(0)=",I30/h0
print "h(45)/h(0)=",I45/h0
h(15)/h(0)= 1.00430057428
h(30)/h(0)= 1.01740879789
h(45)/h(0)= 1.03997334423
260
Con este resultado se concluye que los valores de h(θ0 ) aumentan a medida que θ0 aumenta. El
valor de h(15) es muy similar al valor de h(0). Sin embargo, como h(θ0 ) aumenta a medida que
aumenta θ0 , la diferencia entre estas funciones y el valor de h(0) también aumenta. Por lo tanto,
el periodo de un péndulo simple se ve afectado y para ángulos grandes no se puede despreciar
el factor h(θ0 )
261
262
13
13.1. Introducción
La forma general para una ecuación diferencial de primer orden es:
y 0 = f (x, y)
dy
Donde y 0 = dx y f (x, y) es una función dada. La solución de esta ecuación puede tener una
constante arbitraria. Para hallar esta constante se necesita saber un punto en la solución de
la curva, lo que implica que y debera se especificado como un valor de x. De este modo la
constante; conocida a su vez como constante de integración se define como:
y(a) = α
Siempre podrá replantearse in una en una ecuación ordinaria de orden n. Usando la notación:
y0 = y
y1 = y 0
y2 = y 00
..
.
y( n − 1) = y ( n − 1)
263
y00 = y1
y10 = y2
y20 = y3
..
.
y0 (a) = α0
y1 (a) = α1
..
.
y( n − 1)(a) = α( n − 1)
Ejemplo
y 00 = −y
y(0) = 1
y 0 (0) = 0
Las ecuaciones previas corresponde a un problema de valor inicial pues ambas ecuaciones de
integración o auxiliares se imponen como x = 0.
Corresponde a un problema de contorno pues las dos condiciones tienen un x diferente para un
mismo orden de derivación.
264
Ecuaciones diferenciales ordinarias. Definiciones y Terminología
Una ecuación diferencial es una ecuación cuya incógnita es una función y en la que aparecen
algunas derivadas de esa función. Si la función que interviene tiene sólo una variable independiente,
la ecuación se llama ecuación diferencial ordinaria (E.D.O.). Si la función tiene varias variables
independientes, se dice que es una ecuación diferencial en derivadas parciales (E.D.P.).En este
tema restringimos nuestra atención a las ecuaciones diferenciales ordinarias.[13]
Además del tipo (ordinaria o parcial), las ecuaciones diferenciales se clasifican según su orden.
El orden de una ecuación diferencial viene determinado por la derivada de orden más alto que
aparece en dicha ecuación. En su forma más general una ecuación diferencial de orden n se
puede escribir como: [13]
F (x, y, y 0 ...y n )
Una ecuación diferencial con condiciones previas de la función a encontrar y sus derivadas; dadas
para el mismo valor de la variable independiente, corresponde a un problema de valor inicial.
De este modo una ecuación diferencial de orden n se expresa de manera general de manera:
F (x, y, y 0 ...y n ) = 0
Para un problema de valor inicial se debe considerar junto con la ecuación a solucionar, n
condiciones de tipo:
y(x0 ) = y0
y 0 (x0 ) = y1
y ( n − 1)(x0 ) = y( n − 1)
265
Para una ecuación xy 0 = y1 cuya condición inicial es y(0) = 1, tiene como soluciones
y = 1 + cx
A continuación se expondra el Teorema de Picard el cual permite que las ecuaciones diferenciales
ordinarias de primer orden muestra condiciones suficientes, pero no necesarias, para que el
problema de valor inicial dado por:
y 0 = f (x, y)
y(x0 ) = y0
De esta manera habra una única solución defnida al menos en un intervalo que contiene al
punto x0 .
Teorema de Picard[13]
∂f
Si f (x, y) y ∂y (x, y) son funciones continuas en un rectangulo R.Donde R equivale a:
R = (x, y) : a ≤ x ≤ b, c ≤ yd
Entonces para cada punto (x0 , y0 ) interior de R existe una única solución del problema de
valor inicial.
y 0 = f (x, y)
y(x0 ) = y0
Existen ecuaciones cuyas soluciones vienen expresadas en términos tan complicados que, con
frecuencia, es preferible obtener una tabla de valores aproximados de la solución en los puntos
de un determinado intervalo.
De este modo si se supone la existencia de una solución de una ecuación diferencial dada,
esta es representada en un lugar geométrico (curva) en el plano. Esta sección buscara mostrar
procedimientos numéricos que utilizan la ecuación diferencial para obtener una sucesión de
puntos cuyas coordenadas aproximan las coordenadas de los puntos de la curva solución
y 0 = f (x, y)
y(x0 ) = y0
266
se busca obtener valores proximales de la solución, si existen, en un conjunto para un intervalo
[a,b] fijado, entre los cuales ha de estar el punto x = x0 .Para ello, se fija un h > 0 y se obtiene
un conjunto de puntos x0 , x)1, ..., xn ⊂ [a, b], expresado de la siguiente manera:
x1 = x0 + h
x2 = x0 + h
x3 = x0 + h
xn = x0 + nh
Para desarrollar el cálculo de los valores proximales de la solución para cada paso se usa
el metodo de Polinomios de Taylor.
h2 hk
y(x + y) ≈ y(x) + hy 0 (x) + y”(x) + . . . + y k (x)
2! k!
Enfatizando en h, en donde si el valor de h es pequeño, las potencias más altas h2 , h3 etc..., son
muy pequeñas.
267
Donde la primera derivada muestra la posición xi + 1 en un instante ti + 1, a partir de la
posición xi en un instante ti según muestra la siguiente expresión:
xi + 1 = xi + f (ti , xi )h
Dicho esto es menester recordar que dicho método arte de la una aproximación en series de
Taylor de la función:
y(x + h) ≈ y(x) + hy0 (x)
‘ Para la resolución del problema se escribe una función denominada euler con las condiciones:[25]
Función f(t,x)
Instante final tf
y00
0
y1
F(x, y) =
y20
..
.
268
A continuación se presentaran dos ejemplos para la resolucón del método de un modo
algebraico y en forma de código para pyhton
Ejemplo 1[25]
dx
= Cos(t)
dt
t=0
x=0
De modo que:
Zt
x−0= Cos(t)dt
0
x = Sin(t)
Ejemplo 2
## module euler
’’’ X,Y = integrate(F,x,y,xStop,h).
269
Euler’s method for solving the
initial value problem {y}’ = {F(x,{y})}, where
{y} = {y[0],y[1],...y[n-1]}.
x,y = initial conditions
xStop = terminal value of x
h = increment of x used in integration
F = user-supplied function that returns the
array F(x,y) = {y’[0],y’[1],...,y’[n-1]}.
’’’
import numpy as np
def integrate(F,x,y,xStop,h):
X = []
Y = []
X.append(x)
Y.append(y)
while x < xStop:
h = min(h,xStop - x)
y = y + h*F(x,y)
x = x + h
X.append(x)
Y.append(y)
return np.array(X),np.array(Y)
## module printSoln
’’’ printSoln(X,Y,freq).
Prints X and Y returned from the differential
equation solvers using printput frequency ’freq’.
freq = n prints every nth step.
freq = 0 prints initial and final values only.
’’’
def printSoln(X,Y,freq):
def printHead(n):
print("\n x ","end= ")
for i in range (n):
print(" y[",i,"] ","end= ")
print()
def printLine(x,y,n):
print("{:13.4e}".format(x),"end= ")
for i in range (n):
print("{:13.4e}".format(y[i]),"end= ")
print()
270
m = len(Y)
try: n = len(Y[0])
except TypeError: n = 1
if freq == 0: freq = m
printHead(n)
for i in range(0,m,freq):
printLine(X[i],Y[i],n)
if i != m - 1: printLine(X[m - 1],Y[m - 1],n)
Enunciado
Integre el valor inicial de:
y 00 = −0,1y 0 − x
y(0) = 0
y 0 (0) = 1
%pylab inline
import numpy as np
import matplotlib.pyplot as plt
def F(x,y):
F = np.zeros(2)
F[0] = y[1]
F[1] = -0.1*y[1] - x
return F
271
Imprime
y; = f (x, y)
y(x0 ) = y0
Entonces
Zx
y = y0 + f (x, y(x)) dx
x0
Para proceder a aproximar esta última integral mediante un método numérico adecuado: para
un y(x) desconocido, se planteara un problema que resultara ser mediante la resolción de la
ecuación previa sera:
xZn +1
yn + 1 = yn + f (x, y(x)) dx
xn
272
Ahora intentamos encontrar c0 , c1 , p, y q. Al hacer coincidir la ecuación anterior con la expansión
de Taylor
1
y(x + h) = y(x) + y0 (x)h + y00 (x)h2
2
1
y(x + h) = y(x) + F(x, y)h + F0 (x, y)h2
2
Note que
∂F n−1
X ∂F ∂F n−1
X ∂F
F0 (x, y) = + yi0 = + Fi (x, y)
∂x i=0
∂yi ∂x i=0
∂yi
Reemplazando
1 ∂F n−1
X ∂F
y(x + h) = y(x) + F(x, y)h + + Fi (x, y) h2
2 ∂x i=0
∂yi
n−1
∂F X ∂F
F[x + ph, y + qhF(x, y) = F(x, y) + ph + qh Fi (x, y)
∂x i=0
∂yi
Así que
h ∂F n−1
X ∂F i
y(x + h) = y(x) + (c0 + c1 )F(x, y)h + c1 p+q Fi (x, y) h2
∂x i=0
∂yi
Comparando
c0 + c1 = 1 c1 p = 1/2 c1 q = 1/2
y(x + h) = y(x) + F x + h/2, y + h/2F(x, y) h
K0 = hF(x, y)
h 1
K1 = hF x + , y + K0
2 2
y(x + h) = y(x) + K1
273
Método Runge Kutta de Cuarto orden
K0 = hF(x, y)
h 1
K1 = hF x + , y + K0
2 2
h 1
K2 = hF x + , y + K1
2 2
K3 = hF(x + h, y + K2 )
1
y(x + h) = y(x) + (K0 + 2K1 + 2K2 + K3 )
6
En código para python se escribiria de la siguiente manera :
## module run_kut4
’’’ X,Y = integrate(F,x,y,xStop,h).
4th-order Runge-Kutta method for solving the
initial value problem {y}’ = {F(x,{y})}, where
{y} = {y[0],y[1],...y[n-1]}.
x,y = initial conditions
xStop = terminal value of x
h = increment of x used in integration
F = user-supplied function that returns the
array F(x,y) = {y’[0],y’[1],...,y’[n-1]}.
’’’
import numpy as np
def integrate(F,x,y,xStop,h):
def run_kut4(F,x,y,h):
K0 = h*F(x,y)
K1 = h*F(x + h/2.0, y + K0/2.0)
K2 = h*F(x + h/2.0, y + K1/2.0)
K3 = h*F(x + h, y + K2)
return (K0 + 2.0*K1 + 2.0*K2 + K3)/6.0
X = []
Y = []
X.append(x)
Y.append(y)
274
while x < xStop:
h = min(h,xStop - x)
y = y + run_kut4(F,x,y,h)
x = x + h
X.append(x)
Y.append(y)
return np.array(X),np.array(Y)
275
276
14
En algunos casos para solucionar ecuaciones diferenciales los problemas proveer una o más
condiciones, con un orden diferente al de la ecuación, las cuales permiten comprobar la solución
particular. Si condiciones del problema están dadas en diferente x los cuales se determinan
por el dominio de la función, se denomina un problema de contorno.Estos problemas están
determinados de la siguiente manera:
Existen varias maneras en las que se puede plantear las condiciones de frontera para una ecuación
diferencial. Algunas de estas condiciones son:
Condiciones de Dirichlet
Condiciones de Neumman
En los problemas dados con condiciones de Neumman se dan los valores de la derivada normal
df
dn de f en la frontera. Estas condiciones se plantean de la siguiente manera:
Las condiciones en estos problemas pueden ser también una mezcla de las condiciones de Drichlet
y de Neumman.Es decir de la forma:
277
y 00 = f (x, y, y 0 ), y(a) = α y 0 (b) = β
Este tipo de problemas tiene un nivel de dificultado mayor a los problemas con condiciones
iniciales. Para solucionarlos, se deben hallar los valores faltantes; sin embargo, la solución
encontrada posiblemente no cumplirá la condición de un extremo. Para que se cumpla, es
necesario usar la inspección de discrepancia se puede deducir qué cambios hacer a las condiciones
iniciales antes de integrar de nuevo. El método anterior se denomina método de shooting.
Otro procedimiento que podría realizarse para solucionar problemas de contorno es el método
de diferencias finitas el cual aproxima las ecuaciones diferenciales, mediante diferencias finitas
en puntos de malla espaciados uniformemente, lo cual resulta en un conjunto de ecuaciones
algebraicas.
Los métodos mencionados anteriormente son usados en ecuaciones diferenciales lineales. Para
resolver ecuaciones diferenciales no lineales son necesarios procedimientos iterativos que consumen
gran cantidad de recursos computacionales. Además, se necesitan buenos puntos de partida
para converger y debido a que no hay fórmula para establecer estos valores, un algoritmo para
solucionar ecuaciones diferenciales no lineales requeriría la entrada de conocimiento de causa.
dn y dy d2 y dn−1 y
dxn =f (x, y, dx , dx2 , .... dxn−1 ) con x en [a, b]
dy d2 y dr−1 y
y(a) = α1 , dx (a) = α2 , dx2
(a) = α3 ,.... dxr−1
(a) = αr
dy d2 y dr−1 y
y(b) = β1 , dx (b) = β2 , dx2
(b) = β3 ,.... dxr−1
(b) = βr
Donde r+s=n
Con este método se remplazan las condiciones de contorno de x = b por condiciones iniciales en
x = a. El problema replanteado será:
dn y dy d2 y dn−1 y
dxn =f (x, y, dx , dx2 , .... dxn−1 ) con x en [a, b]
278
dy d2 y dr−1 y
y(a) = α1 , dx (a) = α2 , dx2
(a) = α3 ,.... dxr−1
(a) = αr
En las ecuaciones anteriores, como fue explicado previamente, los parámetros u son términos
que deben ser encontrados. Para estos valores se encontraran soluciones de valores iniciales. La
solución del problema original estará completa si las soluciones obtenidas para u cumplen las s
condiciones.
Se transforma a:
y(b) = θ(u)
r(u) = θ(u) − β = 0
3. Aplicar el método ridder para hallar la raíz u. En cada iteración de este paso se
requiere la evaluación de θ(u) mediante la resolución de la ecuación diferencial como
un problema de condiciones iniciales.
279
4. Después de hallar el valor de u se resuelve ,nuevamente, la ecuación diferencial.
Realizar el primer paso, elegir u1 y u2 se puede hacer de manera aleatoria. Sin embargo, para
reducir la búsqueda se puede analizar la función según las condiciones de frontera. Por ejemplo
en el problema:
Se debe hallar u con el cual y 0 (0) = u. Para esto se crea el intervalo u1 y u2 . Para escoger
estos valores se analizan las condiciones de frontera y se asume que y es suave en el intervalo
0 ≤ x ≤ 2. En este intervalo, de acuerdo a los valores de y(0) y y y(2) se establece que la
función es creciente de 0 a 1; por lo tanto, y 0 en este intervalo debe ser positiva. Como y y y 0
son positivas en [0,2] se establece que y 00 debe ser negativa. La gráfica de lo anterior es:
En esta gráfica se ve que la función debe ser mayor a la pendiente de la recta formada de 0 a 1.
Por lo tanto, al hallar la pendiente de esa recta:
y(2)−y(0)
2−0 = 12 = 0,5 Se establece que f 0 (0) > 0,5, de lo cual se deduce que 1 ≤ f 0 (0) ≤ 2.
Entonces se determina u1 = 1 y u2 = 2.
def err(string):
print(string)
input(’Press return to exit’)
sys.exit(0)
def integrate(F,x,y,xStop,h):
def run_kut4(F,x,y,h):
K0 = h*F(x,y)
280
K1 = h*F(x + h/2.0, y + K0/2.0)
K2 = h*F(x + h/2.0, y + K1/2.0)
K3 = h*F(x + h, y + K2)
return (K0 + 2.0*K1 + 2.0*K2 + K3)/6.0
X = []
Y = []
X.append(x)
Y.append(y)
while x < xStop:
h = min(h,xStop - x)
y = y + run_kut4(F,x,y,h)
x = x + h
X.append(x)
Y.append(y)
return np.array(X),np.array(Y)
def ridder(f,a,b,tol=1.0e-9):
fa = f(a)
if fa == 0.0: return a
fb = f(b)
if fb == 0.0: return b
if sign(fa) == sign(fb):
err(’Root is not bracketed’)
for i in range(30):
# Compute the improved root x from Ridder’s formula
c = 0.5*(a + b); fc = f(c)
s = math.sqrt(fc**2 - fa*fb)
if s == 0.0: return None
dx = (c - a)*fc/s
if (fa - fb) < 0.0: dx = -dx
x = c + dx; fx = f(x)
# Test for convergence
if i > 0:
if abs(x - xOld) < tol*max(abs(x),1.0): return x
xOld = x
# Re-bracket the root as tightly as possible
if sign(fc) == sign(fx):
if sign(fa)!= sign(fx): b = x; fb = fx
else: a = x; fa = fx
else:
a = c; b = x; fa = fc; fb = fx
return None
print(’Too many iterations’)
281
def initCond(u): # Init. values of [y,y’]; use ’u’ if unknown
return np.array([0.0, u])
x y[0] y[1]
0.0 [ 0. 1.51452253]
0.1 [ 0.15031188 1.48063106]
0.2 [ 0.29404453 1.38482498]
0.3 [ 0.42572218 1.24265547]
0.4 [ 0.54170442 1.07434663]
0.5 [ 0.64037616 0.89938933]
0.6 [ 0.72187149 0.73286528]
0.7 [ 0.78754998 0.58416242]
0.8 [ 0.83944329 0.45751925]
0.9 [ 0.87980689 0.35342814]
1.0 [ 0.91082129 0.27012641]
1.1 [ 0.9344291 0.20478372]
1.2 [ 0.95227088 0.15429109]
1.3 [ 0.96568212 0.11570819]
1.4 [ 0.97572206 0.08647101]
1.5 [ 0.98321528 0.064453 ]
1.6 [ 0.98879505 0.047948 ]
282
1.7 [ 0.99294295 0.03561796]
1.8 [ 0.99602253 0.0264302 ]
1.9 [ 0.99830681 0.0195968 ]
2.0 [ 1. 0.01452154]
Resolver
y00 y1
0 0
F(x, y) = y = y1 = y2
y20 2y2 + 6xy0
Falta y2 (5) =?
Solución:
import math
import numpy as np
def integrate(F,x,y,xStop,h,tol=1.0e-6):
b10 = 0.2
b20 = 0.075; b21 = 0.225
b30 = 44.0/45.0; b31 = -56.0/15.0; b32 = 32.0/9.0
b40 = 19372.0/6561.0; b41 = -25360.0/2187.0; b42 = 64448.0/6561.0
283
b43 = -212.0/729.0
b50 = 9017.0/3168.0; b51 =-355.0/33.0; b52 = 46732.0/5247.0
b53 = 49.0/176.0; b54 = -5103.0/18656.0
b60 = 35.0/384.0; b62 = 500.0/1113.0; b63 = 125.0/192.0;
b64 = -2187.0/6784.0; b65 = 11.0/84.0
X = []
Y = []
X.append(x)
Y.append(y)
stopper = 0 # Integration stopper(0 = off, 1 = on)
k0 = h*F(x,y)
for i in range(10000):
k1 = h*F(x + a1*h, y + b10*k0)
k2 = h*F(x + a2*h, y + b20*k0 + b21*k1)
k3 = h*F(x + a3*h, y + b30*k0 + b31*k1 + b32*k2)
k4 = h*F(x + a4*h, y + b40*k0 + b41*k1 + b42*k2 + b43*k3)
k5 = h*F(x + a5*h, y + b50*k0 + b51*k1 + b52*k2 + b53*k3 \
+ b54*k4)
k6 = h*F(x + a6*h, y + b60*k0 + b62*k2 + b63*k3 + b64*k4 \
+ b65*k5)
h = hNext
return np.array(X),np.array(Y)
284
La gráfica que muestra la función y (azul) y su derivada y 0 (verde) es:
Figura 14.2: 1
285
Además de crear la malla, se debe reemplazar la ecuación dada por la fórmula de diferencias
finitas centradas en los puntos donde la soluciones son desconocidas y así crear un sistema
algebraico de ecuaciones. Por último se resuelve el sistemas de ecuaciones formado.
La solución con este método depende del tipo de condiciones dadas en el problema (condiciones
de Dirichlet, Neumman, mixtas).
xi = ih, i = 0, 1, 2, 3, ..., n h = ∆x
Luego se reemplaza el problema con la fórmula de diferencias finitas centradas en cada punto
(xi ):
u(xi −h)−2u(x)+u(xi +h)
u00 (xi ) = h2
Lo cual es igual a:
ui−1 −2ui +ui+1
u00 (xi ) = h2
uα −2u1 +u2
f (x1) = h2
u1 −2u2 +u3
f (x2) = h2
.......
−2 1
f (x1 ) − uhα2
h2 h2
u1
1 −2 1 u2
h2 h2 h2 f (x2 )
1 −2 1 u
h2 h2 h2 3 f (x3 )
= (14.2)
... ... ... ... ...
1 −2 1
un−2 f (xn−2 )
h2 h2 h2
1 −2 u
h2 h2
un−1 f (xn−1 − hβ2 )
286
Estas matriz es definida dentro de la función y sobre esta se aplica alguno de los métodos
conocidos para solucionar sistemas de ecuaciones lineales.
Para solucionar este problemas, se discretizan las condiciones con diferencias centradas, pero al
usarlo en los límites del intervalo se debe debe crear un punto ficticio anterior al límite inferior
del intervalo y un punto ficticio siguiente al límite superior del intervalo; esto se debe a que el
método de diferencias centradas, como fue explicado en un capítulo previo, necesita un punto
anterior y uno siguiente al punto en el cual se realiza la derivada. Por lo tanto las condiciones
se escriben según:
u(xi +h)−u(xi −h)
u0 (x) = 2h
Con lo cual, usando el punto ficticio x−1 con un valor asociado a u−1, la primera condicion
u0 (0) resulta:
u1 −u−1
2h =α
La condición del límite superior del intervalo u0 (1) con el punto ficticio xn+1 con un valor
asociado a un + 1 es:
un+1−un−1
h =β
Despues de establecer las condiciones discretizadas, se reemplaza u00 por su ecuación de diferencias
finitas, la cual es:
u(xi −h)−2u(x)+u(xi +h)
u00 (xi ) = h2
La ecuación anterior se usa desde u1 hasta un−1 y tomando las condiciones de frontera, se
tienen las ecuaciones:
u1 −u−1
2h =α
u0 −2u1 +u2
h2
= f (x1 )
u1 −2u2 +u3
h2
= f (x2 )
......
287
un−2 −2un−1 +un
h2
= f (xn−1 )
un+1−un−1
h =β
α u1 −u
2h
−1
+ βun = λ
Lo cual es igual a:
2β 2hλ
u−1 = u1 + α un − α
o
" #
−1 β u1 f0 λ
h2
+ αh un + h2
= 2 + αh
Se debe agregar un punto final ficticio xn+1 con un valor asociado a un+1 , Con el cual la
condición de Neumman se transforma a :
un+1 −un−1
2h = cte
Usando la expresión de derivadas discretas para u00 (x) , la cual ha sido escrita anteriormente,
288
resultan las siguientes ecuaciones:
uα −2u1 +u2
h2
= f (x1 )
u1 −2u2 +u3
h2
= f (x2 )
............
ui−1 −2ui +ui+1
h2
= f (xi )
.............
un+1 −un−1
2h = cte
Para solucionar este problema en python es necesario utilizar algún método para solucionar
sistemas de ecuaciones. En este caso se usará el método de descomposición LU explicado en un
capítulo previo.
Figura 14.4
289
#Funciones para solucionar sistema de ecuaciones
def LUdecomp3(c,d,e):
n = len(d)
for k in range(1,n):
lam = c[k-1]/d[k-1]
d[k] = d[k] - lam*e[k-1]
c[k-1] = lam
return c,d,e
def LUsolve3(c,d,e,b):
n = len(d)
for k in range(1,n):
b[k] = b[k] - c[k-1]*b[k-1]
b[n-1] = b[n-1]/d[n-1]
for k in range(n-2,-1,-1):
b[k] = (b[k] - e[k]*b[k+1])/d[k]
return b
import numpy as np
import math
290
El resultado del código anterior será:
x y
0.00000e+00 0.00000e+00
1.57080e-01 3.14173e-01
3.14159e-01 6.12841e-01
4.71239e-01 8.82030e-01
6.28319e-01 1.11068e+00
7.85398e-01 1.29172e+00
9.42478e-01 1.42278e+00
1.09956e+00 1.50645e+00
1.25664e+00 1.54995e+00
1.41372e+00 1.56451e+00
1.57080e+00 1.56418e+00
14.3. Ejercicios
1.Resuelva el siguiente problema de frontera mediante el método de Shooting:
Solución:
import numpy as np
import math
from numpy import sign
import sys
def err(string):
print(string)
input(’Press return to exit’)
sys.exit(0)
def integrate(F,x,y,xStop,h):
def run_kut4(F,x,y,h):
K0 = h*F(x,y)
K1 = h*F(x + h/2.0, y + K0/2.0)
K2 = h*F(x + h/2.0, y + K1/2.0)
K3 = h*F(x + h, y + K2)
return (K0 + 2.0*K1 + 2.0*K2 + K3)/6.0
X = []
Y = []
X.append(x)
Y.append(y)
while x < xStop:
h = min(h,xStop - x)
y = y + run_kut4(F,x,y,h)
291
x = x + h
X.append(x)
Y.append(y)
return np.array(X),np.array(Y)
def ridder(f,a,b,tol=1.0e-9):
fa = f(a)
if fa == 0.0: return a
fb = f(b)
if fb == 0.0: return b
if sign(fa) == sign(fb):
err(’Root is not bracketed’)
for i in range(30):
# Compute the improved root x from Ridder’s formula
c = 0.5*(a + b); fc = f(c)
s = math.sqrt(fc**2 - fa*fb)
if s == 0.0: return None
dx = (c - a)*fc/s
if (fa - fb) < 0.0: dx = -dx
x = c + dx; fx = f(x)
# Test for convergence
if i > 0:
if abs(x - xOld) < tol*max(abs(x),1.0): return x
xOld = x
# Re-bracket the root as tightly as possible
if sign(fc) == sign(fx):
if sign(fa)!= sign(fx): b = x; fb = fx
else: a = x; fa = fx
else:
a = c; b = x; fa = fc; fb = fx
return None
print(’Too many iterations’)
292
F[1] = -(1-0.2*x)*(y[0])**2 #y[0]=y
return F
#La pendiente entre los puntos de frontera es 2/pi por lo tanto u>2/pi y u<pi/2
# Se integra desde 0.0 a pi/2 según las condiciones de frontera f(0),f(pi/2)
xStart = 0.0
xStop = math.pi/2
u1 = 0.7 #u>2/pi
u2 = math.pi/2 # 2nd trial value of unknown init. cond.
h = 0.1 # Step size
freq = 2 # Printout frequency
u = ridder(r,u1,u2) # Compute the correct initial condition
X,Y = integrate(F,xStart,initCond(u),xStop,h)
El resultado es:
x y[0] y[1]
0.0 [ 0. 0.77880134]
0.1 [ 0.07787513 0.77860222]
0.2 [ 0.15568134 0.77723327]
0.3 [ 0.23324607 0.77359705]
0.4 [ 0.31029143 0.76668635]
0.5 [ 0.386444 0.75560165]
0.6 [ 0.46124668 0.73957231]
0.7 [ 0.53417269 0.71797913]
0.8 [ 0.60464182 0.69037603]
0.9 [ 0.6720387 0.65650841]
1.0 [ 0.73573261 0.61632598]
1.1 [ 0.79509832 0.56998826]
1.2 [ 0.84953691 0.51786131]
1.3 [ 0.89849593 0.4605051 ]
1.4 [ 0.94148773 0.39865173]
1.5 [ 0.97810527 0.3331753 ]
1.57079632679 [ 1. 0.28516145]
y 00 + 2y 0 + 3y 2 = 0 y(0) = 0 y(2) = −1
Solución en la cual las funciones ridder y integrate ya estan previamente definidas.
293
def initCond(u): # Init. values of [y,y’]; use ’u’ if unknown
return np.array([0.0, u]) #valores iniciales
y 00 + 2y + y = 0 y(0) = 0 y(1) = 1
Primero se debe identidicar qué tipo de condiciones tiene este problema. En este caso son
condiciones de Dirichlet y por lo tanto se usan las ecuaciones de este caso:
(−yi + 1 − yi − 1)
yi−1 − 2yi + yi + 1 − h2 ( − 1) = 0 (2)
h
ym − 1 = 0 (3)
294
La ecuación 2 de simplifica quedando:
b = (0, ......., 1)
Se soluciona la ecuación:
def gaussElimin(a,b):
n = len(b)
# Elimination Phase
for k in range(0,n-1):
for i in range(k+1,n):
if a[i,k] != 0.0:
lam = a [i,k]/a[k,k]
a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n]
b[i] = b[i] - lam*b[k]
# Back substitution
for k in range(n-1,-1,-1):
b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]
return b
def newtonRaphson2(f,x,tol=1.0e-9):
def jacobian(f,x):
h = 1.0e-4
n = len(x)
jac = np.zeros((n,n))
f0 = f(x)
for i in range(n):
temp = x[i]
x[i] = temp + h
f1 = f(x)
x[i] = temp
jac[:,i] = (f1 - f0)/h
return jac,f0
295
for i in range(30):
jac,f0 = jacobian(f,x)
if math.sqrt(np.dot(f0,f0)/len(x)) < tol:
return x
dx = gaussElimin(jac,-f0)
x = x + dx
if math.sqrt(np.dot(dx,dx)) < tol*max(max(abs(x)),1.0): return x
print(’Too many iterations’)
def LUdecomp3(c,d,e):
n = len(d)
for k in range(1,n):
lam = c[k-1]/d[k-1]
d[k] = d[k] - lam*e[k-1]
c[k-1] = lam
return c,d,e
def LUsolve3(c,d,e,b):
n = len(d)
for k in range(1,n):
b[k] = b[k] - c[k-1]*b[k-1]
b[n-1] = b[n-1]/d[n-1]
for k in range(n-2,-1,-1):
b[k] = (b[k] - e[k]*b[k+1])/d[k]
return b
import math
def equations(x,h,m): # Set up finite difference eqs.
h2 = h*h
c = np.ones(m)*(1-h)
d = np.ones(m + 1)*(-2+h2)
e = np.ones(m)*(1+h)
b = np.zeros(m+1)
d[0] = 1.0 # EC 1 y_0=0
e[0] = 0.0
d[m] = 1.0#Ec 2 y_1=1
c[m-1] = 0.0
#vector b
b[0]=0.0
b[m]=1.0
return c,d,e,b
def exacta(x):
return x*math.e**(1-x)
296
h = (xStop - xStart)/m
x = np.arange(xStart,xStop + h,h)
c,d,e,b = equations(x,h,m)
c,d,e = LUdecomp3(c,d,e)
y = LUsolve3(c,d,e,b)
print "\n x y y exacta"
for i in range(m + 1):
print(’{:14.5e} {:14.5e} {:14.5e}’.format(x[i],y[i],exacta(x[i])))
x y y exacta
0.00000e+00 0.00000e+00 0.00000e+00
1.00000e-01 2.46601e-01 2.45960e-01
2.00000e-01 4.46124e-01 4.45108e-01
3.00000e-01 6.05314e-01 6.04126e-01
4.00000e-01 7.30057e-01 7.28848e-01
5.00000e-01 8.25483e-01 8.24361e-01
6.00000e-01 8.96055e-01 8.95095e-01
7.00000e-01 9.45649e-01 9.44901e-01
8.00000e-01 9.77630e-01 9.77122e-01
9.00000e-01 9.94908e-01 9.94654e-01
1.00000e+00 1.00000e+00 1.00000e+00
y0 − 0 = 0 (1)
−xi ( yi−1−yi+1 ) − yi
yi−1 − 2yi + yi + 1 − h2 ( 2h
2 ) (2)
xi
ym − 0,638961 = 0 (3)
297
La ecuación 2 de simplifica quedando:
xi h xi h
(x2i − )yi−1 + (h2 − 2x2i )yi + (x2i + )yi+1 = 0
2 2
Se soluciona la ecuación:
(con los métodos GaussElimin, newtonRaphson2, LUdecomp3 y LUsolve3 ya declarados)
def exacta(x):
return sin(log(x))
#intervalo de [1,2]
xStart = 1.0 # x at left end
xStop = 2.0 # x at right end
m = 10 # Number of mesh spaces
h = (xStop - xStart)/m
x = np.arange(xStart,xStop + h,h)
c,d,e,b = equations(x,h,m)
c,d,e = LUdecomp3(c,d,e)
y = LUsolve3(c,d,e,b)
print "\n x y y exacta"
298
for i in range(m + 1):
print(’{:14.5e} {:14.5e} {:14.5e}’.format(x[i],y[i],exacta(x[i])))
x y y exacta
1.00000e+00 0.00000e+00 0.00000e+00
1.10000e+00 9.51818e-02 9.51659e-02
1.20000e+00 1.81335e-01 1.81313e-01
1.30000e+00 2.59386e-01 2.59365e-01
1.40000e+00 3.30178e-01 3.30159e-01
1.50000e+00 3.94461e-01 3.94446e-01
1.60000e+00 4.52901e-01 4.52890e-01
1.70000e+00 5.06083e-01 5.06075e-01
1.80000e+00 5.54525e-01 5.54521e-01
1.90000e+00 5.98683e-01 5.98681e-01
2.00000e+00 6.38961e-01 6.38961e-01
299
300
15
15.1. Introducción
Existen numerosos fenómenos físicos que pueden ser descritos mediante ecuaciones diferenciales
relacionadas con derivadas parciales. Estas ecuaciones se conocen como ecuaciones diferenciales
parciales.
Entre las ecuaciones diferenciales parciales relacionadas con fenómenos físicos se encuentran:
∂2u ∂2u ∂u
− 2
− 2 + f (x, y)u = i
∂x ∂y ∂t
∂2u ∂u
2
=A
∂x ∂t
donde u podría ser por ejemplo un campo escalar representando calor y A se conoce
como la constante de difusión
301
Las ecuaciones diferenciales parciales de segundo orden presentadas se pueden clasificar en
parabólicas, hiperbólicas y elípticas. La diferencia entre los tres tipos de ecuaciones se basa en
la forma general de una ecuación diferencial parcial de segundo orden en dos variables [17]:
B 2 − 4AC = 0
Las ecuaciones diferenciales parciales parabólicas están relacionadas con ecuaciones que rigen la
difusión de partículas en movimiento o la conducción de calor. Por lo tanto, “ [...] los métodos de
solución numérica de las EDP [ecuaciones diferenciales parciales] parabólicas son importantes
en campos como difusión molecular, la transferencia de calor, el análisis de reactores nucleares y
el flujo de fluidos ” [17]. Las ecuaciones diferenciales parciales parabólicas representan procesos
de difusión que dependen del tiempo y que evolucionan en espacios bidimensionales.
Adicionalmente, las ecuaciones diferenciales parciales elípticas cumplen con
B 2 − 4AC < 0
“Las EDP elípticas aparecen en problemas estacionarios de dos y tres dimensiones. Entre los
problemas elípticos típicos están la conducción del calor en los sólidos, la difusión de partículas
y la vibración de una membrana, entre otros. Estas ecuaciones tienen una relación cercana
con las de tipo parabólico. Por ejemplo, al resolver una EDP parabólica, es frecuente el uso
de los métodos numéricos para una EDP elíptica como parte del esquema de solución. Las
EDP elípticas se pueden considerar como la contraparte de estado estacionario de las EDP
parabólicas. Las ecuaciones de Poisson y Laplace son casos especiales de las EDP elípticas” [17]
Por último, las ecuaciones diferenciales parciales hiperbólicas cumplen con
B 2 − 4AC > 0
“Las ecuaciones que rigen el comportamiento del transporte convectivo de la materia y sus
cantidades físicas así como el de las ondas elásticas, acústicas y electromagnéticas son EDP
hiperbólicas. Las ecuaciones básicas del flujo de fluidos sin viscosidad son EDP hiperbólicas.
Incluso las ecuaciones para los flujos viscosos se pueden analizar como si fueran hiperbólicas si
el efecto de La viscosidad es débil ”[17]. Por lo tanto, se ha presentado un gran desarrollo
de esquemas numéricos para las ecuaciones diferenciales parciales hiperbólicas en la parte
computacional de la dinámica de fluidos [17].
En este capítulo se explica el método de diferencias finitas para resolver ecuaciones diferenciales
parciales implementando IPython. Con este método, se busca generar animaciones de distintas
ecuaciones diferenciales parciales como la ecuación de difusión, la ecuación de onda y la ecuación
de convección. Algunas de estas ecuaciones son ecuaciones diferenciales parciales de orden dos,
como la ecuación de difusión y la ecuación de onda. Además, se generan animaciones de las
302
ecuaciones diferenciales parciales en una y en dos dimensiones. Más adelante, se expone la
ecuación de Burger que cumple un papel importante en la mecánica de fluidos por carecer de
linealidad convectiva, pero presenta numerosas soluciones analíticas. Por último, se presentan
algunos ejercicios relacionados con la solución de la ecuación de difusión de calor utilizando
código en IPython.
∂2u ∂u
2
= (15.2)
∂x ∂t
Las derivadas se pueden aproximar por medio del primer término de la expansión en series de
Taylor. Para la derivada con respecto al tiempo, la posición x permanece constante y se obtiene:
Si ahora se define el espacio en la región 0 < x < L como un espacio dividido en N + 1 puntos
con un espaciamiento de ∆x y el tiempo se discretiza en M puntos espaciados por ∆t para
t > 0, se puede obtener una serie de puntos que cumplen para el tiempo
tj = j∆t j≥0
xi = i∆x 0≤i≤N
Con esta notación, la ecuación 15.3 que presenta la derivada con respecto al tiempo se escribe
como
303
Asimismo, la ecuación 15.4 que presenta la segunda derivada con respecto a la posición se escribe
Para simplificar aún más las expresiones anteriores, se introduce una nueva notación según
la cual el índice superior describe la discretización con respecto al tiempo y el índice inferior
describe la discretización con respecto al espacio, con lo que se obtiene para la derivada temporal
∂u uj+1 − uji
≈ i
∂t ∆t
Para la derivada espacial se obtiene
uj+1
i = αuji+1 + (1 − 2α)uji + αuji−1 (15.5)
donde α = ∆t/(∆x)2
Para resolver la ecuación 15.5 es necesario conocer el valor de la función para t = 0, es decir, el
valor de u0i debe ser conocido. Además, se requieren las condiciones de frontera u(0, t) = a(t) y
u(L, t) = b(t) para x = 0 y x = L, respectivamente.
Adicionalmente, para solucionar la ecuación 15.5 existe como condición para la estabilidad
numérica que α sea menor a 1/2.
Ecuación hiperbólica
La ecuación de onda es
∂2u 2
2∂ u
= c (15.6)
∂x2 ∂t2
Aplicando la expansión en series de Taylor y simplificando la ecuación de onda, se obtiene para
uj+1
i
uj+1
i = 2(1 − r2 )uji − uj−1
i + r2 (uji+1 + uji−1 ) (15.7)
304
Adicionalmente, para solucionar la ecuación 15.7 se requiere una condición inicial para la
derivada temporal dada por
∂u u+1 − u−1
(t = 0) = i i
=0
∂t 2∆t
Despejando de este ecuación el término u−1 i y reemplazándolo en la ecuación 15.7 se obtiene
una condición especial para el primer intervalo de tiempo:
r2 0
u1i = u0i + (u − 2u0i + u0i−1 ) (15.8)
2 i+1
Como con la ecuación de difusión, para resolver la expresión anterior es necesario especificar las
condiciones de frontera uj0 = a y ujN = 0
Para resolver la ecuación de onda en IPython, se debe especificar inicialmente el numero de
puntos en los cuales se divide la región del espacio y se asigna la función para t = 0, como se
presenta en este código:
n_points = 1000
x = linspace(0.0,1.0,n_points) #x entre 0 y 1
u_initial = exp(-((x-0.3)*(x-0.3))/0.01)
plot(x,u_initial)
print r
305
Figura 15.1: Gráfica exponencial obtenida para t = 0
u_initial[0] = 0.0
u_initial[n_points-1] = 0.0
u_future = zeros(n_points)
u_future[0] = 0.0
u_future[n_points-1] = 0.0
#Se crea una variable para guardar el valor de u para j-1, es decir,
#el u pasado
u_past = u_initial.copy()
#Se crea una segunda variable para guardar el valor de u para j, es decir,
#el u presente
u_present = u_future.copy()
Para visualizar el cambio de la función inicial a lo largo del tiempo se crea una animación. Para
ello, se realiza una secuencia de imágenes y se aplica iterativamente la ecuación 15.8:
306
plt.ion()
n_time = 350
fig = plt.gcf()
for j in range(n_time):
for i in range(1,n_points-1):
plt.close()
plt.ioff()
Como se puede observar, la figura 15.2 es similar a la figura 15.1 ya que en este instante de
la animación ha transcurrido poco tiempo. Posteriormente, la onda reduce su amplitud y se
aplana, lo que se observa en las figuras 15.3 y 15.4. Por último, la onda se divide por la mitad
y el resultado final es la figura 15.5
307
Figura 15.3: Evolución de la función en la ecuación de onda
308
Figura 15.4: Evolución de la función en la ecuación de onda
309
15.3. Fundamentos de la dinámica de fluidos computacional
~
∂V
+ (V ~ = − 1 ∇p + ν∇2 V
~ · ∇)V ~
∂t ρ
∂Vx ∂Vx 1 ∂p ∂ 2 Vx
+ Vx =− +ν
∂t ∂x ρ ∂x ∂x2
2
En esta ecuación Vx ∂V ∂ Vx
∂x representa la convección y ν ∂x2 representa la difusión.
x
Convección lineal
Convección no lineal
Difusión
Para estudiar el fenómeno de convección lineal es necesario resolver la siguiente ecuación diferencial
parcial:
∂u ∂u
+c =0
∂t ∂x
Para ello, se discretizan las derivadas por medio de una división espacial y temporal. Con esto,
se genera una expansión en series de Taylor. Para la derivada espacial se obtiene
∂u uji − uji−1
≈
∂x ∆x
Asimismo, para la derivada temporal se obtiene
∂u uj+1
i − uji
≈
∂t ∆t
310
Por lo tanto, la ecuación de convección lineal se puede reescribir al reemplazar las dos expresiones
anteriores:
∆t j
uj+1
i = uji − c (u − uji−1 )
∆x i
Para implementar esta expresión en IPython se aplica el siguiente código:
%pylab inline
u = ones(n_x)
plot(x,u)
311
Figura 15.6: Grafica con forma cuadrada según las condiciones iniciales
fig = plt.gcf()
u_past = u.copy() #Función en el tiempo pasado
plt.close()
plt.ioff()
La animación obtenida con este código presenta una secuencia de imágenes representada por la
figura 15.7
El resultado final de la animación se puede observar al graficar nuevamente con matplotlib en
el cuaderno de IPython:
312
Figura 15.7: Secuencia de imágenes a lo largo de la animación de la ecuación de convección
lineal
313
%pylab inline
plot(x,u)
La ecuación que se desea analizar ahora es la que describe la convección no lineal. La diferencia
con la ecuación diferencial anterior es que ahora se agrega un u al segundo término:
∂u ∂u
+u =0
∂t ∂x
Por lo tanto, el código que se implementa en el cuaderno de IPython para resolver la ecuación
diferencial parcial es:
%pylab inline
n_x = 80
n_t = 300
dx = x[1]-x[0]
dt = 0.001
314
u[where((x<1.25) & (x>0.75))] = 2.0
Si se grafica u contra x se obtiene la figura 15.6, ya que las condiciones iniciales son equivalentes
a las condiciones aplicadas para la convección lineal.
%pylab
import matplotlib.pyplot as plt
plt.ion()
u_past = u.copy()
plt.close()
plt.ioff()
%pylab inline
plot(x,u)
315
Figura 15.9: Resultado final de la ecuación de convección no lineal
15.3.3. Difusión
∂u ∂2u
=ν 2
∂t ∂x
Como en los casos anteriores, se obtienen las series de Taylor después de discretizar la ecuación
anterior. Para la parte temporal se tiene
∂u uj+1
i − uji
≈
∂t ∆t
Asimismo, para la parte espacial se obtiene
Despejando uj+1
i se obtiene el esquema de diferencias finitas:
uj+1
i = ναuji+1 + (1 − 2να)uji + ναuji−1
donde α = ∆t/(∆x)2 .
Con base en lo anterior, el código que permite generar las condiciones iniciales del problema es:
316
Figura 15.10: Secuencia de imágenes a lo largo de la animación de la ecuación de convección
lineal
317
n_x = 80
n_t = 100
c = 1.0
nu = 0.3
sigma = 0.2 #sigma es un parámetro para asegurar que \alpha\nu < 0.5
u = ones(n_x)
Al igual que en las ecuaciones anteriores, se define una condición inicial según la cual la función
u presenta forma cuadrada. Por lo tanto, la figura 15.6 representa nuevamente las condiciones
iniciales.
La animación que presenta la evolución de la función u a lo largo del tiempo se genera con el
siguiente código:
%pylab
import matplotlib.pyplot as plt
plt.ion()
plt.close()
plt.ioff()
318
La serie de imágenes que representan la animación se presentan en la figura 15.12. El resultado
final se observa en la figura 15.11 y se obtiene graficando con matplotlib:
%pylab inline
plot(x,u)
∂u ∂u ∂2u
+u =ν 2
∂t ∂x ∂x
Esta ecuación incluye los términos de convección no lineal y de difusión. Para resolver esta
ecuación se aplica nuevamente el método de diferencias finitas. Sin embargo, se varía la condición
inicial con el fin de que la función u tenga forma sinusoidal:
n_x = 100
n_t = 700
nu = 0.07
sigma = 0.02 #sigma es un parámetro para asegurar que \alpha\nu < 0.5
319
Figura 15.12: Secuencia de imágenes a lo largo de la animación de la ecuación de difusión
320
dt = sigma*dx**2/nu #dt se define usando sigma
alpha = dt/dx**2
u = sin(x)
Con base en el código, el resultado que se obtiene al graficar u contra x es un seno (ver figura
15.13).
%pylab
import matplotlib.pyplot as plt
plt.ion()
u_past = zeros(n_x)
u_past = u.copy()
321
ax.plot(x,u_past)
plt.show()
plt.pause(0.0001)
plt.clf()
plt.close()
plt.ioff()
xi = x0 + i∆x
yi = y0 + i∆y
Ahora, se define ui,j = u(xi , yj ) y se aplica la fórmula de diferencias finitas. Las variables x, y se
representan por lo índices i, j, respectivamente. Las derivadas se basan ahora en una expansión
de Taylor en dos dimensiones de un valor alrededor de ui,j .
Así, para una derivada parcial de primer orden en la dirección x, la fórmula de diferencias finitas
es:
∂u ui+1,j − ui,j
= + O(∆x)
∂x i,j ∆x
322
Figura 15.15: Secuencia de imágenes a lo largo de la animación de la ecuación de Burger
323
En la dirección y la derivada es similar. Por lo tanto, se pueden aplicar códigos en IPython
similares a los expuestos en la sección anterior.
En esta sección se pretende encontrar la solución de las ecuaciones presentadas en la sección
anterior en dos dimensiones. Para ello, se debe recordar que la derivada parcial con respecto a
una variable es la variación al dejar las otras variables constantes. En esta sección se generan
graficas en tres dimensiones y, para finalizar la sección, se presenta la solución a la ecuación
diferencial parcial de Burger en dos dimensiones.
La ecuación diferencial parcial que gobierna la convección lineal en dos dimensiones se escribe
como
∂u ∂u ∂u
+c +c =0
∂t ∂x ∂y
Nuevamente, los pasos temporales se discretizan junto con clos pasos espaciales. Como ahora el
indice j se reserva para la segunda variable espacial y, el tiempo se denotará con el subíndice n.
Con estos cambios, la ecuación diferencial parcial lineal de convección se puede escribir como
un+1 n
i,j − ui,j uni,j − uni−1,j uni,j − uni,j−1
+c +c =0
∆t ∆x ∆y
∆t n ∆t n
un+1 n
i,j = ui,j − c (u − uni−1,j ) − c (u − uni,j−1 )
∆x i,j ∆y i,j
Las condiciones iniciales para las cuales se resolverá la ecuación anterior son
(
2 for 0,5 ≤ x ≤ 1
u(x) =
1 for everywhere else
Para solucionar esta ecuación, se genera la gráfica correspondiente a las condiciones iniciales en
tres dimensiones en IPython con el siguiente código:
import numpy
from matplotlib import pyplot
%matplotlib inline
324
#declaración de variables
nx = 81
ny = 81
nt = 100
c = 1
dx = 2.0/(nx-1)
dy = 2.0/(ny-1)
sigma = .2
dt = sigma*dx
#Función en x y y entre 0 y 2
x = numpy.linspace(0,2,nx)
y = numpy.linspace(0,2,ny)
#Arreglo bidimensional
u = numpy.ones((ny,nx)) ##create a 1xn vector of 1’s
un = numpy.ones((ny,nx)) ##
ax = fig.gca(projection=’3d’)
X, Y = numpy.meshgrid(x,y)
surf = ax.plot_surface(X,Y,u[:])
Con el código anterior se obtiene la figura 15.16. El capítulo 3 presenta los conceptos básicos
para graficar en tres dimensiones, por lo que puede ser de utilidad recurrir a este capítulo.
Para evaluar la onda en dos dimensiones se requiere de iteraciones reiterativas en las dos
dimensiones espaciales y en la dimensión temporal, con el fin de resolver la siguiente ecuación
∆t n ∆t n
un+1 n
i,j = ui,j − c (u − uni−1,j ) − c (u − uni,j−1 )
∆x i,j ∆y i,j
u = numpy.ones((ny,nx))
u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2
325
Figura 15.16: Grafica en tres dimensiones de las condiciones iniciales
u[0,:] = 1
u[-1,:] = 1
u[:,0] = 1
u[:,-1] = 1
Con esto, se obtiene la gráfica en tres dimensiones que aparece en la figura 15.17
Un método de solución alternativo al código anterior se obtiene al implementar arreglos. Así,
se evitan los tres bucles de iteración y se implementa un solo bucle:
326
Figura 15.17: Grafica en tres dimensiones de las condiciones iniciales
u = numpy.ones((ny,nx))
u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2
un = u.copy()
u[1:,1:]=un[1:,1:]-(c*dt/dx*(un[1:,1:]-un[1:,:-1]))-(c*dt/dy*
(un[1:,1:]-un[:-1,1:]))
u[0,:] = 1
u[-1,:] = 1
u[:,0] = 1
u[:,-1] = 1
327
15.4.2. Convección en dos dimensiones
∂u ∂u ∂u
+u +v =0
∂t ∂x ∂y
∂v ∂v ∂v
+u +v =0
∂t ∂x ∂y
un+1 n
i,j − ui,j uni,j − uni−1,j un − uni,j−1
n i,j
+ uni,j + vi,j =0
∆t ∆x ∆y
n+1 n
vi,j − vi,j v n − vi−1,j
n i,j
n v n − vi,j−1
n i,j
n
+ ui,j + vi,j =0
∆t ∆x ∆y
∆t n n ∆t
un+1 n
i,j = ui,j − ui,j (ui,j − uni−1,j ) − vi,j (un − uni,j−1 )
∆x ∆y i,j
n+1
Asimismo, al solucionar vi,j para la segunda ecuación se obtiene:
n+1 n ∆t n n n ∆t
vi,j = vi,j − ui,j (v − vi−1,j ) − vi,j (v n − vi,j−1
n
)
∆x i,j ∆y i,j
Adicionalmente, las condiciones iniciales son las mismas que se usaron para la convección en
una dimensión:
(
2 para x, y ∈ (0,5, 1) × (0,5, 1)
u, v =
1 para cualquier valor diferente
Las condiciones de frontera restringen u y v igual a uno a lo largo de los límites de la cuadrícula:
(
x = 0, 2
u = 1, v = 1 para
y = 0, 2
328
#Declaraciones de las variables
nx = 101
ny = 101
nt = 80
c = 1
dx = 2.0/(nx-1)
dy = 2.0/(ny-1)
sigma = .2
dt = sigma*dx
x = numpy.linspace(0,2,nx)
y = numpy.linspace(0,2,ny)
un = u.copy()
vn = v.copy()
u[1:,1:]=un[1:,1:]-(un[1:,1:]*c*dt/dx*(un[1:,1:]-un[1:,:-1]))\
-vn[1:,1:]*c*dt/dy*(un[1:,1:]-un[:-1,1:])
v[1:,1:]=vn[1:,1:]-(un[1:,1:]*c*dt/dx*(vn[1:,1:]-vn[1:,:-1]))\
-vn[1:,1:]*c*dt/dy*(vn[1:,1:]-vn[:-1,1:])
u[0,:] = 1
u[-1,:] = 1
u[:,0] = 1
u[:,-1] = 1
v[0,:] = 1
v[-1,:] = 1
v[:,0] = 1
v[:,-1] = 1
329
Figura 15.18: Grafica en tres dimensiones de las condiciones iniciales
ax.plot_surface(X,Y,u, cmap=cm.coolwarm);
∂u ∂2u ∂2u
=ν 2 +ν 2
∂t ∂x ∂y
Al aplicar los métodos de discretización para derivadas parciales de segundo orden se obtiene
un+1 n
i,j − ui,j uni+1,j − 2uni,j + uni−1,j uni,j+1 − 2uni,j + uni,j−1
=ν + ν
∆t ∆x2 ∆y 2
ν∆t n ν∆t n
un+1 n
i,j = ui,j + (ui+1,j − 2uni,j + uni−1,j ) + (u − 2uni,j + uni,j−1 )
∆x 2 ∆y 2 i,j+1
330
Para resolver esta ecuación en IPython, se especifican en un inicio las condiciones iniciales:
import numpy
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D #libreria para graficos 3D
from matplotlib import cm #cm = "colormap" para cambiar el color
%matplotlib inline
#Declaración de variables
nx = 31
ny = 31
nt = 17
nu=.05
dx = 2/(nx-1)
dy = 2/(ny-1)
sigma = .25
dt = sigma*dx*dy/nu
x = numpy.linspace(0,2,nx)
y = numpy.linspace(0,2,ny)
fig = pyplot.figure()
ax = fig.gca(projection=’3d’)
X,Y = numpy.meshgrid(x,y)
surf = ax.plot_surface(X,Y,u, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_xlim(0,2)
ax.set_ylim(0,2)
ax.set_zlim(1,2.5);
def diffuse(nt):
u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2
331
Figura 15.19: Grafica en tres dimensiones de las condiciones iniciales
for n in range(nt+1):
un = u.copy()
u[1:-1,1:-1]=un[1:-1,1:-1]+nu*dt/dx**2*(un[1:-1,2:]-2*un\
[1:-1,1:-1]+un[1:-1,0:-2])+\
nu*dt/dy**2*(un[2:,1:-1]-2*un[1:-1,1:-1]+un[0:-2,1:-1])
u[0,:]=1
u[-1,:]=1
u[:,0]=1
u[:,-1]=1
fig = pyplot.figure()
ax = fig.gca(projection=’3d’)
surf = ax.plot_surface(X,Y,u[:], rstride=1, cstride=1, \
cmap=cm.coolwarm, linewidth=0, antialiased=True)
ax.set_zlim(1,2.5)
Al implementar la función anterior para nt=10 (diffuse(10)) se obtiene la figura 15.20. Para
nt=14 se obtiene la figura 15.21. Por último, al implementar la función para nt=50 se obtiene
la figura 15.22.
332
Figura 15.20: Grafica en tres dimensiones de la ecuación de difusión para nt=10+
333
Figura 15.22: Grafica en tres dimensiones de la ecuación de difusión para nt=50+
!
∂u ∂u ∂u ∂2u ∂2u
+u +v =ν + 2
∂t ∂x ∂y ∂x2 ∂y
!
∂v ∂v ∂v ∂2v ∂2v
+u +v =ν +
∂t ∂x ∂y ∂x2 ∂y 2
un+1 n
i,j − ui,j uni,j − uni−1,j un − uni,j−1
n i,j
+ uni,j + vi,j =
∆t ∆x ∆y
n
u − 2uni,j + uni−1,j uni,j+1 − 2uni,j + uni,j−1
i+1,j
ν +
∆x2 ∆y 2
n+1 n n − vn
vi,j − vi,j vi,j i−1,j v n − vi,j−1
n i,j
n
+ uni,j + vi,j =
∆t ∆x ∆y
n n + vn n n + vn
v − 2vi,j vi,j+1 − 2vi,j
i+1,j i−1,j i,j−1
ν +
∆x2 ∆y 2
Ahora se reordenan las ecuaciones anteriores para obtener las únicas incógnitas. Para la primera
expresión se obtiene:
∆t n n ∆t n n
un+1 n
i,j = ui,j − ui,j (ui,j − uni−1,j ) − v (u − uni,j−1 )+
∆x ∆y i,j i,j
ν∆t n ν∆t n
(ui+1,j − 2uni,j + uni−1,j ) + (u − 2uni,j + uni,j+1 )
∆x 2 ∆y 2 i,j+1
334
Asimismo, al despejar la única incógnita de la segunda expresión se obtiene:
n+1 n ∆t n n n ∆t n n n
vi,j = vi,j − ui,j (vi,j − vi−1,j )− v (v − vi,j−1 )+
∆x ∆y i,j i,j
ν∆t n n n ν∆t n n n
(v − 2vi,j + vi−1,j )+ (v − 2vi,j + vi,j+1 )
∆x2 i+1,j ∆y 2 i,j+1
Con base en lo anterior, si se desea graficar el conjunto de condiciones iniciales, se implementa
el siguiente código:
#Declaración de variables
nx = 41
ny = 41
nt = 120
c = 1
dx = 2/(nx-1)
dy = 2/(ny-1)
sigma = .0009
nu = 0.01
dt = sigma*dx*dy/nu
x = numpy.linspace(0,2,nx)
y = numpy.linspace(0,2,ny)
335
Figura 15.23: Gráfica en tres dimensiones de las condiciones iniciales
u[0,:] = 1
u[-1,:] = 1
u[:,0] = 1
u[:,-1] = 1
336
Figura 15.24: Gráfica en tres dimensiones de la ecuación de Burger
v[0,:] = 1
v[-1,:] = 1
v[:,0] = 1
v[:,-1] = 1
15.5. Ejercicios
∂2u ∂u
2
=
∂x ∂t
337
15.5.1. Condición 1
Este ejercicio consiste en escribir un programa en ipython notebook que resuelva la ecuación
de difusión de calor en el intervalo 0 <x<1 para las siguiente condiciones iniciales: u(x, 0) =
4x(1 − x), u(0, t) = u(1, t) = 0. Esta ecuación debe estar resuelta para el intervalo 0<t<3.
El código que permite solucionar la ecuación anterior es:
%pylab inline
#70 puntos en x y 10 en t
n_x = 70
n_t = 10
#Se define dx
dx = x[1]-x[0]
#Se define dt
dt = 0.0001
c = 1.0
print alfa
#Se grafica
plot(x,u)
Con este código se obtiene la gráfica de la condición inicial, que se presenta en la figura 15.25
La solución a la ecuación se obtiene mediante el método de diferencias finitas, que se incluye en
el siguiente ejercicio al interior de una animación.
15.5.2. Animación 1
Para el caso anterior, se desea realizar una animación que muestre la evolución de la función
u(x, t) para 10 intervalos de tiempo igualmente espaciados entre 0<t<3.
El código que permite solucionar el ejercicio es:
338
Figura 15.25: Gráfica generada con las condiciones iniciales
%pylab
plt.ion()
fig = plt.gcf()
u_past = u.copy() #Función en el tiempo pasado (j-1)
for i in range(1,n_x-1): #bucle sobre el espacio
plt.close()
plt.ioff()
339
15.5.3. Condición 2
Este ejercicio consiste en escribir un programa en ipython notebook que resuelva la ecuación
de difusión de calor en el intervalo -5<x<5 para las siguiente condiciones iniciales: u(x, 0) =
exp(−x2 ), u(−5, t) = u(5, t) = 0. Esta ecuación debe estar resuelta para el intervalo 0<t<3.
El código que permite solucionar la ecuación de difusión de calor es:
%pylab inline
#210 puntos en x y 10 en t
n_x = 210
n_t = 10
#Se define dx
dx = x[1]-x[0]
#Se define dt
dt = 0.001
c = 1.0
#Se grafica
plot(x,u)
Con este código se obtiene la gráfica de la condición inicial, que se presenta en la figura 15.26
La solución a la ecuación se obtiene mediante el método de diferencias finitas, que se incluye en
el siguiente ejercicio al interior de una animación.
15.5.4. Animación 2
Para el caso anterior, se desea realizar una animación que muestre la evolución de la función
u(x, t) para 10 intervalos de tiempo igualmente espaciados entre 0<t<3.
El código que permite solucionar el ejercicio es:
340
Figura 15.26: Gráfica generada con las condiciones iniciales
%pylab
plt.ion()
fig = plt.gcf()
u_past = u.copy() #Función en el tiempo pasado
for i in range(1,n_x-1): #bucle sobre el espacio
plt.close()
plt.ioff()
341
342
16
Procesamiento de imágenes
Al ejecutar los comandos anteriores, OpenCV estará disponible para utilizar en los cuadernos
de IPython.
En este capítulo se pretenden exponer algunas aplicaciones de la librería OpenCV para la
modificación de imágenes en IPython. En la primera sección se expone brevemente el origen de
343
OpenCV. Posteriormente, se exponen múltiples aplicaciones de OpenCV para la modificación de
imágenes, entre las que se encuentran la mezcla de imágenes, el cambio de color, la implementación
del umbral de imagen, la binarización Otsu, traslación y rotación de imágenes, suavizado y
desenfoque, filtros y transformaciones morfológicas como la erosión, dilatación, apertura, cierre,
gradiente morfológico y elemento estructurante en imágenes.
344
Figura 16.1: Línea de tiempo de OpenCV
Para abrir una imagen en Ipython con OpenCV, se sigue un procedimiento similar al que se
realiza cuando se abren imágenes con la librería de matplotlib. Por lo tanto, puede ser útil
repasar el capítulo 3.
IPython permite abrir imágenes si el de archivos que se localicen en el mismo directorio que el
cuaderno de Ipython. Para abrir la imagen se deben importar las librerías de matplotlib, numpy
y cv2:
%pylab inline
from matplotlib import pyplot as plt
import numpy as np
import cv2
Si se desea abrir una imagen con matplotlib utilizando pyplot se puede implementar el siguiente
código:
Con el código anterior se muestra la figura 16.2 correspondiente al archivo messi5.jpg ubicado
en el mismo directorio que el cuaderno de IPython. La forma de abrir un archivo con una imagen
con OpenCV y visualizarlo en IPython es por medio de la función cv2.imread() en reemplazo
345
Figura 16.2: Imagen abierta con matplotlib
de la función plt.imread(). Al interior de los paréntesis se especifica el nombre del archivo con
la imagen.
Sin embargo, al implementar la función de OpenCV se obtiene la imagen con los colores rojo y
azul intercambiados. Por ejemplo, para visualizar la figura 16.2 con OpenCV se implementa el
siguiente código
plt.xticks([]), plt.yticks([])
plt.subplot(122);plt.imshow(img2) #Se observa la imagen verdadera
plt.xticks([]), plt.yticks([])
plt.show()
Al aplicar el código anterior se visualiza la figura 16.3. La función cv2.split() permite obtener
los colores de las imágenes. Además, la función cv2.merge([r,g,b]) intercambia los colores
obtenidos con las función anterior para poder visualizar la imagen original. La figura 16.3
contiene la imagen que se visualiza con OpenCV sin intercambiar los colores. A la derecha de
la figura se observa la imagen obtenida al aplicar las dos funciones que permiten observar la
imagen con los colores originales.
Si se desea seleccionar una región de interés (abreviada ROI) es necesario recordar que la imagen
consiste en un arreglo bidimensional. Por lo tanto, se pueden especificar las coordenadas en x
346
Figura 16.3: Comparación entre imágenes abiertas con matplotlib y con OpenCV
plt.imshow(img2)
plt.show()
Cuando se quieren mezclar dos imágenes en IPython se puede recurrir a numpy o a OpenCV.
Con numpy se suman las dos imágenes como arreglos, pero las dos imágenes no son totalmente
visibles. OpenCV ofrece un mejor resultado, ya que se pueden ajustar los pesos de las imágenes,
es decir, se puede ajustar la intensidad de cada imágen con respecto a la otra.
Con OpenCV las imágenes se añaden con la siguiente ecuación:
donde α varía entre cero y uno y las imágenes son f0 (x) y f1 (x). Si α es uno, se cancela el
primer término de la ecuación anterior y la única imagen que se visualiza es f1 (x).
En el siguiente ejemplo se suman dos imágenes. A la primera se le da un peso de 0.7 y a la segunda
de 0.3. Para ello, se puede implementar la función de OpenCV llamada cv2.addWeighted(),
que aplica la siguiente ecuación:
347
Figura 16.4: Selección y duplicación de la región del balón
plt.figure(figsize=(10,10))
plt.subplot(121);plt.imshow(img1)
plt.xticks([]), plt.yticks([])
plt.subplot(122);plt.imshow(img2)
plt.xticks([]), plt.yticks([])
plt.show()
Con este código se visualiza la figura 16.5, que contiene las dos imágenes a mezclar.
Implementando la función cv2.addWeighted() de OpenCV se mezclan las dos imágenes. El
código en IPython que permite visualizar la mezcla es:
dst = cv2.addWeighted(img1,0.7,img2,0.3,0)
plt.imshow(dst)
plt.xticks([]), plt.yticks([])
plt.show()
El resultado se presenta en la figura 16.6. La primera imagen se observa con mayor claridad
debido a que se le asignó un peso mayor con respecto a la imágen del árbol.
348
Figura 16.5: Imágenes a mezclar
349
16.5. Cambio de color en imágenes
Para cambia el color de una imagen en IPython con OpenCV se debe implementar la función
cvtColor(). Esta función permite convertir una imagen de un espacio de color a otro. Para
ello, se requieren mínimo dos parámetros de entrada que son la imagen que se desea modificar
y el código de conversión del espacio de color. Los códigos de conversión inician con COLOR_ y
se pueden imprimir en IPython con el siguiente código:
350
’COLOR_RGBA2BGRA’, ’COLOR_RGBA2GRAY’, ’COLOR_RGBA2M_RGBA’,
’COLOR_RGBA2RGB’, ’COLOR_RGBA2YUV_I420’, ’COLOR_RGBA2YUV_IYUV’,
’COLOR_RGBA2YUV_YV12’, ’COLOR_XYZ2BGR’, ’COLOR_XYZ2RGB’,
’COLOR_YCR_CB2BGR’, ’COLOR_YCR_CB2RGB’, ’COLOR_YUV2BGR’,
’COLOR_YUV2BGRA_I420’, ’COLOR_YUV2BGRA_IYUV’, ’COLOR_YUV2BGRA_NV12’,
’COLOR_YUV2BGRA_NV21’, ’COLOR_YUV2BGRA_UYNV’, ’COLOR_YUV2BGRA_UYVY’,
’COLOR_YUV2BGRA_Y422’, ’COLOR_YUV2BGRA_YUNV’, ’COLOR_YUV2BGRA_YUY2’,
’COLOR_YUV2BGRA_YUYV’, ’COLOR_YUV2BGRA_YV12’, ’COLOR_YUV2BGRA_YVYU’,
’COLOR_YUV2BGR_I420’, ’COLOR_YUV2BGR_IYUV’, ’COLOR_YUV2BGR_NV12’,
’COLOR_YUV2BGR_NV21’, ’COLOR_YUV2BGR_UYNV’, ’COLOR_YUV2BGR_UYVY’,
’COLOR_YUV2BGR_Y422’, ’COLOR_YUV2BGR_YUNV’, ’COLOR_YUV2BGR_YUY2’,
’COLOR_YUV2BGR_YUYV’, ’COLOR_YUV2BGR_YV12’, ’COLOR_YUV2BGR_YVYU’,
’COLOR_YUV2GRAY_420’, ’COLOR_YUV2GRAY_I420’, ’COLOR_YUV2GRAY_IYUV’,
’COLOR_YUV2GRAY_NV12’, ’COLOR_YUV2GRAY_NV21’, ’COLOR_YUV2GRAY_UYNV’,
’COLOR_YUV2GRAY_UYVY’, ’COLOR_YUV2GRAY_Y422’, ’COLOR_YUV2GRAY_YUNV’,
’COLOR_YUV2GRAY_YUY2’, ’COLOR_YUV2GRAY_YUYV’, ’COLOR_YUV2GRAY_YV12’,
’COLOR_YUV2GRAY_YVYU’, ’COLOR_YUV2RGB’, ’COLOR_YUV2RGBA_I420’,
’COLOR_YUV2RGBA_IYUV’, ’COLOR_YUV2RGBA_NV12’, ’COLOR_YUV2RGBA_NV21’,
’COLOR_YUV2RGBA_UYNV’, ’COLOR_YUV2RGBA_UYVY’, ’COLOR_YUV2RGBA_Y422’,
’COLOR_YUV2RGBA_YUNV’, ’COLOR_YUV2RGBA_YUY2’, ’COLOR_YUV2RGBA_YUYV’,
’COLOR_YUV2RGBA_YV12’, ’COLOR_YUV2RGBA_YVYU’, ’COLOR_YUV2RGB_I420’,
’COLOR_YUV2RGB_IYUV’, ’COLOR_YUV2RGB_NV12’, ’COLOR_YUV2RGB_NV21’,
’COLOR_YUV2RGB_UYNV’, ’COLOR_YUV2RGB_UYVY’, ’COLOR_YUV2RGB_Y422’,
’COLOR_YUV2RGB_YUNV’, ’COLOR_YUV2RGB_YUY2’, ’COLOR_YUV2RGB_YUYV’,
’COLOR_YUV2RGB_YV12’, ’COLOR_YUV2RGB_YVYU’, ’COLOR_YUV420P2BGR’,
’COLOR_YUV420P2BGRA’, ’COLOR_YUV420P2GRAY’, ’COLOR_YUV420P2RGB’,
’COLOR_YUV420P2RGBA’, ’COLOR_YUV420SP2BGR’, ’COLOR_YUV420SP2BGRA’,
’COLOR_YUV420SP2GRAY’, ’COLOR_YUV420SP2RGB’, ’COLOR_YUV420SP2RGBA’]
#cv2.cvtColor(input_image, flag)
img1 = cv2.imread(’luna.jpg’)
hsv = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
plt.figure(figsize=(10,10))
plt.subplot(121);plt.imshow(img1)
plt.xticks([]), plt.yticks([])
plt.subplot(122);plt.imshow(hsv)
plt.xticks([]), plt.yticks([])
plt.show()
351
Figura 16.7: Cambio de color en una imagen
Si el valor de pixel es mayor que un valor umbral, se le asigna un valor (puede ser de color
blanco), de lo contrario se le asigna otro valor (puede ser de color negro). La función utilizada
es cv2.threshold. El primer argumento de esta función es la imagen de origen, que debe ser
una imagen en escala de grises. El segundo argumento de la función es el valor umbral que se
utiliza para clasificar los valores de los píxeles. El tercer argumento de la función es el Maxval
que representa el valor que debe darse si el valor del píxel es más que (a veces menos) el valor
de umbral. OpenCV proporciona diferentes estilos de umbral, que se especifica como cuarto
parámetro de la función. Con el siguiente código se pueden visualizar los diferentes estilos de
umbral:
img = cv2.imread(’luna.jpg’,0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)
plt.show()
352
Figura 16.8: Estilos de umbral con OpenCV
353
Figura 16.9: Estilos de umbral con OpenCV
Adicionalmente, el tamaño del bloque sobre el cual se desea calcular la media o la suma
ponderada es otro parámetro de entrada de la función cv2.threshold().
En contraste a los estilos de umbral globales, solo se obtiene un parámetro de salida. El siguiente
código permite comparar el estilo de umbral global con los dos métodos de umbral adaptativo
en una imagen con brillos diferentes en distintas regiones:
img = cv2.imread(’sudoku.jpg’,0)
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,11,2)
354
plt.figure(figsize=(10,10))
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],’gray’)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
El resultado se presenta en la figura 16.10. Las dos imágenes en la parte inferior de la figura
son los resultados de aplicar los métodos de umbral adaptativos. A la izquierda se observa
el método adaptativo cv2.ADAPTIVE_THRESH_MEAN_C. A la derecha de la figura se observa el
método cv2.ADAPTIVE_THRESH_GAUSSIAN_C.
La binarización Otsu es otro tipo de filtro que se puede aplicar a las imágenes. Este filtro
es óptimo para imágenes bimodales, es decir, imágenes con cuyo histograma de intensidades
presenta dos picos. Para una imagen bimodal se puede tomar un valor de umbral aproximadamente
en la mitad de los dos picos del histograma de la imagen. Para imágenes que no son bimodales
se obtiene una binarización no exacta.
Al igual que para los métodos de la sección anterior para el establecimiento de umbrales, se debe
implementar la función cv2.threshold() en la binarización Otsu. Uno de los parámetros de
entrada de la función es el tipo de filtro: cv2.THRESH_BINARY+cv2.THRESH_OTSU. Por ejemplo,
el siguiente código permite visualizar la diferencia entre un umbral global y una binarización
Otsu aplicada a la imagen original y a la imagen filtrada por el método gaussiano:
img = cv2.imread(’luna.jpg’,0)
#umbral global
ret1,th1 = cv2.threshold(img,120,255,cv2.THRESH_BINARY) #Se obtienen dos
#valores: intensidad del umbral que se escojió.
#es decir el valor del threshold otsu
print ret1
#binarización Otsu
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print ret2
#Binarización Otsu después de filtro gaussiano
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print ret3
#Se imprimen las gráficas
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = [’Original Noisy Image’,’Histogram’,’Global Thresholding (v=127)’,
’Original Noisy Image’,’Histogram’,"Otsu’s Thresholding",
355
Figura 16.10: Estilo de umbral global y métodos adaptativos
356
’Gaussian filtered Image’,’Histogram’,"Otsu’s Thresholding"]
plt.figure(figsize=(10,10))
for i in xrange(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],’gray’)
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],’gray’)
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([]) #primero se
#vuelve unidimensional con ravel y se genera el histograma
plt.show()
16.8. Transformaciones
Las transformaciones que se pueden aplicar a una imagen en IPython con OpenCV son escalado,
traslación, rotación, transformación afín, y transformación de perspectiva, entre otras. En esta
sección se presenta brevemente cada una de estas transformaciones
16.8.1. Escala
img = cv2.imread(’messi5.jpg’)
print shape(img)
357
Figura 16.11: Umbral global y binarización Otsu
358
Figura 16.12: Umbral global y binarización Otsu
359
print shape(res1)
#Alternativamente:
(342, 548, 3)
(684, 1096, 3)
16.8.2. Traslación
La translación es el cambio de ubicación del objeto. Si conoce el cambio en la dirección (x, y),
que se llamará (tx , ty ), se puede crear la matriz de transformación H de la siguiente manera:
" #
1 0 tx
M=
0 1 ty
import cv2
import numpy as np
img = cv2.imread(’messi5.jpg’,0)
rows,cols = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))
plt.imshow(’img’,dst)
Con este código se obtiene la figura 16.13. La traslación en este ejemplo es de (100,50). La
traslación se realiza mediante un arreglo de numpy de tipo float32, que entra como un
parámetro en la función de OpenCV cv2.warpAffine(), que realiza la traslación. El tercer
argumento de esta función es el tamaño de la imagen de salida, que debe estar en la forma
(ancho, alto).
360
Figura 16.13: Traslación de una imagen
16.8.3. Rotación
Pero OpenCV proporciona una rotación escalada con el centro de rotación ajustable de manera
que puede girar en un punto de referencia. La matriz de transformación de modificación está
dada por
" #
α β (1 − α) · center.x − β · center.y
−β α β · center.x + (1 − α) · center.y
donde:
α = escala · cos θ,
β = escala · sin θ
El resultado se puede visualizar en la figura 16.14. En este ejemplo se realizó una rotación de
30◦ .
361
Figura 16.14: Rotación de una imagen
En una transformación afín, todas las líneas paralelas en la imagen original serán paralelas en la
imagen de salida. Para llevar a cabo una transformación afín se debe implementar la función de
OpenCV cv2.getAffineTransform. Esta función genera una rotación de la imagen por medio
de una matriz de transformación.
Para encontrar la matriz de transformación, necesitamos tres puntos de imagen de entrada desde
y sus correspondientes ubicaciones en la imagen de salida. Entonces cv2.getAffineTransform
creará una matriz de 2x3, que entra como parámetro en la función cv2.warpAffine.
Por ejemplo, el siguiente código genera una transformación afín en una imagen que está compuesta
por numerosas líneas:
img = cv2.imread(’rejilla.png’)
rows,cols,ch = img.shape #Filas,columnas, canales de la imagen
dst = cv2.warpAffine(img,M,(cols,rows))
plt.figure(figsize=(10,10))
plt.subplot(121),plt.imshow(img),plt.title(’Input’)
362
Figura 16.15: Trasformación afín en una imagen
plt.subplot(122),plt.imshow(dst),plt.title(’Output’)
plt.show()
Para la transformación de perspectiva, se necesita una matriz de transformación 3x3. Con una
transformación de perspectiva se visualiza una imagen aparentemente desde otro punto de vista.
Para ello, las líneas rectas continúan siendo rectas aún después de la transformación.
Para encontrar la matriz de transformación se requieren 4 puntos de la imagen de entrada y los
puntos correspondientes en la imagen de salida. Entre estos 4 puntos, 3 de ellos no deben ser
colineales. Con esto, la matriz de transformación se puede encontrar con la función de OpenCV
cv2.getPerspectiveTransform función. Después, se debe aplicar la función cv2.warpPerspective
con esta matriz de transformación 3x3, de forma similar a como se realizó para una transformación
afín.
El siguiente código genera una transformación de perspectiva en una imagen de un sudoku:
img = cv2.imread(’sudoku.jpg’)
rows,cols,ch = img.shape
363
Figura 16.16: Trasformación de perspectiva en una imagen
Las imágenes se pueden filtrar con filtros de paso bajo (LPF) o filtros de paso alto (HPF). Por
medio de un filtro se reduce el ruido en las imágenes y se obtiene una apariencia suavizada con
unos valores de intensidad más uniformes y con unos bordes más definidos. Un LPF ayuda en
la eliminación de ruido o desenfoque de la imagen. En cuanto a los filtros HPF, se facilita la
búsqueda de los bordes de una imagen.
364
OpenCV proporciona una función, cv2.filter2D (), que convoluciona un núcleo con una
imagen. Por ejemplo, un núcleo de filtro 5x5 de promedio se puede definir como sigue:
1 1 1 1 1
1 1 1 1 1
1
K= 1 1 1 1 1
25
1
1 1 1 1
1 1 1 1 1
A esta matriz se le llama kernel y después de definirla se relizan los siguientes pasos: para
cada píxel, una ventana de 5x5 se centra en este píxel, todos los píxeles que caen dentro de esta
ventana se suman, y el resultado se divide por 25. Esto equivale a calcular el promedio de los
valores de los píxeles dentro de esa ventana. Esta operación se realiza para todos los píxeles de
la imagen para producir la imagen de salida filtrada.
En el siguiente ejemplo se implementa la matriz de dimensiones 5x5 para suavizar una imagen:
img = cv2.imread(’ruido.png’)
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title(’Averaging’)
plt.xticks([]), plt.yticks([])
plt.show()
365
Figura 16.17: Filtrado de una imagen para suavizarla
16.10.1. Promediar
Esta técnica de desenfoque se hace mediante la convolución de la imagen con un filtro de caja
normalizada. Simplemente se toma el promedio de todos los píxeles alrededor de un pixel central.
Este pixel se remplaza por el promedio de los elementos en un área determinada, denominada
núcleo. Para esto, se implementa la función cv2.blur() o cv2.boxFilter(). Se debe especificar
el ancho y la altura del núcleo. Un filtro de caja 3x3 normalizado se vería así
1 1 1
1
K = 1 1 1
9
1 1 1
El siguiente ejemplo aplica esta técnica para obtener un desenfoque de una imagen con un filtro
de 5x5:
img = cv2.imread(’ruido.png’)
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title(’Blurred’)
plt.xticks([]), plt.yticks([])
plt.show()
Con este código se obtiene la figura 16.18. A la izquierda de la figura se observa la imagen
original y a la derecha se observa la imagen resultante al aplicar la técnica de promediar para
conseguir el desenfoque.
366
Figura 16.18: Desenfoque de una imagen promediando
1 − x2 +y2 2
G(x, y) = e 2σ
2πσ 2
Con base en lo anterior, el siguiente código permite realizar un filtrado de Gauss:
img = cv2.imread(’ruido.png’)
blur = cv2.GaussianBlur(img,(5,5),3,3) #Se sigue la distribución
#gausiana
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title(’Blurred’)
plt.xticks([]), plt.yticks([])
plt.show()
Con este código se obtiene la figura 16.19. A la izquierda de la figura se observa la imagen
original y a la derecha se observa la imagen desenfocada por medio del filtro de Gauss.
367
Figura 16.19: Desenfoque de una imagen con filtrado de Gauss
img = cv2.imread(’pimienta.png’)
blur = cv2.medianBlur(img,5)
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title(’Blurred’)
plt.xticks([]), plt.yticks([])
plt.show()
Los filtros que hemos presentado anteriormente tienden a difuminar los bordes. Este no es el
caso para el filtro bilateral, cv2.bilateralFilter(), que se definió para la eliminación de ruido
368
Figura 16.20: Desenfoque de una imagen por medio del Median Blur
preservando los bordes en la imagen. Pero la operación es más lenta en comparación con otros
filtros. Ya vimos que un filtro de Gauss toma el área alrededor de un píxel y encuentra su media
ponderada de Gauss. Este filtro de Gauss es una función del espacio por sí sola, es decir, los
píxeles cercanos se consideran. No tiene en cuenta si los píxeles vecinos tienen el mismo valor de
intensidad y, por lo tanto, no tiene en cuenta si el píxel se encuentra en un borde o no. El efecto
resultante es que los filtros gaussianos tienden a difuminar los bordes, lo cual es indeseable.
El filtro bilateral también utiliza un filtro de Gauss en el dominio espacial, pero también utiliza
una función con base en las diferencias de intensidad de un pixel y los píxeles a su alrededor.
La función gaussiana de espacio se asegura de que sólo los píxeles que son ’vecinos espaciales’
se consideran para el filtrado, mientras que el componente gaussiano aplicado en el dominio
de intensidad (una función gaussiana de las diferencias de intensidad) se asegura de que sólo
los píxeles con intensidades similares a la del pixel central se incluyen para calcular el valor de
intensidad borrosa. Como resultado, este método conserva los bordes en la imagen.
El siguiente código aplica el método de filtrado bilateral para obtener el resultado de desenfoque
sin afectar los bordes en la imagen:
img = cv2.imread(’flor.jpg’)
blur = cv2.bilateralFilter(img,5,75,75) #5 es el tamaño del area y 75
#es que tenga en cuenta las 75 posiciones alrededor
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title(’Blurred’)
plt.xticks([]), plt.yticks([])
plt.show()
369
Figura 16.21: Desenfoque de una imagen por medio del filtro bilateral
El resultado del filtro bilateral se observa en la figura 16.21. Al igual que en las imágenes
anteriores, a la izquierda se observa la imagen original y a la derecha se observa la imagen
resultante del filtro bilateral. Como se puede evidenciar, los bordes no se desenfocan y son muy
similares a los de la imagen original.
Para comparar la efectividad de este filtro se puede visualizar el resultado obtenido con un filtro
de Gauss con el siguiente código:
img = cv2.imread(’flor.jpg’)
blur = cv2.bilateralFilter(img,5,75,75) #5 es el tamaño del area y 75 es
#que tenga en cuenta las 75 posiciones alrededor
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title(’Blurred’)
plt.xticks([]), plt.yticks([])
plt.show()
Como se puede observar en la figura 16.22, los bordes de la imagen se desenfocan con un filtro
gaussiano y la diferencia es notable con respecto a la figura 16.21. Por lo tanto, un filtro bilateral
genera unos mejores resultados, ya que los bordes no se distorsionan.
370
Figura 16.22: Desenfoque de una imagen por medio del filtro gaussiano
16.11.1. Erosión
Esta transformación consiste en erosionar los limites del objeto en el primer plano de la imagen.
Para que esta transformación funcione correctamente se debe mantener siempre la imagen en
primer plano con color blanco. Al igual que para la convolución 2D, es necesario crear un kernel.
Un pixel en la imagen original que esté rodeado de píxeles con valor 1, tomará el valor de 1 en
la imagen resultante. De lo contrario, el pixel toma el valor de 0.
Así, todos los píxeles cercanos a un límite serán descartados dependiendo del tamaño del kernel.
Por lo tanto, el tamaño de la imagen en el primer plano decrece o simplemente la región blanca
en la imagen decrece. Con base en esto, la erosión es útil para remover ruido blanco pequeño o
separar dos objetos conectados.
A continuación se presenta un código que genera una erosión con un kernel de 5x5:
img = cv2.imread(’bolas.jpg’,0)
kernel = np.ones((5,5),np.uint8) #Area de 5x5 para transformar
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img,cmap="gray"),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(erosion,cmap="gray"),plt.title(’Erosion’)
plt.xticks([]), plt.yticks([])
plt.show()
371
Figura 16.23: Erosión de una imagen
16.11.2. Dilatación
dilation = cv2.dilate(erosion,kernel,iterations = 2)
lt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(erosion,cmap="gray"),plt.title(’erode’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dilation,cmap="gray"),plt.title(’Dilatation’)
plt.xticks([]), plt.yticks([])
plt.show()
Con este código se obtiene la figura 16.24. A la izquierda se observa la imagen original y a la
derecha se visualiza el resultado de la dilatación, con círculos menos espaciados y que ocupan
un mayor área.
16.11.3. Apertura
Apertura es sólo otro nombre de una erosión seguida de una dilatación. Es útil en la eliminación
de ruido, como hemos explicado anteriormente. Aquí se utiliza la función, cv2.morphologyEx()
de OpenCV.
Por ejemplo, para aplicar una apertura en IPython se aplica el siguiente código:
372
Figura 16.24: Dilatación de una imagen
img = cv2.imread(’bolas2.png’,0)
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) #La matriz
#MORPH_OPEN realiza las dos operaciones
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img,cmap="gray"),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(opening,cmap="gray"),plt.title(’Openning’)
plt.xticks([]), plt.yticks([])
plt.show()
373
Figura 16.26: Cierre de una imagen
16.11.4. Cierre
img = cv2.imread(’close.png’,0)
kernel = np.ones((5,5),np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img,cmap="gray"),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(closing,cmap="gray"),plt.title(’Closing’)
plt.xticks([]), plt.yticks([])
plt.show()
El resultado del código anterior se presenta en la figura 16.26. La imagen a la derecha presenta
el cierre de las pequeñas líneas negras en la imagen original.
374
Figura 16.27: Gradiente morfológico en una imagen
img = cv2.imread(’close.png’,0)
kernel = np.ones((2,2),np.uint8) #Si se aumenta el kernel se vuelve mas
#ancho
img = cv2.imread(’cuadrado.png’,0)
kernel = np.ones((2,2),np.uint8)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img,cmap="gray"),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(tophat,cmap="gray"),plt.title(’Top Hat’)
plt.xticks([]), plt.yticks([])
plt.show()
375
Figura 16.28: Top Hat en una imagen
Black Hat es la diferencia entre el cierre de una imagen y la propia imagen. Esto pone de relieve
las regiones negras estrechos en la imagen.
Para implementar esta transformación se utiliza el siguiente código:
img = cv2.imread(’cuadrado.png’,0)
kernel = np.ones((3,3),np.uint8)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) #Se resaltan
#las regiones negras estrechas
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img,cmap="gray"),plt.title(’Original’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blackhat,cmap="gray"),plt.title(’Black Hat’)
plt.xticks([]), plt.yticks([])
plt.show()
Con las transformaciones anteriores se modificaba la imagen a través de una matriz de transformación
cuadrada, que se denominó kernel. Sin embargo, en algunos casos se requieren kernels con forma
elíptica o circular. Para esto, OpenCV cuenta con la función cv2.getStructuringElement().
Lo único que se debe realizar es especificar la forma y el tamaño del kernel para obtener la
matriz.
376
Figura 16.29: Black Hat en una imagen
Por ejemplo, el siguiente código permite generar un kernel elíptico en el cuaderno de IPython:
cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
Asimismo, con el siguiente código se puede crear una matriz con forma de cruz:
cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
El siguiente código permite visualizar la diferencia entre cada uno de los distintos tipos de
kernel:
377
Figura 16.30: Transformaciones con distintos tipos de kernel
img = cv2.imread(’bolas.jpg’,0)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)) #Se
#cambia la estructura del kernel
kernel2=cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5)) #Morph_cross
#es una cruz
erosion = cv2.erode(img,kernel,iterations = 4)
erosion2=cv2.erode(img,kernel2,iterations = 4)
plt.figure(figsize=(15,15))
plt.subplot(131),plt.imshow(img,cmap="gray"),plt.title(’original’)
plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(erosion,cmap="gray"),plt.title(’Erode-
ellipse’)
plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(erosion2,cmap="gray"),plt.title(’Erode-
morph_cross’)
plt.xticks([]), plt.yticks([])
plt.show()
378
17
Procesamiento de imágenes 2
Formulación Matemática
El operador requiere de dos kernels de 33, los cuales son elementos para aplicar una convolución
a la imagen original calculando aproximaciones a las derivadas para cada punto a calcular, de
este modo un kernel es usado para los cambios horizontales y el otro para las verticales. Si se
define A como la imagen original, Gx y Gy serán dos imágenes que representan para cada punto
las aproximaciones horizontal y vertical de las derivadas de intensidades, es expresado como:[19]
−1 0 +1
−2 0 +2 * A
Gx =
−1 0 +1
379
−1 −2 −1
Gy =
0 0 0*A
+1 +2 +1
De modo que para cada punto de la imagen los gradientes horizontal y vertical pueden combinarse
para resultar ser aproxiamadamente la magnitud del gradiente, expresado como:
q
G= G2x + G2y
Gy
θ = arctan( )
Gx
Donde Θ es 0 para bordes verticales con puntos más oscuros al lado izquierdo.
Se puede observar como los puntos de la imágen cambian de izquierda a derecha en una escala
380
de grises, para cada uno de los puntos de la imágen original.
%pylab inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread(’sudoku.jpg’,0)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
#imagen entrada, formato imagen salida,
#orden derivada x, orden derivada y, tamaño del kernel
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
plt.figure(figsize=(15,15))
plt.subplot(1,3,1),plt.imshow(img,cmap = ’gray’)
plt.title(’Original’), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx,cmap = ’gray’)
plt.title(’Sobel X’), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobely,cmap = ’gray’)
plt.title(’Sobel Y’), plt.xticks([]), plt.yticks([])
plt.show()
Fijese que necesita importar una imágen previamente establecida en ell kernel de
python
3. Resultado
381
Operador Laplaciano
∂2f ∂2f
∆f = +
∂x2 ∂y 2
Lo anterior resumiria rapidamente lo nesesario para recordar que se debe hacer para usar este
operador en las imagenes, sin embargo se explicara con mas argumento la utilidad de este
operador.
Como se vio previamente se busca minimizar los efectos de ruido o cáncer de la imagen, un
método consiste en unir junto con la operación de detección de ejes con un proceso de suavizado
de la imagen.
Uno le los métodos con mayor uso para el suavizado por medio de la utilización de una
Gaussiana, con el procedimiento de estos dos pasos:[24]
382
Al ser ambas operaciones lineales se pueden combinar de distintas maneras como:
Este método de detección de ejes fue propuesto por Marr and Hildreth por primera vez ,
introduciendo el principio de detecciones mediante el mt́odo de cruces por cero. Principio que
se basa en encontrar las posiciones en una imagen donde la segunda derivada toma el valor 0.
El Operador LoG es también sensible al ruido, pero los efectos del ruido pueden ser
reducidos si se ignoran los cruces por cero producidos por pequeos cambios en la
intensidad de la imagen.
img = cv2.imread(’sudoku.jpg’,0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
plt.figure(figsize=(15,15))
plt.subplot(1,2,1),plt.imshow(img,cmap = ’gray’)
plt.title(’Original’), plt.xticks([]), plt.yticks([])
plt.subplot(1,2,2),plt.imshow(laplacian,cmap = ’gray’)
plt.title(’Laplacian’), plt.xticks([]), plt.yticks([])
plt.show()
Imprimira
383
Figura 17.2: Ejemplo imágen bajo el operador Laplaciano
Métodos basados en el gradiente: detectan los bordes en base a las derivadas espaciales
de la imagen que se calculan mediante operadores de convolución
Fue desarrollado por John F. Canny en 1986. Se trata de un algoritmo de múltiples etapas.
1. Reducción de ruido
La detección de bordes es susceptible al ruido en la imagen, por eso el primer paso
es eliminar el ruido en la imagen con un filtro gaussiano de 5x5.
!
Gy
Ángulo (Θ) = tan−1
Gx
384
la imagen se realiza para eliminar los píxeles no deseados que pueden no constituir
el borde. Para ello, en cada pixel, pixel se comprueba si es un máximo local en su
vecindad en la dirección del gradiente.
4. Umbral de histéresis
Esta etapa se decide cuáles de todos los bordes son realmente bordes. Para esto,
necesitamos dos valores de umbral para los gradientes, MINVAL y Maxval. Cualquier
borde con gradiente de intensidad mayor a Maxval se esta seguro de ser bordes y el
gradiente menor a MINVAL se esta seguro que no es un borde. Los que se encuentran
entre estos dos umbrales son bordes que pueden ser considerados dependiendo su
conectividad. Si están conectados a elementos de la imagen que tengan un borde se
consideran. De lo contrario, también se descartan.
385
5. Umbral de histéresis
El borde A está por encima del Maxval, por lo que considera como un "borde seguro".
Aunque borde C está por debajo del Maxval, es conectado con el borde A, por lo
que también se considera como un borde válido Pero el borde B, a pesar de que
está por encima de MINVAL y está en misma región que el de borde C, no está
conectado a ningún borde, por lo que se descarta. Por lo tanto, es muy importante
que tenemos que seleccionar MINVAL y Maxval en consecuencia para obtener el
resultado correcto.
Esta etapa también elimina ruidos pequeños píxeles en el supuesto de que los bordes
son líneas largas.
OpenCV pone todo lo anterior en una sola función, cv2.Canny ().El operador coloca el primer
argumento como la imagen de entrada de entrada, el segundo y tercer argumento son nuestra
MINVAL y MAXVAL respectivamente.El cuarto argumento es apertures ize, el cual se refiere
a el tamaño del kernel Sobel utilizado para calcular el gradiente de la imagen (Por defecto es
3). Por último argumento es L2gradient que especifica la ecuación para encontrar magnitud del
gradiente.
Ejemplo
Fijese como el operador resulta ser tan efectivo como el operador Sobel a la hora de desifrar
contornos de la imagen sin ncesidad de tantas operaciones.
386
17.3.1. Imágenes Piramidales
Es común que trabajar con imágenes de forma constante, sin embargo en ciertas ocacíones es
necesario trabajar con imágenes de diferente resolución para una misma imágen. De esta manera
al buscar algún objeto determinado en la imágen, tal como una cara, no se esta completamente
seguro de cual sera el tamaño del objeto en la imágen. Es por ello que se requiere crear una serie
de imágenes de distinto tamaño, pero con el mismo contenido, siendo esta operación llamada
imágen piramidal.
Gaussiana
Laplaciana
El nivel más alto (baja resolución) en una pirámide Gaussiana es formado por la eliminación
de filas y columnas seguidas de la imagen.Se presentara como cada píxel de nivel superior está
formado por la contribución de 5 píxeles en el siguiente con pasos gaussianos. De esta manera,
una imagen de M ∗ N resultara ser M/2N/2. De esta manera una región se reduce a un cuarto
de la superficie original. El mismo patrón continúa a medida que avanzamos en la parte superior
de la pirámide Lo que permita que la resolución se redusca. Deligaul manera, mientras que la
expansión, se aumenta el área 4 veces en cada nivel. Podemos encontrar las pirámides gaussianas
utilizando cv2.pyrDown () y cv2.pyrUp () funciones.[? ]
img = cv2.imread(’messi5.jpg’)
lower_reso = cv2.pyrDown(higher_reso)
De igual manera es posible visualizar imagenes con una resolución menor con el uso de:
387
higher_reso2 = cv2.pyrUp(lower_reso)
17.3.2. Contornos
Los contornos pueden explicarse simplemente como una curva que une todos los puntos continuos
( a lo largo de la frontera ) , que tiene el mismo color o intensidad .[6]
Asumiendo que es una imágen binaria ,se puede decir que su contorno es la curva que une
los puntos blancos todos los límites . Así que si se observa un contorno de una imagen binaria
,se pódra encontrar los límite de los objetos de una imagen. Por eso OpenCV trabaja con
contornos, pues son una herramienta útil para el análisis de la forma y la detección de objetos
y reconocimiento.
Encontrando contornos
import numpy as np
import cv2
388
im = cv2.imread(’test.jpg’)
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
1. Para mayor precisión, se utiliza imágenes binarias. Así que antes de encontrar
contornos, se aplica un thershold o la detección de bordes de Canny
389
Como dibujar contornos
cv2.drawContours(im,contours,-1,(0,255,0),3)
Este cóigo dibujara a 3 píxeles de ancho un esquema verde del objeto . Si desea rellenar el objeto
con un color determinado, coloque el valor de -1 en el grosor de la línea.[7]
cv2.drawContours(im,contours,-1,(0,255,0),-1)
390
Figura 17.4: Contorno con relleno
En otras plabras [ara dibujar los contornos, se utiliza la función cv2.drawContours. También
puede ser usado para dibujar cualquier forma siempre y cuando tenga sus puntos frontera.
Su primer argumento es la imagen de origen, segundo argumento es el contorno que debe ser
aprobada como una lista de Python, tercer argumento es el índice de contornos (útil cuando
el dibujo del contorno individual. Para dibujar todos los contornos, pase -1) y el resto de
argumentos son de color, grosor ,etcétera.
Ejemplo
plt.imshow(im,cmap = ’gray’)
plt.xticks([]), plt.yticks([])
plt.show()
Imprime
391
El tercer argumento de la función cv2.findContours tiene diferentes opciones.
Los contornos son los límites de una forma con la misma intensidad. Almacena las coordenadas
(x, y) las coordenadas del límite de una forma. Pero almacena todas las coordenadas?
Si pasa cv2.CHAINA P P ROXN ON E, se almacenan todos los puntos de la frontera. Pero en
realidad necesitamos todos los puntos? Por ejemplo, se encuentre el contorno de una línea recta.
¿Necesita todos los puntos en la línea para representar a esa línea? No, necesitamos sólo dos
puntos extremos de la línea. Esto es lo que hace cv2.CHAINA P P ROXS IM P LE. Elimina
todos los puntos redundantes y comprime el contorno, con el consiguiente se ahorra memoria.
17.3.3. Momentos
Los momentos ayudan a calcular algunas características como centro de masa del objeto , área
del objeto , etc. El comando cv2.moments función ( ) da un diccionario de todos los valores
calculados momento . A continuacion se mostrara un ejemplo de como se realiza esta operación.
A partir de este momento , se puede extraer datos útiles como el área , centroide etc. centroide
M1 0 M0 1
está dada por las relaciones,Cx = M 00
y Cy = M00
. Esto puede mostrarse de la siguiente manera
cx = int(M[’m10’]/M[’m00’])
cy = int(M[’m01’]/M[’m00’])
print cx,cy
Imprime
270 148
plt.imshow(im,cmap = ’gray’)
plt.show()
392
Imprime
17.3.4. Área
zona del contorno está dada por la función cv2.contourArea () o de momentos , M [ ’ m00 ’] .
area = cv2.contourArea(cnt)
print int(M[’m00’]),area
Imprime
17433 17433.0
17.3.5. Perímetro
perimeter = cv2.arcLength(cnt,True)
print perimeter
393
Imprime
532.0
394
18
Procesamiento de imágenes 3
18.1. Histogramas
Figura 18.1
Para entender mejor cómo funciona un histograma de una imagen se puede utilizar el siguiente
ejemplo obtenido de [1]:
395
Figura 18.2
Del mismo modo funciona un histograma sobre una imagen; la diferencia es que en vez de
distribuir los píxeles en color se distribuye en intensidades. Por lo tanto en el eje x del histograma
se encuentra el nivel de intensidad (de 0 a 255) y en el eje y la cantidad de píxeles.
Figura 18.4: Histograma de una imagen con iluminación promediada. Obtenida de [1]
396
Figura 18.5: Histograma de una imagen con sobreexpuesta. Obtenida de [1]
Para calcular el histograma de una imagen en un notebook de ipython por medio de OpenCV
se usa la función cv2.calcHist() en la cual se ingresan los siguientes parámetros:
HistSize: representa nuestra cuenta BIN. Necesitará ser dada entre corchetes. Para
la escala completa, pasamos [256]. - gamas: este es nuestro rango. Normalmente, es
[0,256].
El siguiente ejemplo mostrará cómo usar la función cv2.calcHist() en una imagen y para un
sólo canal:
img = cv2.imread(’gato_gris.jpg’,0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
397
plt.figure(figsize=(10,10))
plt.subplot(1,2,1),plt.imshow(img,cmap = ’gray’)
plt.title(’Original’), plt.xticks([]), plt.yticks([])
plt.subplot(1,2,2),plt.plot(hist)
plt.title(’Histograma’)
plt.show()
Para ralizar un histograma para varios canales de una imagen se debe realizar un recorrido de
la siguiente manera:
img = cv2.imread(’paris.jpg’)
b,g,r = cv2.split(img)
img2 = cv2.merge([r,g,b])
color = (’b’,’g’,’r’)
398
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()
plt.imshow(img2)
plt.xticks([]), plt.yticks([])
plt.show()
El histograma resultante indicara la distribución de intensidades por cada canal (rojo, verde y
azul)
Si se desea crear el histograma sólo para una región de una imagen es necesario crear una
máscara e ingresarla como parámetro dentro de la función cv2.calcHist() de la siguiente
manera:
img = cv2.imread(’home.jpg’,0)
plt.figure(figsize=(15,15))
# creación de máscara
mask = np.zeros(img.shape[:2], np.uint8)
399
mask[100:300, 100:400] = 255
plt.show()
Figura 18.8
400
En azul se grafica el histograma de la imagen completa y en verde el histograma de imagen con
máscara.
En numpy también se puede graficar el histograma de una imagen con la función np.histogram()
implementando el siguiente código:
hist,bins = np.histogram(img.ravel(),256,[0,256])
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread(’gato_gris.jpg’,0)
plt.figure(figsize=(10,10))
plt.subplot(1,2,1),plt.imshow(img,cmap = ’gray’)
plt.title(’Original’), plt.xticks([]), plt.yticks([])
plt.subplot(1,2,2),plt.hist(img.ravel(),256,[0,256])
plt.title(’Histograma’)
plt.show()
401
A pesar de que el resultado con Numpy y OpenCV es muy similar la velocidad de procesamiento
de imágenes de OpenCV es mucho mayor y por lo tanto hacer el procedimiento de los ejemplos
anteriores con OpenCV es 40 veces más rápido que con Numpy.
Cuando se quieren corregir los errores de exposición y contraste de una imagen, como sobreexposición
o subexposición, es útil recurrir a la ecualización del histograma de la imagen. Con la ecualización
se transforma la imagen al distribuir uniformemente las intensidades de los píxeles.
#píxeles de intensidad k
H(k) = (18.1)
N ×M
Donde k es un nivel de intensidad en una imagen de tamaño N × M .
Teoricamente, el histograma debe ser uniforme sobre todos los niveles de intensidad; sin embargo
esto no es posible ya que se trabaja con funciones de distribución discreta y no de distribución
continua. En la transformación de la imagen los píxeles de un mismo nivel se convertirán a otro
nivel de gris. Esto causa que el histograma acumulado se aproxime a una recta.
El resultado al ecualizar una imagen es la misma imagen con el contraste maximizado sin
perder información estructural. El siguiente ejemplo muestra cómo realizar este procedimiento
y el efecto que tiene sobre una imagen:
Primero, se muestra la imagen y su histograma sin alterar (el historgama acumulado es representado
por la línea azul).
img = cv2.imread(’paisaje.jpg’,0)
hist,bins = np.histogram(img.flatten(),256,[0,256])
402
cdf = hist.cumsum()
cdf_normalized = cdf*30000.0 / 700000.0
plt.figure(figsize=(15,15))
plt.subplot(121), plt.imshow(img, ’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(122),
plt.plot(cdf_normalized, color = ’b’)
plt.hist(img.flatten(),256,[0,256], color = ’r’)
plt.xlim([0,256])
plt.legend((’cdf’,’histogram’), loc = ’upper left’)
plt.show()
Figura 18.12
Para ecualizar el histograma se usa la función cv2.equalizeHist(). La cual tiene como parámetro
la imagen.
equ = cv2.equalizeHist(img)
plt.figure(figsize=(15,15))
plt.subplot(121), plt.imshow(img, ’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(equ, ’gray’)
plt.xticks([]), plt.yticks([])
plt.show()
403
Al comparar la imagen resultante con la original se puede decir que el contraste ha aumentado
y los objetos más cercanos ahora son más reconocibles. Sin embargo se pierde detalle en las
zonas claras de la imagen.
hist,bins = np.histogram(equ.flatten(),256,[0,256])
cdf = hist.cumsum()
cdf_normalized = cdf *30000.0 / 700000.0
plt.plot(cdf_normalized, color = ’b’)
plt.hist(equ.flatten(),256,[0,256], color = ’r’)
plt.xlim([0,256])
plt.legend((’cdf’,’histogram’), loc = ’upper left’)
plt.show()
404
18.1.5. CLAHE (Contrast Limited Adaptive Histogram Equalization)
img = cv2.imread(’cabeza.jpg’,0)
equ = cv2.equalizeHist(img)
plt.figure(figsize=(15,15))
plt.subplot(121), plt.imshow(img, ’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(equ, ’gray’)
plt.xticks([]), plt.yticks([])
plt.show()
plt.show()
405
Figura 18.16: Original (derecha) y con ecualización adaptativa (izquieda)
Se puede observar que con este tipo de ecualización los detalles estructurales de la imagen logran
mayor resolución; sin importar que las zonas estés sobreexpuestas o subexpuestas.
Una imagen es dada en dos dimensiones y por lo tanto se se define con una función (f (m, n)) de
dos variables discretas m y n, las cuales definen las coordenadas de los píxeles. Esta función es
sinusoidal y se caracteriza por su fase, su frecuencia de oscilación y su dirección de oscilación.
f (m, n) tiene la forma:
f (m, n) = sin{2π(w1 m + w2 n)} (18.2)
Donde w1 y w2 son las dos frecuencias (ciclos/píxel); las cuales tienen un periodo 1. Por
lo tanto la imagen a w1 = 0,2 es igual a w1 = 1,2. Estas frecuencias están en el rango
0ciclos/pix < w1 , w2 < 1ciclos/pix; debido a que este es el máximo permitido para digitalizar
una imágen.
∞ ∞
f (m, n)e−jw1 m e−jw2 n
X X
F (w1 , w2 ) = (18.3)
m=−∞ n=−∞
Donde F (w1 , w2 ) es la representación del dominio de frecuencias de f (m, n). Esta función debe
ser discretizada. Para una imagen de tamaño finito M × N la inversa de la transformada de
fourier discretizada es :
Z 1Z 1
f (m, n) = F (w1 , w2 )ej2π(w1 m+w2 n) dw1 dw2 (18.4)
0 0
406
v
Se realiza un cambio de variables: w1 = uM y w2 = N.
−1 N −1
1 MX u v
f (m, n)e−j2π( M m+ N n)
X
F (u, v) = (18.5)
M N m=0 n=0
−1 N −1
1 MX X u v
f (m, n) = F (u, v)ej2π( M m+ N n) (18.6)
M N m=0 n=0
Un filtro pasa-baja atenúa los componentes de medias-altas frecuencias y no modifican las bajas
dependiendo de la frecuencia límite que se elija. Este tipo de filtro se usa para atenuar ruido de
alta frecuencia o eliminar cambios bruscos en el nivel de gris.
El filtro pasa-alta atenúa los componentes de medias-bajas frecuencias y no modifica las altas
frecuencias. Esto permite realizar una diferenciación más clara de los bordes de un objeto en
una imagen ya que estos representan altas frecuencias al ser cambios bruscos en el nivel de gris.
Por último, el filtro pasa-banda mantiene atenúa frecuencias bajas y altas manteniendo un rango
medio.
img = cv2.imread(’ruido.png’,0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(img, cmap = ’gray’)
plt.title(’Input Image’), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = ’gray’)
plt.title(’Magnitude Spectrum’),
plt.show()
407
Figura 18.17: Magnitud del espectro de la imagen
plt.show()
El resultado será:
408
Para aplicar el filtro pasa-baja, sobre la imagen, se usa el siguiente código:
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
mask = np.zeros((rows,cols),np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
Para encontrar la imagen plantilla, de tamaño (w ×h) en la imagen fuente se recorre la imagen
fuente (I) comparando los píxeles de la plantilla (T ) con los píxeles del área recorrida en
409
la imagen fuente. Si los píxeles son iguales, o similares, estos se guardan en una matriz R.
Para determinar ,la ubicación de R en I se buscan los píxeles similares con máxima o mínima
intensidad (depende del método). Una vez que se establece la similitud máxima o mínima se
toma este punto, con la posición (x,y), como la esquina del rectangulo que señalara la posición
de la imagen resultante y desde este punto se acomoda el tamaño del rectangulo con (w × h) de
ancho y altura . Ya que la imagen fuente tiene mayor tamaño que la imagen plantilla y siendo
el tamaño de la imagen fuente (W × H) la imagen resultante tendrá un tamaño de (W-w + 1,
H-h + 1).
CV_TM_SQDIFF
R(x, y) =
P 0 0 − I(x + x0 , y + y 0 ))2
x0 ,y 0 (T (x , y )
CV_TM_SQDIFF_NORMED
P 0 0 0 0 2
0 0 (T (x ,y )−I(x+x ,y+y ))
R(x, y) = P x ,y
q P
0 0 2 0
T (x ,y ) · I(x+x ,y+y 0 )2
x0 ,y 0 x0 ,y 0
CV_TM_CCORR
R(x, y) =
P 0, y0) · I(x + x0 , y + y 0 )
x0 ,y 0 (T (x
CV_TM_CCORR_NORMED
P
x0 ,y 0
(T (x0 ,y 0 )·I(x+x0 ,y+y 0 )
R(x, y) = qP P
x0 ,y 0
T (x0 ,y 0 )2 · x0 ,y 0
I(x+x0 ,y+y 0 )2
CV_TM_CCOEFF
R(x, y) =
P 0 (x0 , y 0 ) · I 0 (x + x0 , y + y 0 )
x0 ,y 0 (T
Donde
I 0 (x + x0 , y + y 0 ) = I(x + x0 , y + y 0 ) − (w·h)·
P 1
I(x+x0 ,y+y 0 )
x00 ,y 00
CV_TM_CCOEFF_NORMED
P 0 (x0 ,y 0 )·I 0 (x+x0 ,y+y 0 )
x0 ,y 0
(T
R(x, y) = qP P
x0 ,y 0
T 0 (x0 ,y 0 )2 · x0 ,y 0
I 0 (x+x0 ,y+y 0 )2
410
El siguiente código implementa todos los métodos de comparación de plantillas sobre la misma
imagen fuente y con la misma plantilla:
img = cv2.imread(’messi5.jpg’,0)
img2 = img.copy()
template = cv2.imread(’template.jpg’,0)
w, h = template.shape[::-1]
plt.figure(figsize=(15,15))
plt.subplot(121),plt.imshow(res,cmap = ’gray’)
plt.title(’Matching Result’), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img,cmap = ’gray’)
plt.title(’Detected Point’), plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
411
Figura 18.20: Resultado de comparacion de plantilla
18.4. Ejercicio
Escribir un programa en ipython donde mida el área y el perímetro de las monedas de 50, 100,
200, 500 y 1000 pesos. Establecer en la imagen cuántas monedas de cada denominación hay.
Imagen:
412
Figura 18.22: Caption
413
#Se imprimen los contornos encontrados
print("contornos encontrados: ",len(cnts))
414
print "perimetro promedio: ",average(pmtmil)
print "////////////////////////"
print "monedas de quinientos: ",len(quin)
print "area promedio: ",average(quin)
print "perimetro promedio: ",average(pmtquin)
print "////////////////////////"
print "monedas de doscientos: ",len(dos)
print "area promedio: ",average(dos)
print "perimetro promedio: ",average(pmtdos)
print "////////////////////////"
print "monedas de cien: ",len(cien)
print "area promedio: ",average(cien)
print "perimetro promedio: ",average(pmtcien)
print "////////////////////////"
print "monedas de cincuenta: ",len(cinc)
print "area promedio: ",average(cinc)
print "perimetro promedio: ",average(pmtcinc)
print "////////////////////////"
print "Monedas totales:",len(mil)+len(quin)+len(dos)+len(cien)+len(cinc)
Resultado:
415
Figura 18.23: Solución de ejercicio
416
Bibliografía
[1] Salvador Alicea. Qué es el histograma y cómo interpretarlo. (2011). retrieved from
http://www.aprendefotografiadigital.com/afd/2011/04/19/entendiendo-histograma/
axzz4f4m27e5f.
[2] Anton Aubanell, Antoni Benseny, and Amadeu Delshams. Útiles básicos de cálculo
numérico, volume 2. Univ. Autònoma de Barcelona, 1993.
[4] Gary Bradski and Adrian Kaehler. Learning OpenCV: Computer vision with the
OpenCV library. .O’Reilly Media, Inc.", 2008.
[5] Francisco Correa. Métodos Numéricos. Fondo editorial Universidad EAFIT, 2010.
[12] Hotmath.com. Recta que mejor se ajusta (método de mínimos cuadrados). (2016). hotmath.com.
retrieved 2 july 2016, from http://hotmath.com/hotmathh elp/spanish/topics/line−of −best−
f it.html.
[13] N. (2014). Jimenez Jimenez. Tema 1.- ecuaciones diferenciales ordinarias de primer orden (1st
ed., p. 16). sevilla: Escuela politécnica superior.
[14] Jaan Kiusaalas. Numerical Methods in Engineering with MATLAB. Cambridge University
Press, 2005.
417
[17] Shoichiro Nakamura and Óscar Alfredo Palmas Velasco. Métodos numéricos aplicados con
software. Number QA297 N34. Prentice-Hall Hispanoamericana Mexico, 1992.
[19] S. Patnaik and Y.M. Yang. Soft Computing Techniques in Vision Science. "Springer", 2012.
[20] Richard Petersen, Eduardo Jiménez Ferry, Antonio Vaquero Sánchez, and Luis Hernández
Yáñez. Linux: manual de referencia. McGraw-Hill, 1997.
[21] Mark G Sobell. A practical guide to Linux commands, editors, and shell programming. Prentice
Hall Professional Technical Reference, 2005.
[22] Josef Stoer and Roland Bulirsch. Introduction to numerical analysis, volume 12. Springer
Science & Business Media, 2013.
[25] www.sc.ehu.es.
418