0% encontró este documento útil (0 votos)
3 vistas

Bitacora1_Metodos

El documento es una bitácora del curso de Métodos Computacionales en la Universidad de los Andes, que abarca temas de Unix, Linux y Python. Incluye un índice detallado de clases, comandos básicos, estructuras de control, ejercicios prácticos y métodos de programación. Está diseñado para guiar a los estudiantes en el aprendizaje de herramientas computacionales y programación en Python.

Cargado por

esmeraldabrown67
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
3 vistas

Bitacora1_Metodos

El documento es una bitácora del curso de Métodos Computacionales en la Universidad de los Andes, que abarca temas de Unix, Linux y Python. Incluye un índice detallado de clases, comandos básicos, estructuras de control, ejercicios prácticos y métodos de programación. Está diseñado para guiar a los estudiantes en el aprendizaje de herramientas computacionales y programación en Python.

Cargado por

esmeraldabrown67
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 418

Métodos Computacionales

Universidad de los Andes


Departamento de Física
Bitácora

Valentina Carmona Mongua Juan Sebastián Parada Beltrán


Juan David Solano Acosta

20 de septiembre de 2018
2
Índice general

1. Clase 1: Unix y Linux 11


1.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2. La terminal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3. Comandos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.1. Comandos pwd y ls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.2. Comandos mkdir, cd y rmdir . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.3. Comandos mv y rm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.4. Comandos tar y gunzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3.5. Comandos wget y curl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.6. Comandos wc, less y cat . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.7. Comandos sed, grep y awk . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.8. Comandos chmod, man, history y exit . . . . . . . . . . . . . . . . . . . . 16
1.3.9. Comando printf y especificadores de formato . . . . . . . . . . . . . . . . 16
1.4. Condicionales y bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.1. Condicional if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4.2. Bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.4.3. Bucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.1. Tabla de multiplicar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.2. Número entero entre 20 y 30 . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5.3. Información astronómica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.5.4. 150 comics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.6. Taller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.6.1. Una pequeña araña . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.6.2. Planetas extrasolares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

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

3. Clase 3 y clase 4: IPython 47


3.1. Numpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.1.1. Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.1.2. Operaciones básicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.1.3. Indexación, cortar e iteración . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.2. Ejercicio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.3. Matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.3.1. Herramientas de visualización 2D . . . . . . . . . . . . . . . . . . . . . . . 55
3.3.2. Gráficas de dispersión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.3.3. plt.plot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3.4. Color y estilos de línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3.5. Títulos, rótulos en ejes y leyendas . . . . . . . . . . . . . . . . . . . . . . 58
3.3.6. Otros tipos de gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.3.7. Animaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.4.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.4.2. Curvas de Lissajous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.4.3. Caminata aleatoria en 3D . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

4. C: bases, escritura, variables, aritmética y bucles 79


4.1. Estructura, edición, compilación y ejecución . . . . . . . . . . . . . . . . . . . . . 79
4.1.1. Estructura y edición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.2. Compilación y ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.2.1. Variables tipo int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.2.2. Variables de tipo float . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.2.3. Variables tipo carácter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.3. Leyendo variables del usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.4. Operaciones matemáticas y números complejos . . . . . . . . . . . . . . . . . . . 83
4.4.1. Operaciones matemáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.4.2. Números complejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

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. C: arreglos, asignación de memoria dinámica y punteros 91


5.1. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.1.1. Inicializar Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.1.2. Recorrer elementos en un Array . . . . . . . . . . . . . . . . . . . . . . . . 92
5.1.3. Arreglos para caracteres (strings) . . . . . . . . . . . . . . . . . . . . . . . 92
5.1.4. Posición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.1.5. Logitud del Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.1.6. Libreria string.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.1.7. Arrays indeterminados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.1.8. libreria ctype.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.2. ¿Para que sirven las estructuras dinámicas? . . . . . . . . . . . . . . . . . . . . . 96
5.2.1. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.2.2. Operaciones con puntero . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.2.3. funciones con punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.2.4. Asignación dinámica de memoria C . . . . . . . . . . . . . . . . . . . . . . 98
5.2.5. Arrays y punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
5.2.6. Estructuras en punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.2.7. Arrays mutidimensionales y punteros . . . . . . . . . . . . . . . . . . . . . 101

6. C: Condicionales y funciones 103


6.1. Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.1.1. Operadores de comparación y operadores lógicos . . . . . . . . . . . . . . 103
6.1.2. Operador ternario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
6.1.3. Operadores condicionales bitwise . . . . . . . . . . . . . . . . . . . . . . . 106
6.2. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.2.1. Definir una función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.2.2. Parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.2.3. Punteros como argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . 110
6.2.4. Retornar el valor de un puntero . . . . . . . . . . . . . . . . . . . . . . . . 111
6.2.5. Tipos de variables según su ámbito . . . . . . . . . . . . . . . . . . . . . . 112
6.3. Inputs/Outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.4.1. Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.4.2. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.4.3. Lectura de archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

7. Algebra lineal 125


7.1. Sistemas de ecuaciones lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.1.1. Suma y resta de matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
7.1.2. Multiplicación escalar y matriz transpuesta . . . . . . . . . . . . . . . . . 127
7.1.3. Multiplicación entre matrices . . . . . . . . . . . . . . . . . . . . . . . . . 127
7.1.4. Propiedades del álgebra de matrices . . . . . . . . . . . . . . . . . . . . . 128

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

9. Ajuste de curvas en Python 175


9.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9.2. Mínimos Cuadrados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9.2.1. Ajuste de una linea recta por el metodo de los mínimos cuadrados . . . . 175
9.2.2. Interpolación Polinomial . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
9.2.3. Regresión Lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
9.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
9.3.1. Ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
9.3.2. Ejercicio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.3.3. Ejercicio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
9.3.4. Ejercicio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
9.3.5. Ejercicio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
9.3.6. Ejercicio 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194

10.Búsqueda de raíces 199


10.0.1. Búsqueda incremental . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
10.0.2. Método de bisección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
10.0.3. Método Ridder (Falsa Posición) . . . . . . . . . . . . . . . . . . . . . . . . 205
10.0.4. Métodos De Newton-Raphson para sistemas de n ecuaciones no lineales . 206
10.1. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
10.1.1. Ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
10.1.2. Ejercicio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
10.1.3. Ejercicio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
10.1.4. Ejercicio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
10.1.5. Ejercicio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217

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

12.Métodos de integración 237


12.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
12.2. Fórmulas de integración de Newton-Cotes . . . . . . . . . . . . . . . . . . . . . . 238
12.2.1. Regla del trapecio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
12.2.2. Integración Romberg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
12.3. Integración Gaussiana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
12.4.1. Análisis geométrico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
12.4.2. Prueba de tensión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
12.4.3. Integración Romberg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
12.4.4. Péndulo simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

13.Ecuaciones diferenciales de primer orden 263


13.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
13.2. Problemas de valor inicial. Teorema de existencia y unicidad de soluciones. . . . 265
13.3. Métodos numéricos para ecuaciones de primer orden . . . . . . . . . . . . . . . . 266
13.3.1. Método de Euler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
13.3.2. Método Runge Kutta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272

14.Ecuaciones diferenciales: Problemas de contorno 277


14.1. Método de shooting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
14.2. Diferencias finitas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
14.2.1. Diferencias finitas con condiciones de Dirichlet . . . . . . . . . . . . . . . 286
14.2.2. Diferencias finitas con condiciones de Neumman . . . . . . . . . . . . . . 287
14.2.3. Diferencias finitas con condiciones mixtas . . . . . . . . . . . . . . . . . . 288
14.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291

15.Ecuaciones diferenciales parciales 301


15.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
15.2. Diferencias finitas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
15.2.1. Ecuación de difusión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
15.2.2. Ecuación de onda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
15.3. Fundamentos de la dinámica de fluidos computacional . . . . . . . . . . . . . . . 310
15.3.1. Convección lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
15.3.2. Convección no lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
15.3.3. Difusión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
15.3.4. Ecuación de Burger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
15.4. Derivadas parciales en dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . . 322
15.4.1. Convección lineal en dos dimensiones . . . . . . . . . . . . . . . . . . . . . 324
15.4.2. Convección en dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . . 328
15.4.3. Difusión en dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . . . . 330
15.4.4. Ecuación de Burgers en dos dimensiones . . . . . . . . . . . . . . . . . . . 332

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

16.Procesamiento de imágenes 343


16.1. Introducción a OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
16.2. Origen de OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
16.3. Abrir imágenes con OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
16.4. Mezcla de imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
16.5. Cambio de color en imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
16.6. Umbral de imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
16.7. Binarización Otsu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
16.8. Transformaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
16.8.1. Escala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
16.8.2. Traslación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
16.8.3. Rotación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
16.8.4. Transformación afín . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
16.8.5. Transformación de perspectiva . . . . . . . . . . . . . . . . . . . . . . . . 363
16.9. Suavizado de imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
16.9.1. Convolución en dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . 364
16.10.Desenfoque de la imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
16.10.1.Promediar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
16.10.2.Filtrado de Gauss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
16.10.3.Median Blur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
16.10.4.Filtro bilateral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
16.11.Transformaciones morfológicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
16.11.1.Erosión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
16.11.2.Dilatación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
16.11.3.Apertura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
16.11.4.Cierre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
16.11.5.Gradiente morfológico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
16.11.6.Top Hat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
16.11.7.Black hat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
16.11.8.Elemento estructurante . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376

17.Procesamiento de imágenes 2 379


17.1. Gradiente de una imagén . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
17.1.1. Operador Sobel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
17.2. Detección de bordes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
17.3. Deteción de borde Canny en OpenCV . . . . . . . . . . . . . . . . . . . . . . . . 386
17.3.1. Imágenes Piramidales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
17.3.2. Contornos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
17.3.3. Momentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
17.3.4. Área . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
17.3.5. Perímetro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393

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

Clase 1: Unix y Linux

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.

Además de realizar su función de interpretación de comandos de un teclado y enviar esos


comandos al sistema operativo, la terminal es un lenguaje de programación de alto nivel [21].
Los comandos de la terminal se pueden organizar en un archivo para su posterior ejecución.
Esta flexibilidad permite a los usuarios realizar operaciones complejas con relativa facilidad, a
menudo mediante la emisión de órdenes cortas, o construir con pequeños esfuerzos programas
que realizan operaciones muy complejas [21].
A continuación se presentarán algunos comando básicos que se pueden implementar desde
una terminal en Linux.

1.3. Comandos Básicos


1.3.1. Comandos pwd y ls
El comando pwd permite reconocer el directorio actual de trabajo. Por ejemplo, al aplicar
este comando se obtiene lo siguiente

/home/usuario/

Esto indica que el directorio actual de trabajo es /home/usuario/.


Por otro lado, el comando ls permite conocer los archivos y directorios dentro del directorio
de trabajo. Este comando presenta los archivos y directorios en orden alfabético. Un ejemplo
de lo que se obtiene con este comando es el siguiente:

Descargas Escritorio Imágenes Plantillas Vídeos


Documentos Música Público

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

genera un nuevo directorio llamado Metodos en el directorio en el que estamos trabajando.


Para cambiar de ubicación entre los distintos directorios se puede implementar el comando
cd. Al escribir el comando se debe dejar un espacio y escribir el directorio al cual se debe
ingresar. Así, el directorio al cual se desea acceder debe estar ubicado en la carpeta en la que se
está trabajando. Por ejemplo, el siguiente comando nos permite acceder al directorio Metodos
si este ha sido creado anteriormente y se encuentra en el directorio en el que nos encontramos:

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.

1.3.4. Comandos tar y gunzip


El comando tar -xvf permite expandir archivos que se encuentran comprimidos en formato
.tar. Para ello, se escribe el comando en la terminal y en seguida se escribe el archivo que se
desea expandir. El siguiente ejemplo expande el archivo en formato .tar para generar el archivo
notas.sh:

tar -xvf notas.sh.tar

Al aplicar el comando se obtiene el archivo en formato .sh.


El comando gunzip descomprime archivos que se encuentren en formato .gz. Este comando
se implementa de la misma forma que el comando tar -xvf.

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.

1.3.5. Comandos wget y curl


:
El comandon wget descarga ficheros desde Internet mediante el uso de la terminal. Para
implementar este comando, se debe especificar la url de la pagina que contiene el archivo que
se desea descargar. Algunas opciones interesantes de este comando:s no

-c continua la descarga.

-b continua la descarga aun si salimos del terminal.

-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.

-U mozilla toma la identidad del navegador mozilla.

-o log.txt crea un logs con la salida de wget.

-e robots=off no descargar el archivo robots.txt

--random-wait espera un tiempo aleatorio entre descarga, para evitar entrar en la lista
de negra.

--limit-rate=20k limita la velocidad a la que descarga a 20k.

Un ejemplo en el que se aplica el comando wget es el siguiente:

wget https://www.dropbox.com/s/nyumkp0kf0pdkfk/hipparcos.csv.tar.gz

Con este comando se obtiene el archivo hipparcos.csv.tar.gz de la url proporcionada.


El comando curl imprime el código fuente de una pagina web. Para ello, se escribe el
comando y se especifica la pagina de la cual se desea obtener el código. :
curl http://xkcd.com

Con el comando anterior se obtiene el código fuente de la pagina especificada.

1.3.6. Comandos wc, less y cat


El comando wc sirve para contar líneas, palabras y caracteres que contiene un archivo. Para
implementarlo, el archivo del cual se desea obtener los datos debe estar ubicado en el mismo
directorio en el que se está trabajando. Un ejemplo es el siguiente:

wc notas.sh

Con este comando se obtiene

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.

1.3.7. Comandos sed, grep y awk


El comando sed es un editor de flujos y archivos que permite modificar el contenido de las
diferentes líneas de un archivo en base a una serie de comandos o un archivo de comandos. Un
ejemplo es el siguiente:
sed "s/a/b/g"
El resultado del comando anterior es que todas las letras a se cambian por la letra b. Así,
lo que se desea cambiar se debe escribir en a y lo que se desea que aparezca se debe colocar
en b. La letra g indica que el comando se aplica en todas las coincidencias. Si se desea que el
comando solo se aplique en la primera aparición de a en cada línea, se debe sustituir la letra g
por el número 1.
El comando grep permite buscar, dentro de los archivos, las líneas que concuerdan con
un patrón. Este comando imprime las líneas encontradas y entre las opciones más usadas se
encuentran:
-c En lugar de imprimir las líneas que coinciden, muestra el número de líneas que
coinciden. item -e PATRON permite especificar varios patrones de búsqueda o proteger
aquellos patrones de búsqueda que comienzan con el signo -. item -r busca recursivamente
dentro de todos los subdirectorios del directorio actual. item -v muestra las líneas que
no coinciden con el patrón buscado. item -i ignora la distinción entre mayúsculas y
minúsculas. item -n Numera las líneas en la salida. item -E permite usar expresiones
regulares. item -o muestra sólo la parte de la línea que coincide con el patrón. item -f
ARCHIVO extrae los patrones del archivo que se especifiquen item -H imprime el nombre
del archivo con cada coincidencia.
Un ejemplo del uso de este comando es el siguiente:

grep alumno notas.csv

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:

awk ’patrón{acción}’ archivo

El siguiente ejemplo aplica este comando para imprimir la primera columna de un archivo:

awk ’{print $1}’ archivo.csv

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:

r: se refiere a los permisos de lectura

w: se refiere a los permisos de escritura

x: se refiere a los permisos de ejecución

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

Esto nos permite ejecutar el archivo notas.sh.


El comando man permite visualizar el manual de un comando específico. La sintaxis de este
comando es:

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

1.3.9. Comando printf y especificadores de formato


El comando printf permite imprimir un dato y darle formato. Cuenta con muchas opciones,
entras las que se encuentran:

%s cadena de caracteres (string)

%d numero entero en base decimal

%f numero real con punto fijo

%x numero entero en base hexadecimal

Con base en lo anterior, el siguiente códigos es un ejemplo de implementación de este


comando:

printf "%s\n" print arguments on separate lines

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

Con este comando se obtiene:


23
45

1.4. Condicionales y bucles


1.4.1. Condicional if

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:

printf "Ingrese un numero: "


read number
if ["$number" -gt 4]
then
printf "%d es mayor que 4\n" "$number"
exit 1
else
printf "%d es menor que 4\n" "$number"
fi
Al implementar el comando read se guarda el numero ingresado por el usuario en la variable
number. El resultado del condicional es que si el numero ingresado es mayor que 4, se imprime
un mensaje que indica que el numero ingresado por el usuario es mayor que 4. De lo contrario,
se imprime un mensaje que indica que el numero ingresado es menor que 4.

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:

for(( n=<numero inicial>; n<=<numero final>; ++n ))


do
<acciones a ejecutar>
done

Un ejemplo en el que se aplica este comando es el siguiente:

for(( n=1; n<=10; ++n ))


do
echo "$i"
done

Con este comando se obtienen los números del 1 al 10.

1.4.3. Bucle while


Con el bucle while se crea una iteración condicional, de forma que se ejecuta una secuencia
de comandos continuamente mientras se cumpla la condición. La sintaxis es la siguiente:

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:

#Se pide un numero al usuario


printf "Enter a number"

# Se lee el numero ingresado por el usuario


read number

#Se imprime un string a la izquierda que corresponde al numero multiplicado


con la variable de iteración (que va desde 1 hasta 10) y a la derecha se
imprime el valor de la multiplicación

for (( n=1; n<=10; ++n ))


do
echo "$number x $n =" $(($number*$n))
done

1.5.2. Número entero entre 20 y 30


Este ejercicio consiste en escribir un script llamado punto2.sh que le pida al usuario que
introduzca un número entero entre 20 y 30. Si el usuario entra un número no válido, vuelve a
preguntar y repite la pregunta hasta que introduzca un número correcto. El código que soluciona
el problema es:

#Se pide un número al usuario


printf "Ingrese un numero entre 20 y 30 "

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

while [ "$n" -lt 20 -o "$n" -gt 30 ]


do
printf "Ingrese un nuevo numero entre 20 y 30 "
read number2

# El valor de la variable n se asigna al nuevo numero ingresado


n=$number2
done

1.5.3. Información astronómica


Con este ejercicio se busca obtener un script llamado punto3.sh que resuelva todos los
puntos que aparecen a continuación. En el archivo hipparcos.csv se encuentra información
astronómica de estrellas pertenecientes a la vía láctea y el ejercicio consiste en procesar este
archivo usando algunas de las herramientas de la terminal. El archivo se descarga de la dirección:
https://www.dropbox.com/s/nyumkp0kf0pdkfk/hipparcos.csv.tar.gz.

Descargar y descomprimir el archivo hipparcos.csv.tar.gz

¿Cuántas estrellas hay en el catálogo?

¿Cuánto pesa este catálogo en bytes?

¿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

Comprimir el catálogo parcial quedando con el nombre EstrellasG2V.tar.gz

La solución a cada uno de estos numerales se presenta a continuación:

# Descargar el archivo de internet


wget https://www.dropbox.com/s/nyumkp0kf0pdkfk/hipparcos.csv.tar.gz

#Descomprimir el archivo .gz


gunzip hipparcos.csv.tar.gz

#Descomprimir el archivo .tar


tar -xvf hipparcos.csv.tar

#Imprime el numero de estrellas


echo "El numero de estrellas es:"
grep -c "^[0-9]" hipparcos.csv

echo "El numero de bytes es:"


wc -c hipparcos.csv

echo "El numero de estrellas tipo G2V"

20
grep -c "G2V" hipparcos.csv

#Adiciona el encabezado al catalogo de estrellas G2V, creando el archivo .csv


grep StarID hipparcos.csv > EstrellasG2V.csv

#Adiciona las estrellas al catalogo (archivo csv)


grep G2V hipparcos.csv >> EstrellasG2V.csv

#Comprime el catalogo de estrellas G2V para obtener archivo .tar.gz


tar -zcvf EstrellasG2V.tar.gz EstrellasG2V.csv

1.5.4. 150 comics


El objetivo de este ejercicio es escribir un script llamado punto4.sh que descargue los
primeros 150 comics de [xkcd] (http://xkcd.com/). Para ello, se implementa el siguiente código

#Crea una nueva carpeta llamada comics


mkdir comics

#Cambia el directorio actual a comics


cd comics

#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.

for (( n=1; n<=150; ++n ))


do
wget $(curl -s http://xkcd.com/$n/ | grep URL | sed ’s/.*: //g’)
done

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

# Tema: Física. URL: http://arxiv.org/list/physics/new

#Se pregunta al usuario la palabra


printf "Add a word "

#Se obtiene la palabra ingresada


read palabra
p=$palabra

#Interfaz con la palabra arxiv. Si no se encuentra instalado el comando se


debe utilizar "sudo apt install figlet"
figlet arXiv

# iteración para crear las líneas punteadas de la interfaz


for ((x = 0; x < 40; x++))
do
printf %s =
done

# Espacio
printf "\n"

#imprime esta frase


echo Searching the arXiv "for" the new stuff

#Imprime la Url en la que se buscan los artículos


echo "http://arxiv.org/list/physics/new/"

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"

echo "Keyword: $p"

# iteración para crear las líneas punteadas de la interfaz


for ((x = 0; x < 40; x++))
do
printf %s =
done

# 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.

ar=$(curl -s http://arxiv.org/list/physics/new | grep $p | grep -c Title:)

#Imprime el numero de artículos que contienen la palabra

echo "Articles found: $ar"

#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.

curl -s http://arxiv.org/list/physics/new | grep $p | grep Title: |


sed ’s/.*> //g’| sed ’s/^/-/’

1.6.2. Planetas extrasolares


El archivo kepler.csv tiene información astronómica sobre la mayoría de planetas extrasolares
conocidos a la fecha con la especificación de las columnas en el archivo keplerREADME. El objetivo
de este ejercicio es escribir un script de bash llamado bruno.sh que haga lo siguiente:

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.

Para ello, se implementa el siguiente código:

#Punto a

#A la variable "planetas" se asigna el resultado de los comandos awk y wc.


Con awk se imprime unicamente la primera columna del documento. Al agregar
-F"," se indica que el delimitador de columnas es una coma. Además NR!=1
indica que no se imprima la primera fila del documento, ya que esta
corresponde a la especificación de las columnas. Con esto, se aplica el
comando wc -l para que se imprima el numero de filas del documento, que
corresponde al numero de planetas del catálogo.

planetas=$(awk -F"," ’NR!=1{print $1}’ kepler.csv | wc -l)

#Se imprime el mensaje con el numero de planetas del catálogo.


echo "El numero de planetas del catálogo es $planetas"

#Punto b

#Numero de planetas con masa menor a una centesima de la masa de Jupiter.


numero=$(awk -F"," ’$2<0.01{print $1}’ kepler.csv | wc -l)

#Imprime el mensaje con el numero de planetas


echo "El numero de planetas con masa menor a una centésima de la masa de
Júpiter es $numero"

#Imprime el Nombre de los planetas con una masa menor a una centesima de
la masa de Júpiter

awk -F"," ’$2<0.01{print $1}’ kepler.csv

#Punto c

#Primero se utiliza el comando awk para imprimir las columnas 6 y 1, en


este orden. Se agrega -F"," para indicar que el separador de columnas es
una coma. Además OFS="," permite que el resultado también esté separado
por comas. Posteriormente, se aplica el comando sort que organiza las
lineas de menor a mayor según la primera palabra o numero de cada linea.
Como se cambió el orden para que la columna 6, que indica el periodo,
estuviese de primera, el comando sort organiza las entradas del menor
periodo al mayor. Con el comando sed se imprime unicamente la primera

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.

per=$(awk -F"," ’{print $6,$1}’ OFS="," kepler.csv |


sort --field-separator="," --key=6 -n | sed -n 1p | awk -F"," ’{print $2}’)

#Imprime el mensaje con el nombre del planeta con el menor periodo.


echo "El planeta con el menor periodo orbital es: $per"

25
26
2

Clase 2: Python

Python es un lenguaje de scripting independiente de plataforma y orientado a objetos,


preparado para realizar cualquier tipo de programa, desde aplicaciones Windows a servidores
de red o incluso, páginas web. Es un lenguaje interpretado, lo que significa que no se necesita
compilar el código fuente para poder ejecutarlo, lo que ofrece ventajas como la rapidez de
desarrollo e inconvenientes como una menor velocidad. Así, Python es un lenguaje de alto nivel
y hay grandes ventajas de trabajar con estos idiomas. El más importante es que el código fuente
es más rápido para escribir y más fácil de leer. La interpretación y ejecución se producen de
forma alterna.
Python es usado por por al menos cientos de miles de investigadores en el mundo en áreas
como en secuencias de comandos en Internet, programación de sistemas, interfaces de usuario,
personalización de productos y en la programación numérica, entre otras áreas [15]. Se considera
que está entre los cuatro o cinco primeros lenguajes de programación más utilizados en el mundo
en la actualidad [15].
Como un lenguaje popular centrado en la reducción del tiempo de desarrollo, Python se
implementa en una amplia variedad de productos y roles [15]. Entre su base de usuarios actuales
están Google, YouTube, Industrial Light Magic, ESRI, el sistema de intercambio de archivos
BitTorrent, NASA Jet Propulsion Lab, el juego Eve Online y el Servicio Meteorológico Nacional
[15]. Entre otras aplicaciones, Python es útil en la administración de sistemas, desarrollo de
sitios web, celulares, secuencias de comandos de teléfono, educación para pruebas de hardware,
análisis de inversiones, ordenadores de juegos, y controles de naves [15].
En este capítulo se pretende presentar algunas generalidades de Python, que incluyen la
ejecución del programa, el manejo de variables, objetos, operaciones, expresiones lógicas, condicionales,
listas, tuplas, conjuntos, diccionarios, bucles, funciones, clases y módulos. Para empezar, se
describirá la ejecución del programa con la terminal para así empezar la descripción de cada
una de las tareas que puede realizar Python.

2.1. Ejecución, variables y objetos


2.1.1. Ejecución
Para ejecutar Python desde la consola, se escribe python y en seguida la consola debe
imprimir el signo >>>, lo cual indica que el intérprete de Python está listo para recibir instrucciones.
Después de cada instrucción, el intérprete muestra el resultado.
Adicionalmente, es posible ejecutar Python mediante un script con una extensión .py. Para
ejecutar el script se debe escribir en la terminal la palabra python, se deja un espacio y se
escribe el nombre del archivo que se desea ejecutar con python, como lo presenta el siguiente

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

La definición formal de objeto se proporciona a continuación.

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;

int Números enteros

float Números reales

bool Números booleanos

Los objetos escalares son objetos divisibles que tienen una estructura interna. Los tipos de
objetos escalares son:

str cadena de caracteres

Listas

Tuplas

Las listas y tuplas se explican más adelante en este capítulo.

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:

+ : Suma (20+5 tendría como resultado 25)

28
- : Resta (20-5 sería 15)

* : Multiplicación (20*5 daría 100)

/ : División (21/5 tendría como resultado 4.2)

% : Resto de la división, también conocido como "módulo"(21

** : Potencia (un número entero elevado a otro: 12**2 sería 144)

// : División sin decimales (21//5 sería 4, y 21.0//5.0 sería 4.0)

float(x) convierte la variable x a tipo float

int(x) convierte la variable x a tipo int

abs(x) valor absoluto de la variable x

2.2.2. Operaciones con Cadenas


Los operadores con cadenas son los siguientes:

s[x] Da el caracter numero x del objeto "s"tipo string

s+g Retorna l yuxtaposición entre los strings s y g

len(s) Genera el número de caracteres en s

str(x) Retorna la representación de string de la variable x

int(x) Retorna un valor entero contenido en el string x

float(x) Retorna un valor de tipo float contenido en el string x

2.3. Expresiones lógicas y condicionales


2.3.1. Expresiones lógicas
Python introduce las constantes True y False para representar los valores de verdadero y
falso respectivamente. Una expresión booleana o expresión lógica es una expresión que vale o
bien True o bien False. Los expresiones booleanas de comparación que provee Python son las
siguientes:

a == b a es igual a b

a != b a es distinto de b

a < b a es menor que b

a <= b a es menor o igual que b

a > b a es mayor que b

a >= b a es mayor o igual que 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:

x=raw_input("Ingrese numero mayor que cero ")


x=int(x)
if x>0:
print "Correcto"
else:
print "Incorrecto"

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.

2.4. Listas, tuplas, conjuntos y diccionarios


2.4.1. Listas
Las listas son conjuntos ordenados de elementos (números, cadenas, listas, etc). Las listas se
delimitan por corchetes ([ ]) y los elementos se separan por comas. Las listas contienen elementos
que no necesariamente son del mismo tipo. Para invocar un elemento de la lista se utiliza la
siguiente sintaxis:

lista[<numero del elemento>]

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:

lista.append(x) Agrega un ítem al final de la lista

lista.extend(L) Extiende la lista agregándole todos los ítem de la lista dada

lista.insert(i,x) Inserta un ítem en una posición dada.

lista.remove(x) Quita el primer ítem de la lista cuyo valor sea x.

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.

lista.count(x) Devuelve el numero de veces que x aparece en la lista.

lista.sort() Ordena los ítem de la lista.

lista.reverse() Invierte los elementos de la lista.

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:

t = 12345, 54321, ’hola’

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:

colores = {’azul’, ’rojo’, ’verde’, ’blanco’}

La otra forma es mediante el uso de la función set:

set(’abracadabra’)

Con este comando se obtiene el siguiente conjunto:

{’a’, ’r’, ’b’, ’c’, ’d’}

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 : .

punto = {’x’: 2, ’y’: 1, ’z’: 4}

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

2.5. Métodos de iteración y bucle for


2.5.1. bucle for
En general, un bucle es una estructura de control que repite un bloque de instrucciones, como
se mencionó en el capítulo 1. Un bucle for es un bucle que repite el bloque de instrucciones un
número predeterminado de veces. En Python, la sintaxis del bucle for es:

for variable in elemento iterable (lista, cadena, range, etc.):


cuerpo del bucle

En el siguiente ejemplo se imprimen los números de 0 a 10 cada uno en una línea.

for i in range(11):
print i

En este ejemplo, al aplicar el comando range(11) se indica que la iteración se da para


todos los números entero mayores o iguales que cero, pero estrictamente menores que once. Es
necesario adicionar indentación en el cuerpo del bucle.

2.5.2. Métodos de iteración


Cuando se realiza una iteración sobre diccionarios, se pueden obtener al mismo tiempo la
clave y su valor correspondiente usando el método iteritems():

caballeros = {’gallahad’: ’el puro’, ’robin’: ’el valiente’}


for k, v in caballeros.iteritems():
print k, v

Con el código anterior se obtiene el resultado:

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():

preguntas = [’nombre’, ’objetivo’, ’color favorito’]


respuestas = [’lancelot’, ’el santo grial’, ’azul’]
for p, r in zip(preguntas, respuestas):
print ’Cual es tu {0}? {1}.’.format(p, r)

Con el código anterior se obtiene:

Cual es tu nombre? lancelot.


Cual es tu objetivo? el santo grial.
Cual es tu color favorito? azul.

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

Al aplicar esta iteración se imprime en python:

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:

canasta = [’manzana’, ’naranja’, ’manzana’, ’pera’, ’naranja’, ’banana’]


for f in sorted(set(canasta)):
print f

Con el comando anterior se obtienen las frutas ordenadas en orden alfabético:

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:

def f(x): return x % 2 != 0 and x % 3 != 0


filter(f, range(2, 25))

En este ejemplo se obtienen los números primos menores que 25:

[5, 7, 11, 13, 17, 19, 23]

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:

def cubo(x): return x*x*x


map(cubo, range(1, 11))

Con este código se obtiene:

[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

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

La otra manera es usando la palabra from, la cual importará exclusivamente la función


que se le pasa como parámetro. El uso de la palabra from es recomendado ya que el tiempo
de procesamiento es menor. Así, si se requiere únicamente la función adicion del archivo
funciones.py, se aplica la sintaxis:

from funciones import adicion

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:

#Importar el modulo math para calcular el logaritmo en base 10


import math

#Se ingresan los numeros del usuario y se guardan como un string en la variable
lista

lista=raw_input("Ingrese dos numeros enteros ")

#Separa los numeros ingresados. Se guardan los numeros ingresados por el


usuario en las variables a y b

a,b= lista.split()

#Cambia las variables a y b a tipo entero


a=int(a)
b=int(b)

#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)

#Imprime la division entre a y b


print "a/b: %d"%(a/b)

#Imprime el residuo de la division entre a y b


print "Residuo a/b: %d"%(a%b)

#Imprime el logaritmo en base 10 de a


print "Logaritmo en base 10 de a: %d"%(math.log10(a))

#Imprime el resultado de a elevado a la b


print "a^b: %d"%(a**b)

2.7.2. Zodiaco chino


Este ejercicio consiste en que el zodiaco chino asigna animales en un ciclo de 12 años. La
tabla con los años y animales de este calendario se presentan en el cuadro 4.1. El patrón se repite
con el año 2012 siendo otro año del dragón, y 1999 es otro año de la liebre. Se desea escribir un

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:

a=raw_input("Add a year ") #Se guarda el numero como un string en la variable a

a=int(a) #Se cambia el tipo de la variable a para obtener un entero

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

2.7.3. Cifrado de César


Uno de los primeros ejemplos conocidos de encriptación lo utilizó Julio César. César necesitaba
proporcionar instrucciones por escrito a sus generales, pero él no quería que sus enemigos
supieran sus planes si el mensaje se deslizara en sus manos. Como resultado, desarrolló lo
que más tarde se conoció como el cifrado de César. La idea detrás de este sistema de cifrado es
simple. Cada letra en el mensaje original se desplaza en 3 lugares. Como resultado, se convierte
en una D, B se convierte en E, C se convierte en F, D se convierte en G, etc. Las tres últimas
letras del alfabeto se envuelven alrededor del comienzo: X se convierte en A, Y y Z se convierte
en B se convierte en C.
Este ejercicio consiste en crear un programa llamado cesar.py que implemente el sistema de
cifrado César. Se debe permitir que el usuario proporcione el mensaje y la cantidad de lugares
que se desplazan las letras, y luego mostrar el mensaje cambiado. Además, el programa debe
codificar tanto letras mayúsculas y minúsculas. Se debe omitir la ñ, ch, ll. El programa también
debe apoyar valores de desplazamiento negativo de manera que se puede utilizar tanto para
codificar y decodificar mensajes.
El siguiente código permite solucionar el ejercicio:

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’]

# Lista que contiene las letras en mayúsculas


mayusculas=[’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’]

# Diccionario vacio reservado para letras minusculas


letrasMin={}

# Diccionario vacio reservado para letras mayusculas


letrasMay={}

#Al diccionario de letras minusculas se asignan letras de la lista minusculas


con un valor entre 0 y 25
#Al diccionario de letras mayusculas se asignan letras de la lista mayusculas
con un valor entre 0 y 25
for i in range(26): #Se escojen numeros entre 0 y 25
letrasMin[minusculas[i]]= i
letrasMay[mayusculas[i]]= i

# Se pregunta al usuario palabra y espacio que se desea correr y se guarda


la entrada como un string en la variable "entrada"

entrada=raw_input("Add a word and the number of spaces between letters: ")

# Se separa el string recibido de entrada para obtener la palabra y el


numero de espacios en dos variables

palabra,esp= entrada.split()

# La variable del espacio se cambia a tipo entero


esp=int(esp)

# Se crea una variable para guardar las letras que forman la palabra en el
lenguaje encriptado
s=’’

contadorl=0 # contador del numero de ocurrencias de la letra l


contadorch=0 # contador del numero de ocurrencias de "ch"

for letter in palabra:

if letter==’l’ or letter==’L’: #Si se encuentra una letra l o L


contadorch=0 #El contadorch vuelve a cero

if contadorl==1: # Si contadorl era igual a 1, se ha repetido

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

elif letter==’c’ or letter==’C’: #Si se encuentra una letra c o C


contadorl=0 # El contadorl vuelve a cero
if contadorch==0:
contadorch += 1 # Se suma uno al contador

elif letter==’h’ or letter==’H’: #Si se encuentra una letra h o H


if contadorch==1: #Si el contadorch era uno es porque justo antes
de la letra h existe letra c
s=’’ # A la variable s se le asigna un string vacio
print "Caracter ch invalido" #Se imprime el mensaje
break # Se interrumpe la iteracion

else: # Si se encuentra cualquier otra letra


contadorch=0 #El contadorch vuelve a cero
contadorl=0 #El contadorl vuelve a cero

if letter in letrasMin: #Si la letra es minuscula


nletra=letrasMin[letter]+esp #Se crea una variable nletra
que almacena el nuevo numero de
la letra con el espacio sumado

if nletra>=len(minusculas): #Si el valor de nletra es mayor


que la longitud de la lista minusculas
nletra=nletra-len(minusculas) #Se resta la longitud de la lista
minusculas para que nletra indique la
posicion de la letra con el espacio,
pero sin pasarse de la longitud del arreglo

s += minusculas[nletra] #Se adicionan las letras a la variable s para formar


la nueva palabra

elif letter in letrasMay: #Si la letra es mayuscula


nletra=letrasMay[letter]+esp #Se crea una variable nletra que almacena el nuevo
numero de la letra con el espacio sumado

if nletra>=len(mayusculas):#Si el valor de nletra es mayor que la longitud de


la lista mayusculas

nletra=nletra-len(mayusculas) #Se resta la longitud de la lista mayusculas


para que nletra indique la posicion de la letra
con el espacio, pero sin pasarse de la longitud

40
del arreglo

s += mayusculas[nletra] #Se adicionan las letras a la variable s para formar


la nueva palabra
else: #Si la letra no se encuentra ni en la lista de letras minusculas ni en
print "Caracter invalido" #Imprime el mensaje
s=’’ # Se asigna a la variable s un string vacio
break # Se interrumpe la iteracion

if s!=’’: # Si la variable s no es un string vacio


print s #Imprime la palabra

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:

p=raw_input("Add your password: ") #Se guarda la palabra ingresada en la variable p

def contra(password): # Funcion con un string como parametro de entrada


contador=0 # Contador de caracteres
contMinusculas=0 # Contador de minusculas
contMayusculas=0 # Contador de mayusculas
contNumeros=0 # Contador de numeros
for letter in password:
contador += 1 #Se cuenta el numero de caracteres
if letter.islower():
contMinusculas += 1 #Si la letra es minuscula, se suma uno al
contador de minusculas
if letter.isupper():
contMayusculas += 1 #Si la letra es mayuscula, se suma uno al
contador de mayusculas
if letter.isdigit():
contNumeros += 1 # Si el caracter es un numero, se suma uno al
contador de numeros

if contador>=8 and contMinusculas>0 and contMayusculas>0 and contNumeros>0:


return True # Se devuelve True si el string contiene 8 o mas caracteres
con al menos una minuscula, una mayuscula y un numero

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"

2.7.5. Número perfecto


Un número entero, n, se dice que es perfecto cuando la suma de todos los divisores es igual a
n. Por ejemplo, 28 es un número perfecto porque sus divisores son 1, 2, 4, 7 y 14, y 1 + 2 + 4 +
7 + 14 = 28. Este ejercicio consiste en escribir una función que determina si un número entero
positivo es perfecto. La función se llevará a un parámetro. Si dicho parámetro es un número
perfecto, entonces la función devolverá verdadero. En caso contrario devolverá falso. Además, se
debe escribir un programa principal llamado perfecto.py que utiliza su función que identifique
y muestre todos los números perfectos entre 1 y 10.000.
El código que permite solucionar el problema es:

def perfecto(n): # Funcion con parametro de entrada entero


suma=0 #variable que representa la suma de divisores
for i in range(1,n): # Se realiza iteracion entre 1 y el parametro de entrada
if n%i==0:
suma += i #Si el residuo de la division del parametro de entrada entre
un numero es igual a cero, se suma a la variable "suma" el
valor de el numero, que seria un divisor
if suma==n:
return True #Si la suma de divisores es igual al parametro de entrada,
se devuelve True

else:
return False #De lo contrario, se devuelve False

for i in range(1,10001): #Se realiza iteracion entre 1 y 10000


if perfecto(i)==True: #Se aplica la funcion "perfecto" para saber si el numero
es perfecto
print i # Si la funcion "perfecto" devuelve True, se imprime el numero
correspondiente

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:

import string #Se importa modulo string


letras=string.ascii_letters #Se genera una lista con todas las letras
dic={} #Se crea un diccionario vacio

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

cont=0 #Se crea una variable para guardar el puntaje

entrada=raw_input("Add a word: ") #Se guarda la entrada en una variable

entrada=entrada.lower() #Se cambian los caracteres de la entrada a minusculas

for letter in letras: #Se itera sobre todas la letras en la lista


if letter==’a’ or letter==’e’ or letter==’i’ or letter==’l’ or letter==’n’ or
letter==’o’ or letter==’r’ or letter==’s’ or letter==’t’ or letter==’u’:
dic[letter]=1 #Se asigna el valor 1 a las letras correspondientes en el
diccionario
elif letter==’d’ or letter==’g’:
dic[letter]=2 #Se asigna el valor 2 a las letras correspondientes en el
diccionario
elif letter==’b’ or letter==’c’ or letter==’m’ or letter==’p’:
dic[letter]=3 #Se asigna el valor 3 a las letras correspondientes en el
diccionario
elif letter==’f’ or letter==’h’ or letter==’v’ or letter==’w’ or letter==’y’:
dic[letter]=4 #Se asigna el valor 4 a las letras correspondientes en el
diccionario
elif letter==’k’:
dic[letter]=5 #Se asigna el valor 5 a las letras correspondientes en el
diccionario
elif letter==’j’ or letter==’x’:
dic[letter]=8 #Se asigna el valor 8 a las letras correspondientes en el
diccionario
elif letter==’q’ or letter==’z’:
dic[letter]=10 #Se asigna el valor 10 a las letras correspondientes en el
diccionario

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

p=raw_input("Ingrese un elemento: ") # Se guarda el elemento de entrada en la


variable p

p=p.lower() # Se cambian las letras a minusculas

esElemento=False # booleano que indica que el elemento no se encuentra en la lista


de elementos

for i in range(len(elementos)): #Se itera sobre toda la lista elementos


if p==elementos[i]:
esElemento=True #Si la entrada coincide con un elemento, la variable
esElemento cambia a True

44
sec=[p] # Lista para guardar las palabras que se imprimiran

def secuencia(palabra): # Funcion que retorna la secuencia. COmo parametro entra


el elemento inicial
letraf=palabra[len(palabra)-1] #Letra final del elemento
cont=0 # Contador para indicar si la secuencia acaba. Vale cero si la
secuencia acaba y uno de lo contrario

for i in range(len(palabra)): # Iteracion sobre la cantidad de letras del


elemento
if letraf in ’aeiou’:
letraf=palabra[len(palabra)-2-i] # Si la letra final es vocal se busca
la letra anterior que no lo sea

for i in range(len(elementos)): #iteracion sobre todos los elementos


el=elementos[i] # variable que guarda el nombre de los elementos
esta=False # booleano que indica si la letra final del elemento es
la letra inicial de otro elemento
for j in range(len(sec)):
if sec[j]==el:
esta=True #Si la lista "sec", que contiene los elementos que se imprimiran,
contiene el elemento, el booleano "esta" cambia a True

if letraf==el[0] and esta==False: #Se aplica si la letra es la letra inicial de


otro elemento y si este elemento no se
encuentra en la lista
cont+=1 #indica que la secuencia no acaba
sec.append(el) #Agrega el elemento a la lista de los elementos
que se imprimiran
letraf=el[len(el)-1] #la letra final nueva es la letra final
del nuevo elemento
return str(palabra) + ’ ’ + secuencia(el) #Retorna los elementos
separados por espacio mediante funcion recursiva

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

if esElemento==True: #SI el elemento existe en la lista


print secuencia(p) #Aplica la función recursiva

45
else:
print "Elemento invalido" #De lo contrario imprime el mensaje

46
3

Clase 3 y clase 4: IPython

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:

Resaltado del texto utilizando colores y destacando los errores.

Posibilidad de utilizar el tabulador para autocompletar la escritura de líneas.

Acceso a un histórico de entradas y de salidas y numerosas funciones propias denominadas


funciones mágicas que aumentan la potencia de IPython.

Apoyo para la visualización interactiva de datos.

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:

SciPy: agrupa funciones relevantes para el cálculo numérico.

NumPy: proporciona funciones específicas para el cálculo numérico vectorial y matricial.

SymPy: agrupa las funciones necesarias para el cálculo simbólico.

Matplotlib: contiene herramientas para la elaboración de gráficos 2D y 3D (semejante a


la generación de gráficos en MATLAB).

Mayavi: librería específica para la creación de gráficos 3D.

Pandas: librería especializada en la manipulación y análisis de datos.

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.

Para importar numpy se implementa el siguiente código, que en prácticamente toda la


literatura sobre numpy se utiliza:

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

Para ilustrar la sintaxis anterior se presenta un ejemplo:

>>> 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:

>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )


>>> print c
>>> d = np.array( [ [1,2], [3,4] ], dtype=float)
>>> print d
[[ 1.+0.j 2.+0.j]
[ 3.+0.j 4.+0.j]]
[[ 1. 2.]
[ 3. 4.]]

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:

>>> print np.zeros(3,4)


[[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]

>>> print np.ones( (2,3,4), dtype=np.int16 )


[[[1 1 1 1]
[1 1 1 1]
[1 1 1 1]]

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:

>>> a=np.linspace( 0, 2, 9 ) #lim_inf, lim_sup, divisiones


>>> print a
[ 0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]

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

3.1.2. Operaciones básicas


Las operaciones básicas permitidas en Ipython son similares a las operaciones que se realizan
en Python. Adicional a esto, se pueden realizar operaciones entre arreglos de numpy, como se
observa en el siguiente ejemplo:

>>> a= np.array( [20,30,40,50] )


>>> print a
>>> b = np.arange( 4 )
>>> print b
>>> print a-b #operacion elemento a elemento, misma dimension +,*,/
>>> print b**2 #Potenciación
>>> print 10*np.sin(a) #funciones trigonométrica
>>> print a<35 #operaciones booleanas
[20 30 40 50]
[0 1 2 3]
[20 29 38 47]
[0 1 4 9]
[ 9.12945251 -9.88031624 7.4511316 -2.62374854]
[ True True False False]

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 :

>>> A = np.array( [[1,1],[0,1]] )


>>> B = np.array( [[2,0],[3,4]] )
>>> print np.dot(A, B)
[[5 4]
[3 4]]

Numpy también permite crear arreglos de ceros y unos al implementar np.zeros() y


np.ones(), respectivamente. Al interior de los paréntesis se debe especificar el número de
filas y columnas del arreglo, separados por comas. Si se desea especificar el tipo de elementos
que componen el arrego, se puede escribir np.zeros((<filas>,< columnas>), dtype=int).
Además, se pueden generar números aleatorios con numpy, implementando el comando np.random.
En el paquete random de NumPy se encuentran varias funciones para generar datos aleatorios:

rand(d0, d1, ..., dn) Valores aleatorios en un rango dado.

randn(d0, d1, ..., dn) Retorna elementos de una distribución estándar normal.

randint(low[, high, size]) Retorna enteros aleatorios desde el numero <low>hasta


el numero <high>, excluyendo al límite superior.

random_integers(low[, high, size]) Retorna enteros aleatorios entre los números


<low>y <high>, incluyendo a ambos límites.

random_sample([size]) Retorna elementos tipo float aleatorios en el intervalo [0.0,


1.0).

random([size]) Retorna elementos tipo float aleatorios en el intervalo [0.0, 1.0).

ranf([size]) Retorna elementos tipo float aleatorios en el intervalo [0.0, 1.0).

sample([size]) Retorna elementos tipo float aleatorios en el intervalo [0.0, 1.0).

choice(a[, size, replace, p]) Genera un elemento aleatorio de un arreglo específico.

bytes(length) Retorna bytes aleatorios.

Ipython también permite simplificar operaciones que modifican a la misma variable:

>>> a = np.ones((2,3), dtype=int)


>>> a *= 3
>>> print a
[[3 3 3]
[3 3 3]]

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:

print a.sum() suma los elementos de todo el arreglo a

51
print a.min() encuentra el mínimo del arreglo a

print a.max() encuentra el máximo del arreglo a

print a.mean() encuentra la media del arreglo a

print a.std() encuentra la desviación estándar 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]]

3.1.3. Indexación, cortar e iteración


Para obtener el elemento que se encuentra en una posición de un arreglo en IpyPhon, se
implementan las siguientes funciones:

print a[2] a[i] muestra el elemento que se encuentra en el indice i del arreglo a

print a[i:j] muestra los elementos del i al j-1 del arreglo a

print a[i:j:k] muestra los elementos del i al j cada k espacios del arreglo a

print a[ : :-1] muestra el arreglo a invertido

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

>>> a = np.arange(12)**2 # Los primeros 12 numeros al cuadrado


>>> print a
>>> i = np.array( [ 1,1,3,8,5 ] ) # un arreglo de indices
>>> print a[i]
>>> j = np.array( [ [ 3, 4], [ 9, 7 ] ] ) #un arreglo de indices bidimencional
>>> print a[j]
[ 0 1 4 9 16 25 36 49 64 81 100 121]
[ 1 1 9 64 25]
[[ 9 16]
[81 49]]

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

El código que permite resolver el ejercicio es:

%pylab inline

#Se obtiene la imagen como arreglo


homero=imread("homero.png")

#Se asigna el tamaño de cada dimensión a las variables x,y,z


x,y,z=homero.shape

#Se genera un arreglo bidimensional con el tamaño del arreglo de la imagen


x,y = np.ogrid[ 0:x:1, 0:y:1 ]

#Se genera un arreglo de índices


i= sqrt((x-70)**2 + (y-110)**2 ) > 60

#A cada posición por fuera del círculo se asigna un valor cero


homero[i]=0

#Se imprime la imagen


plt.imshow(homero)

#Se guarda la imagen en formato png


plt.savefig("homero2.png")

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.

3.3.1. Herramientas de visualización 2D


Para realizar gráficas se implementa el principal submódulo para dibujar, que corresponde a
pyplot. Matplotlib.pyplot es una colección de funciones estilo comando que hacen que matplotlib
trabaje como matlab. Cada función de pyplot hace algún cambio a una figura, como crear una
figura, crear un área para graficar en una figura, graficar algunas lineas y decora la gráfica con
etiquetas, entre otras funciones. Para utilizar este submódulo, se debe agregar en el cuaderno
de IPython el siguiente código:

import matplotlib.pyplot as plt

Alternamente, se puede agregar la siguiente línea y el resultado será el mismo:

%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

3.3.2. Gráficas de dispersión


Un tipo de gráficas que se pueden generar con matplotlib son las gráficas de dispersión. Para
ello, se deben generar dos arreglos del mismo tamaño que contengan las coordenadas en el eje x
y en el eje y de los puntos que se desean graficar. En el siguiente ejemplo se genera una función
cuadrática con diez puntos implementando un gráfico de dispersión:

x = np. linspace(0, 10, 10)


y = np.power(x, 2)
plt.scatter(x, y)
plt.show()

El resultado obtenido se visualiza en la figura 3.3


Con base en el código anterior, la función plt.scatter(<arreglo x>, <arreglo y>) permite
generar gráficos de dispersión. Adicionalmente, la función de numpy np.power(x, 2) es la que
genera los números del arreglo x elevados al cuadrado.

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:

x = np. linspace(0, 10, 10)


y = np.power(x, 2)
plt.plot(x, y)
plt.show()

La figura 3.4 presenta el resultado del código anterior.

3.3.4. Color y estilos de línea


Con pl.plot() también es posible generar gráficas con distintos colores, con distintos estilos
de línea, con títulos, con rótulos en los ejes y con leyendas. Para cambiar el color de la línea
de una gráfica, se debe especificar al interior de la función plt.plot() el color que se desea
visualizar. Para ello, existen las siguientes letras que representan los colores básicos:

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 discontinua

:, Línea punteada

-., Línea punteada discontinua

None, Ninguna línea

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:

x = np.linspace(0, 10, 50)


y1 = np.power(x, 2)
y2 = np.power(x, 3)
plt.plot(x, y1, ’b--’)
plt.plot(x, y2,’go’)
plt.show()

La gráfica resultante se presenta en la figura 3.5


Como se observa en el ejemplo, es posible simplificar la sintaxis del estilo de línea y del color,
especificando los símbolos entre comillas.

3.3.5. Títulos, rótulos en ejes y leyendas


Para agregar un título en una gráfica generada con matplotlib, se debe utilizar la función
plt.title() y al interior de los paréntesis se debe escribir el título de la gráfica entre comillas.
Para adicionar rótulos a los ejes, se implementa la función plt.xlabel() o plt.ylabel(). Con

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:

x = np. linspace(0, 10, 50)


y1 = np.power(x, 2)
y2 = np.power(x, 3)
plt . plot(x, y1, ’b--’, label=’$x^2$’)
plt . plot(x, y2, ’go’ , label=’$x^3$’)
plt . xlim((1, 5))
plt . ylim((0, 30))
plt . xlabel( ’my x label’)
plt . ylabel( ’my y label ’)
plt . title ( ’ plot title , including $\Omega$’)
plt . legend()
plt.savefig(’label.png’, format=’png’)

El resultado del código anterior se presenta en la figura 3.6.


Con la función plt.savefig(’label.png’, format=’png’) se guarda la imagen en el
directorio que contiene el cuaderno de IPython en formato png.

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()

El histograma se visualiza en la figura 3.7.

Figura 3.7: Histograma

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:

samp1 = np.random.normal(loc=0., scale=1., size=100)


samp2 = np.random.normal(loc=1., scale=2., size=100)
samp3 = np.random.normal(loc=0.3, scale=1.2, size=100)
f, ax = plt.subplots(1, 1, figsize=(5,4))
ax.boxplot((samp1, samp2, samp3))
ax.set_xticklabels([’sample 1’, ’sample 2’, ’sample 3’])

El diagrama resultante se presenta en la figura 3.8.

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’)

El resultado del código anterior se visualiza en la figura 3.9.


Otro tipo de gráficos que se puede generar con matplotlib son los Wire-frame plots. Estos
diagramas generan lineas entre puntos cercanos y se pueden visualizar por medio de la función
plot_wireframe(). En el siguiente ejemplo se genera una esfera por medio de las ecuaciones
paramétricas en tres dimensiones:

from mpl_toolkits.mplot3d import axes3d


u = np.linspace(0, np.pi, 30)
v = np.linspace(0, 2 * np.pi, 30)
x = np.outer(np.sin(u), np.sin(v))
y = np.outer(np.sin(u), np.cos(v))
z = np.outer(np.cos(u), np.ones_like(v))
fig = plt.figure()
ax = plt.axes(projection=’3d’)
ax.plot_wireframe(x, y, z)

La esfera resultante se visualiza en la figura 3.10.


Adicionalmente, con la librería de matplotlib se pueden generar gráficas de tipo streamplot().
Un streamplot() permite graficar campos vectoriales por medio de líneas de corriente. Matemáticamente

61
Figura 3.9: Gráfica de arreglo aleatorio con imshow

Figura 3.10: Gráfica con plot_wireframe() de una esfera parametrizada

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()

El resultado del código anterior se presenta en la figura 3.11.

Figura 3.11: Gráfica de campo vectorial

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.

Figura 3.12: Gráfica de contornos

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

botones de selección y casillas de verificación

áreas de visualización

En el siguiente ejemplo se implementan algunos widgets con el fin de modificar la amplitud,


la frecuencia y el color de una función sinusoidal.

# 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

# Configurar los deslizadores


axcolor = ’lightgoldenrodyellow’ #color
axfreq = axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor) #[x,y,largo,ancho]
axamp = axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)

sfreq = Slider(axfreq, ’Freq’, 0.1, 30.0, valinit=f0) #definen valor maximo


y minimo del slider
samp = Slider(axamp, ’Amp’, 0.1, 10.0, valinit=a0)

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

sfreq.on_changed(update)#llama la funcion mientras se esta cambiando (modo changed)


samp.on_changed(update)
#configura el boton de reset
resetax = axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, ’Reset’, color=axcolor, hovercolor=’0.975’)
def reset(event):
sfreq.reset()
samp.reset()

button.on_clicked(reset)

#configura el boton del color de la grafica


rax = axes([0.025, 0.5, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, (’red’, ’blue’, ’green’), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()

radio.on_clicked(colorfunc)
show()

El resultado del código anterior se visualiza en la figura 3.13.

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:

from matplotlib import animation


%matplotlib osx

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’)

# Los datos se pueden encontrar aquí:


https://raw.githubusercontent.com/ComputoCienciasUniandes/
MetodosComputacionalesDatos/master/hands_on/coreo.csv
choreography=genfromtxt("coreo.csv",delimiter=",")
x1=choreography[:,1]
x2=choreography[:,3]
x3=choreography[:,5]
y1=choreography[:,2]
y2=choreography[:,4]
y3=choreography[:,6]
t=choreography[:,0]

completetrayectory = ax.plot(x1,y1,"--k",alpha=0.4)
title("Gravitational Choreography\n")

# The following three plot the trails


trayectory1, = ax.plot([], [], ’-’, lw=2,label="mars")
trayectory2, = ax.plot([], [], ’-’, lw=2,label="earth")
trayectory3, = ax.plot([], [], ’-’, lw=2,label="venus")

# The following three plot the current positions


planet1, = ax.plot([], [], ’ok’, lw=2)
planet2, = ax.plot([], [], ’ok’, lw=2)
planet3, = ax.plot([], [], ’ok’, lw=2)

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

animacion = animation.FuncAnimation(fig, animate, np.arange(1, len(t)),


interval=10, blit=False)
#animacion.save(’grav_choreography.mp4’, fps=15)
show()

Si se desea guardar la animación, se puede omitir el numeral de la penúltima línea en el


código anterior. La interfaz del resultado se visualiza en la figura 3.14.
Ahora se presenta el código que genera una animación de una placa metálica que aumenta su
temperatura. Para simular esto, los colores van cambiando en la placa hacia colores rojos, lo que
indica una mayor temperatura. Para llevar a cabo esta animación se pueden implementar dos
funciones: animate.FuncAnimation() y animation.ArtistAnimation(). En el primer caso,
es necesario crear una función que lleve a cabo las transformaciones para que se genere la
animación. Posteriormente, al interior de los paréntesis de animate.FuncAnimation() se debe
llamar la función creada previamente:

# Ahora usando animate.FuncAnimation


%matplotlib osx
side=20
from matplotlib import animation
# Placa fría con borde caliente
placa=zeros((side,side))
placa[:,0]=1
placa[:,-1]=1
placa[0,:]=1
placa[-1,:]=1

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,

animacion = animation.FuncAnimation(fig, animate, arange(1,100),interval=20, blit=False)


show()

Con este código, se genera la interfaz presentada en la figura 3.15.

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:

from matplotlib import animation


%matplotlib osx
rcdefaults()
side=30
# Placa fría con borde caliente
placa=zeros((side,side))
placa[:,0]=1
placa[:,-1]=1
placa[0,:]=1
placa[-1,:]=1
fig = figure()
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
angs=linspace(0.,2*3.14,100)
def f(x, y):
return sin(x) + cos(y)
x = linspace(0, 2 * pi, 120)
y = linspace(0, 2 * pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the

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)

Figura 3.16: Interfaz con función animation.ArtistAnimation de la placa metálica

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])

ani = animation.ArtistAnimation(fig, ims, interval=50, blit=False)


show(fig)

La interfaz de la animación generada con este código se presenta en la figura 3.17.

Figura 3.17: Interfaz con función animation.ArtistAnimation de la placa metálica

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:

##Importa el modulo math para utilizar operaciones matemáticas


import math

#Se generan 100 numeros entre 0 y 2, que son los valores en el eje x
x=linspace(0,2,100)

#Se asignan los valores en el eje y


y1=((sin(x-2))**2)*(exp(-(x**2)))
y2=cos(x)

#Se asigna el titulo del eje x


plt.xlabel(’x’)

#Se asigna el titulo del eje y


plt.ylabel(’F(x)’)

#Se grafica
plt.plot(x,y1,’b’,label=’$sin(x-2)^2 e^{-x^2}$’)
plt.plot(x,y2,’r’,label=’$cos(x)$’)

#Se adiciona titulo a la grafica


plt.title ( ’F(x) vs x’)

#Se adiciona la leyenda a la grafica


plt.legend()

#Se guarda la figura en el archivo punto1.pdf


plt.savefig( ’punto1.pdf’)

Con este código se obtiene la gráfica que se visualiza en la figura 3.18.

3.4.2. Curvas de Lissajous


Este ejercicio consiste en realizar un panel de 3x2 donde se grafiquen diferentes curvas de
Lissajous. Debe especificarse los diferentes parámetros que se cambian dentro de cada figura.
Además, incluir un título en común para las 6 gráficas y leyenda para cada una de estas.
El código que permite solucionar el ejercicio es:

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

Figura 3.18: Gráfica de dos funciones

#Se generan 100 numeros entre 0 y 2pi


t=linspace(0,2*pi,100)

#tamaño de la figura
plt.figure(figsize=(10,10))

#Título general
plt.suptitle(’Curvas de lissajous’, fontsize=14)

#Angulos, variable indenpendiente de las funciones x y y


angs=linspace(0,2*pi,100)

#Funcion x
x= sin(t)

for i in range(6):

##define filas, columnas, posicion de la figura


subplot(3,2,i+1)

#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()

Con este código se obtiene la gráfica mostrada en la figura 3.19.

3.4.3. Caminata aleatoria en 3D


Este ejercicio consiste en realizar una animación de una caminata aleatoria en 3D, para un
sistema de 100 partículas. El código que genera la caminata aleatoria es:

#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)

# Arreglo para coordenada de todas las partículas en el eje X,Y y Z


x=[0]*100
y=[0]*100
z=[0]*100

#Primera posición
par1,=ax.plot(x,y,z,"ok",ms=5)

#Arreglo para guardar las posiciones como imagenes


ims=[]
ims.append([par1]) #Se adiciona la posición inicial

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

ani = animation.ArtistAnimation(fig, ims, interval=100, blit=False) #Se genera la


animación

show(fig)

La interfaz de la animación obtenida se presenta en la siguiente figura 3.20.

77
Figura 3.20: Interfaz de la animación de caminata aleatoria en 3D

78
4

C: bases, escritura, variables,


aritmética y bucles

C es un lenguaje de programación originalmente desarrollado por Dennis M. Ritchie entre


1969 y 1972 en los Laboratorios Bell. Es un lenguaje orientado a la implementación de Sistemas
Operativos, concretamente Unix. Se trata de un lenguaje de tipos de datos estáticos, débilmente
tipificado, de medio nivel pero con muchas características de bajo nivel. Dispone de las estructuras
típicas de los lenguajes de alto nivel pero, a su vez, dispone de construcciones del lenguaje que
permiten un control a muy bajo nivel. Los compiladores suelen ofrecer extensiones al lenguaje
que posibilitan mezclar código en ensamblador con código C o acceder directamente a memoria
o dispositivos periféricos.

4.1. Estructura, edición, compilación y ejecución


4.1.1. Estructura y edición
Para crear un script que contenga los comandos de C se debe crear un script con un editor
de texto con la extensión .c. Al interior del script se debe escribir la estructura básica que
permite la edición en C. Esta estructura es:

#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

\f Representa un carácter de avance

\t Representa tabulación horizontal

\v Representa tabulación horizontal

\a Inserta un carácter alerta

\? Inserta un símbolo de pregunta

\’ Inserta una doblde comilla

\" Inserta una comilla

\\ Inserta un backslash

4.1.2. Compilación y ejecución


Para compilar un script que contiene la extensión .c se debe escribir en la terminal cc y en
seguida el nombre del archivo que contiene el lenguaje C. Por ejemplo, si se desea compilar el
archivo programacion.c se debe escribir en la terminal cc programacion.c.
Para ejecutar el script se debe escribir en la terminal ./a.out. Con este comando, se imprime
en la terminal el resultado de ejecutar el código contenido en el script. Por ejemplo, si el código
contenido en el script es:

/*primer codigo en c*/


#include <stdio.h>
int main(void)
{
printf("Hola mundo\n");
return 0;
}

Al ejecutar el script aparece en la terminal el mensaje Hola mundo.

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

4.2.1. Variables tipo int


Los tipos de variables más comunes en C de tipo entero son:

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

unsigned short int entero corto con valores entre 0 y 65535

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;

printf("signed char %d, short int %d, int %d \n",a,b,c);


printf("long int %ld, long long int %lld \n",d,e);
return 0;
}

El mensaje obtenido en la terminal al ejecutar este código es:

signed char 127, short int 32767, int 2147438647


long int 2147438647, long long int 9223372036854775806

81
4.2.2. Variables de tipo float
Los tipos de variables de tipo float más comunes en C son:

float Rango de valores: ±3,4 × 1038


double Rango de valores: ±1,7 × 10308
long double Rango de valores: ±1,19 × 104932

Con el siguiente código se generan estos tres tipos de variables float en un mensaje:

/*tipo de varibles flotantes*/


#include <stdio.h>
int main()
{
float a=2.5878f;
double b=123E9;
long double c=a/b;

printf("float a %.2f, double b %lf, long doble %Le \n",a,b,c);


return 0;
}

El mensaje obtenido con el código es:

float a 2.59, double b 123000000000.000000, long doble 2.103902e-11

4.2.3. Variables tipo carácter


Para generar variables tipo carácter se pueden implementar tres métodos. El primer método
es escribir la palabra char con el símbolo asterisco (*) al comienzo de la variable que contiene el
conjunto de caracteres. La segunda opción es utilizar wchar_t y en seguida el símbolo asterisco
con el nombre de la cadena de caracteres, que en este caso es más extensa que al implementar
únicamente char. Por último, se puede escribir la palabra char y en seguida el nombre de la
variable que contiene la cadena de caracteres. Sin dejar espacio, se escribe después de la variable
el numero máximo de bytes entre corchetes ([]) que puede contener la cadena de caracteres.
En el siguiente ejemplo se implementan los tres métodos anteriores:

#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

4.3. Leyendo variables del usuario


Para leer variables del usuario es necesario implementar scanf("%<tipo de variable>",&<nombre de la
En el tipo de variable se escribe únicamente la letra correspondiente. Si la variable que se lea
del usuario es de tipo entero, se escribe la letra d. Si la variable es de tipo float, se escribe la
letra f.
Si se desea obtener una variable de tipo short int se escriben las letras hd. Si se desea
obtener una variable de tipo long int se agregan las letras ld. En el siguiente ejemplo se guarda
el diámetro ingresado por el usuario en una variable de tipo float con nombre diametro y como
resultado se imprime el radio, el perímetro y el área de un círculo con ese diámetro:

/*calcula el area y perimetro de un


circulo, r dado por el usuario */

#include <stdio.h>
#define PI 3.14159265f
int main(void)
{
float diametro, radio, perimetro,area;

printf("Ingrese el diametro de un circulo:\n");


scanf("%f",&diametro);

radio=diametro/2.0;
perimetro=2.0*PI*radio;
area=PI*radio*radio;

printf("El radio del circulo es %.2f\n",radio);


printf("El perimetro del circulo es %.2f\n", perimetro);
printf("El area del circulo es %.2f\n",area);
return 0;
}

4.4. Operaciones matemáticas y números complejos


4.4.1. Operaciones matemáticas
Para llevar a cabo algunas operaciones matemáticas comunes, se puede agregar al código
el comando #include <math.h>. Con este comando se pueden llevar a cabo las siguientes
operaciones:

floor(x) Retorna el entero más largo que no es mayor a x

83
ceil(x) Retorna el numero entero más pequeño que no es menor que x

fabs(x) Retorna el valor absoluto de x

log(x) Retorna el logaritmo natural de x

log10(x) Retorna el logaritmo en base 10 de x

exp(x) Retorna el valor de ex

sqrt(x) Retorna el valor de la raiz cuadrada de x

pow(x) Retorna el valor de xy

sin(x) seno de x en radianes

cos(x) coseno de x

tan(x) tangente de x

4.4.2. Números complejos


Para generar número complejos en C se debe escribir double complex y en seguida el nombre
de la variable con su valor. La parte imaginaria del número complejo se escribe como la parte
entera multiplicada por la letra I, como se presenta en el siguiente ejemplo:

#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);

printf("Operaciones con numeros complejos:\n");


printf("cx = %.2f%+.2fi , cy = %.2f%+.2fi\n",creal(cx), cimag(cx), creal(cy), cimag(cy));
printf("La suma cx + cy = %.2f%+.2fi\n",creal(sum),cimag(sum));
printf("La resta cx - cy = %.2f%+.2fi\n",creal(resta),cimag(resta));
printf("La multiplicacion cx * cy = %.2f%+.2fi\n",creal(multiplicacion),cimag(multiplicacion
printf("La division cx /cy = %.2f%+.2fi\n",creal(division),cimag(division));
printf("El conjugado cx = %.2f%+.2fi\n",creal(conjugado),cimag(conjugado));
return 0;
}

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>

Para generar condiciones se pueden implementar los siguientes operadores lógicos:

< <= menor y menor o igual

> >= mayor y mayor o igual

== != igual o no igual

& Adición ("Y")

^ |* ambos sirven como .O"

&& Adición lógica

|| .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:

/*determinando si es mayor 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.

4.6.2. bucle while


Asimismo, el bucle while se implementa de la misma forma que la que se utilizó en la
terminal con los comandos de linux y es también similar a la sintaxis utilizada con Python. El
ejemplo que se presenta aplica este bucle con el fin de imprimir los número desde el uno hasta
el diez:

#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:

/* Calcula la distancia entre dos puntos, dada su longitud y latitud */

/*Incluye modulo math*/


#include <math.h>

#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;
}

4.7.2. Zodiaco chino


Este ejercicio consiste en que el zodiaco chino asigna animales en un ciclo de 12 años. La
tabla con los años y animales de este calendario se presentan en el cuadro 4.1. El patrón se repite
con el año 2012 siendo otro año del dragón, y 1999 es otro año de la liebre. Se desea escribir un
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.

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

El código que permite solucionar el ejercicio es:


/* Muestra el animal asociado a un año del calendario chino */
#include <stdio.h>
int main(void)
{
int y; //variable que guarda el año
printf("Add a year: "); //Se pide el año
scanf("%d",&y); //Se guarda el año en y
/*Si el año es menor que cero se imprime el mensaje Año incorrecto*/
if(y<0)
printf("Año incorrecto\n");
/*Si el modulo de la división del año entre 12 es cero, se imprime Mono*/
else if(y % 12==0)
printf("Mono\n");

/*Si el modulo de la división del año entre 12 es uno, se imprime Gallo*/


else if(y%12==1)
printf("Gallo\n");

/*Si el modulo de la división del año entre 12 es dos, se imprime Perro*/


else if(y%12==2)
printf("Perro\n");

/*Si el modulo de la división del año entre 12 es tres, se imprime Cerdo*/


else if(y%12==3)
printf("Cerdo\n");

/*Si el modulo de la división del año entre 12 es cuatro, se imprime Rata*/


else if(y%12==4)
printf("Rata\n");

/*Si el modulo de la división del año entre 12 es cinco, se imprime Buey*/


else if(y%12==5)

88
printf("Buey\n");

/*Si el modulo de la división del año entre 12 es seis, se imprime Tigre*/


else if(y%12==6)
printf("Tigre\n");

/*Si el modulo de la división del año entre 12 es siete, se imprime Liebre*/


else if(y%12==7)
printf("Liebre\n");

/*Si el modulo de la división del año entre 12 es ocho, se imprime Dragon*/


else if(y%12==8)
printf("Dragon\n");

/*Si el modulo de la división del año entre 12 es nueve, se imprime Serpiente*/


else if(y%12==9)
printf("Serpiente\n");

/*Si el modulo de la división del año entre 12 es diez, se imprime Caballo*/


else if(y%12==10)
printf("Caballo\n");

/*Si el modulo de la división del año entre 12 es once, se imprime Obeja*/


else
printf("Obeja\n");
return 0;

89
90
5

C: arreglos, asignación de memoria


dinámica y punteros

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:

[language=C, caption= Ejemplo Arrays C ]


/*creando un arreglo*/

#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;
}

5.1.1. Inicializar Arrays


Al crear un arelgo se puedn sar valores a los elementos de una tabla desde el inicio en la
ejecución del programa, pues es mas efectivo que definir uno por un cada elemento.Es importante
recordar que los elementos tendrán todos el mismo nombre, y ocuparán un espacio contiguo en
la memoria. El siguiente ejemplo mostrara una funcion que define su espacio y da la suma de

91
sus elementos1 :

[language=C, caption= Ejemplo Arrays C 2 ]


/*creando un arreglo*/
#include <stdio.h>

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 */

suma = numero[0] + /* Y hallamos la suma */


numero[1] + numero[2] + numero[3] + numero[4];
printf("Su suma es %d", suma);
/* Nota: esta forma es algo menos engorrosa, pero todavía no */
/* está bien hecho. Lo seguiremos mejorando */

return 0;
}

5.1.2. Recorrer elementos en un Array


Es de imaginar la existencia de formas mas efectivas de acceder a los elementos de un array
sin necesidad de invocarlos repetidamente. Entonces es de suponer la existencia de estructuras
repetitivas que permitan este porceso, tales como:

while

do... while

for

5.1.3. Arreglos para caracteres (strings)


El uso de cadenas de texto se implementa en C en modo de arreglos de ”caracteres”.La
sucecion de este arreglo termina con un caracter nulo (
0), lo que produsca que se reserve un espacio de sobra del necesario.Este caracter nulo indica el
final de la cadena, generando un orden a la hora de manipulación de la misma. Es importante
mecionar que para esta clase de arreglos el distintivo que usa el array es char.

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:

{language=C, caption= Ejemplo Arrays C ]


#include <stdio.h>

1
Tomado dehttp://www.nachocabanes.com/c/curso/cc05.php

92
int main()
{
char x [40]; /* Para guardar hasta 39 letras */

printf("Introduce palabra: ");


/*Pide entrada*/
scanf("%s", texto);
/*Pide los caracteres de arreglo*/
printf("Su palabra es : %s. La priemra letra es : %c\n", texto, texto[0]);
/*Donde texto [0] indica la primera letra*/

return 0;
}

5.1.5. Logitud del Array


Ya que al inicio del programa se declaro y definio el tama;o de arreglo lo comun es que
la longitud de este este definida.Sin embrago si no se usa todo el espacio determinado para el
arreglo el compilador lo que hace es reservar las posiciones más no limpiarlas.De esta manera
hay dosopciones si se quiere saber la lungitud real de la cadena creada.Estas son:

Leer la cadena desde el inicio hasta encontrar el caracter nulo que maracara el final.

Usar el comando strlen, el cual es un comando predefinido, y retorna la longitud real


correspondiente a la cadena llamar

Como ejemplo del uso de strlen se mostrara el siguiente codigo

#include <stdio.h>
#include <string.h>

int main()
{
char texto[40];

printf("Introduce una palabra: ");


scanf("%s", texto);
printf("el numero de letras de la palabra es es %d ", strlen(texto));

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.

5.1.6. Libreria string.h


Al declarar en lasa primeras lineas la libreria string.h, se permite la facilidad de hacer
operaciones numericas con las funciones caracter.Para esto dicha libreria trae consigo comandos
predeterminados que permiten hacer operaciones con distintos arreglos. Dichos comandos son :

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); .

strncat:Añade una cadena al final de otra (concatenarla) pero con un parametro de


esntrad de la cantidad d.Sbytes a usarus parametros son strcat (destino, origenn); .
A modo de ejemplo se escribira un codigo representativo de los cuatro comandos previos.
/*librerio para string*/
#include <string.h>
#include <stdio.h>
int main(void)
{ char s1[20]="Juan ";
char s2[10]="Marlon";
char s3[10];
char s4[40]="Mendez Reina ";
strcpy(s3,s2); //copia reemplazando s3 por s2
printf("%s\n",s3);
strncpy(s3,s1,2);//copia s1 2 carcateres a s3
printf("%s\n",s3);
//Contar la longitud de un string mas facil
printf("%d\n",strlen(s2));
strcat(s1,s2); //copia s2 al final de s1
printf("%s\n",s1);
strncat(s4,s1,7); //copia 7 caracteres de s1 al final de s4
printf("%s\n",s4);
return 0;
}
La libreria previamente mencionada tambien da la posibilidad de comparar cadenas alfabeticas
con el comando predeterminado strcmp, de modo que permitan observar su similitud o poder
ordenarlas.Dicho esto el orden y los parametros de entrada son:
strcmp (cadena1, cadena2);
Dados los dos parametros de entrada los valores retorno posibles que dara esta función serán:
0 si ambas cadenas son iguales.

Un valor <0 si cadena1 <cadena2

Un valor positivo si cadena1 >cadena2


Sin embargo este comando tiene ciertas peculiaridades, por lo que ha condiciones que se tienen
que tener en cuenta tales como:
Las palabras que inicien por B son ”mayores” a las que comiencen por A.

Las palabras escritas en minuscula son ”mayores” a las escritas enmayuscula.

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);

char text[]="Every dog has his day";


char word[]="dog";
char *pFound=NULL;
pFound = strstr(text,word);
if(pFound!=NULL)
printf("La palabra %s fue encontrada en %s\n",word,text);
return 0;
}

5.1.7. Arrays indeterminados

Si al declarar un arreglo se le asigna un valor inicial, no sera necesario idicar la longitud de


este pues el compilador contara los valore detallados. La forma de declarar dicha explicación se
da a modo de ejemplo de la siguiente manera:

char Ejemplo[] = {10, 0, -10};

5.1.8. libreria ctype.h

ctype.h es un archivo de cabecera de la biblioteca estándar del lenguaje de programación


C diseñado para operaciones básicas con caracteres. Al incluir esta libreria en la cabecera del
programa permitira declarar funciones que se utilizan para clasificar y buscar codigos del arreglo
de caracteres.

[caption : Funciones libreria ctype.h]

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)

5.2. ¿Para que sirven las estructuras dinámicas?


Es comun comenzar el trabajo de programación en C con el uso de funciones ESTÁTICAS,
las cuales poseen caracteristicas determinadas desde la creacion del programa, y aunque su
funcionamiento es sencillo y veloz solo sirven si en el desarrollo y estuctura del programa amerita
que las funciones no cambien.
De esta manera C posee una memoria dinamica para variables, la cual consiste en utilizar la
memoria del disco duro, paa guardar en ella los caracteres o bytes que le quepan a la memoria.
Es común pensar en crear una variable de arreglo, y sobredimensionar en ella la capacidad de
memoria que se va a utilizar. Sin e eesr eos cantidad de espacio y obliga a cMenor o igual que
los valores con los que se va a trabajar, Lo que genera problemas a la hora de dinamizar el
funcionamiento del programa .
De esta manera aparecen como recurso y solución la creacion de funciones DINÁMICAS, las
cuales permiten crecer o disminuir segun objetivos especificos. Dicho esto podemos clasificar
dichas funciones como :

Pilas: Se pueden apilar caracteres e ir usandolos a la vez.

Listas:En las que se puede añadir elementos, consultarlos o borrarlos en cualquier posición.

Las estructuras dinámicas como caracteristica general la función creciendo o decreciendo


según haga falta, contrario a un array cuyo tamaño es prefijado. De manera que lo queen
palabras simples hacen las funciones dinámicas es reservar un espacio de memoria para cada
elemento nuevo, el cualse enlaza con los elemntos que se tenian previamente.

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 */

n1 = 5.0; /* valor real funcion estatica */


n2 = (float *) malloc (sizeof(float)); /* Reservamos espacio para n2 */
*n2 = 6.7; /* Valor prefijado para n2 (puntero a real) */

suma = (float *) malloc (sizeof(float)); /* Reservamos espacio para suma */


*suma = n1 + *n2; /* Calculamos la suma */

printf("El valor fijado para la suma era %4.2f\n",*suma);

printf("Introdusca el primer número ");


scanf("%f",&n1); /* Leemos valor para n1 (real) */

printf("Introdusca segundo número ");


scanf("%f",n2); /* Valor para n2 (puntero) */

*suma = n1 + *n2; /* Calculamos nuevamente la suma */

printf("la nueva suma es %f\n", *suma);

return 0;
}

Las características de las variables en el anterior código se pueden observar a continuación

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);).

5.2.2. Operaciones con puntero


SumatoriaCuando se declara una variable, por ejemplo de tipo int n = x; donde n es el
nombre de la función y x un digito, y continuo a eso digitamos n++ o +1 la condición
que cumplira sera que n = x+1.Sin embargo en los punteros es de una forma algo distinta.
Siga el ejemplo para entender la explicación posterior.

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.

5.2.3. funciones con punteros


El uso de funciones bajo la función de variables que hacen caso a los punteros requiere que el
parámetro que se escribe dentro de la función main no es la variable real pues al ser un puntero
el usado significa que es una dirección de memoria en donde se encuentra la variable, por lo se
usa &.
Como ejemplo se presenta el siguiente código:

#include <stdio.h>

void operacion(int *i)


{
*i= *i * 3;
}

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;
}

5.2.4. Asignación dinámica de memoria C


En esta sección se explicara lo que se refiere a la aplicación y asignación de memoria dinámica
e el lenguaje de C a través de un grupo de funciones de la biblioteca stdlib.h. Dichos comandos
son:

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

Las diferencias principales entre malloc () y calloc() son:

malloc ()

• malloc() toma un único argumento, la cual es la canitdad d memoria al asignar la


cantidad de bytes

98
• malloc() no inicializa la memoria asignada

calloc ()

• calloc() requiere de dos argumentos,el número de variables para definir en la memoria


y el tamaño en bytes para una sola variable.
• calloc() inicializa todos los bytes del bloque de memoria asignada a cero.

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.

Errores lógicos: Todas las asignaciones deben seguir el mismo patrón.

Comprobación de errores de asignación

5.2.5. Arrays y punteros


Una de las importancias más significativas de el uso de pointers, es la incorporación de arrays
simultaneamente a la función de estos. Sin embargo la interaccion entre punteros y arrays suele
ser confusa por lo que en primer instancia se deben tener en cuenta dos principios:

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

Un puntero puede ser indexado como el nombre de un arreglo

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"};

printf("El primer mensaje de error es: %s\n",mensajeError[0]);


printf("El segundo mensaje de error es: %s\n", mensajeError[1]);
printf("El tercer mensaje de error es: %s\n", mensajeError[2]);

return 0;
}

5.2.6. Estructuras en punteros


Como se explico previamene un puntero puede pertenecer a cualquir tipo de dato básico, el
cual si se le desea reservar memoria se usa el comando ’malloc()’. esto mismo se puede hacer
como un ’struct’ pero tiene condiciones especificas en su uso.
Al ingresar en los datos de una variable dentro de un ’struct’ la sintaxis que se debe usar el
nombre de la variable previo a una flecha (->) que estara a su vez tras el campo en donde se
llamara la variable a usar. Un ejemplo explicativo de lo comentado anteriormente se muestra a
continuación 2 :

#include <stdio.h>

int main() {
/* Primero definimos nuestro tipo de datos */
struct datosPersona {
char nombre[30];
char email[25];
int edad;
};

/* La primera persona será estática */


struct datosPersona persona1;
/* La segunda será dinámica */
struct datosPersona *persona2;

/* Damos valores a la persona estática */


strcpy(persona1.nombre, "Juan");
strcpy(persona1.email, "[email protected]");
persona1.edad = 20;

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;

/* Mostramos los datos y liberamos la memoria */


printf("Primera persona: %s, %s, con edad %d\n",
persona1.nombre, persona1.email, persona1.edad);
printf("Segunda persona: %s, %s, con edad %d\n",
persona2->nombre, persona2->email, persona2->edad);
free(persona2);

return 0;
}

5.2.7. Arrays mutidimensionales y punteros


Una matriz bidimensional es implementada en C como un vector cuyos elementos son
vectores. Es decir, la matriz se implementa en forma de vector, sin embargo, cuando accedemos
a ella sigue teniendo la forma de matriz. La forma de declarar un array multidimensional es:

tipo nombre[expresion1][expresion2]...;

La forma de acceder a un elemento de la matriz es:

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:

6.1.1. Operadores de comparación y operadores lógicos


Operadores de comparación:
Estos valores permiten realizar comparaciones entre dos valores
OPERADOR SIGNIFICADO
< Estrictamente menor que
> Estrictamente mayor que
<= Menor o igual que
>= Mayor o igual que
== igual que
!= distinto de
Operadores lógicos Con este tipo de operadores se unen varias condiciones. Retornan valor
lógico True o False.

OPERADOR SÍMBOLO EJEMPLO RESULTADO


Y && a>5 && b<5 True si ambos son iguales.
O || a>5 || b<5 True si alguna, o las dos condiciones se cumple.
O exclusivo XOR a>5 XOR b<5 True si sólo una de las dos condiciones se cumple.
Negación != a!=5 True si a es diferente a 5
La estructura de if en C se realiza de la siguiente manera:

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:

if (a>b && b<c){


printf("a es mayor que b");
printf("b es menor que c"");

En el caso de querer realizar un segundo conjunto de acciones condicionadas, si la primera


condición no se cumple, se usa el comando else if. Además, si se desea realizar una acción si
ninguna condición se cumple se usa el comando else. Ejemplo:

if (a>b && b<c){


printf("a es mayor que b");
printf("b es menor que c"");

}
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);

if (letra>=A && letra<= P)


printf("la letra está en el rango A-P");

else if (letra>P && letra<= Z );


printf("la letra está en el rango Q-Z");

}
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.

6.1.2. Operador ternario


En c hay otro modo de estructurar los condicionales sin usar el comando if y es con el
operador ternario. Con este operador se evalúa una condición y si el resultado es True se
ejecuta la primera expresión, de lo contrario se efectua la segunda. La estructura del operador
ternario es:

condición ? expresión1:expresión2;
Ejemplo:

valor= 7*( y>10 ? 5 : 3);

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;

Otro ejemplo de este operador es:


x= y>10 ? 3 : 4;
Acá si y>10 retorna True la variable x toma el valor de 3. Si retorna lo contrario x toma el
valor de 4. La estructura equivalente, de este proceso, con el comando if es:
if(y>10)
x= 3;
else
x= 4;
Este operador puede ser ejecutado dentro del comando printf como en el siguiente ejemplo:

printf("Alcanzarás tu meta de ahorro en \%d dia\%s.", dias, dias==1 ? " " : "s" );

En este ejemplo si la variable dias es igual a 1 se imprimirá el mensaje:


Alcanzarás tu meta de ahorro en 1 dia.
En el caso en que la variable dias es diferente de 1, por ejemplo tiene el valor 20, el mensaje
será:
Alcanzarás tu meta de ahorro en 20 dias.

6.1.3. Operadores condicionales bitwise


Los operadores bitwise son similares a los operadores lógicos.Sin embargo,estos no comparan
el valor asignado a una variable sino el valor el bits de la variable y sólo pueden operar sobre
variables enteras (int) y char.La siguiente tabla contiene los operadores bitwise que pueden ser
usados como condicionales:

SÍMBOLO DESCRIPCIÓN RESULTADO


& Y Resulta 1 si ambos bits son 1 de lo contrario resulta 0
| O Resulta a 1 si uno o ambos bits son 1. Si ninguno es 1 resulta 0
^ O exclusivo Resulta 1 ambos bits son diferentes.

Ejemplos de cómo funcionan los operadores bitwise:

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

Como el primer bit de x y y vale 1, la primera entrada de z vale 1 Dándole un valor a z


de 8.

2.
x=12
y= 11
z= x|y

El valor de cada variable en binario:

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

El valor de cada variable en binario:

x= 1 1 0 0
y= 1 0 1 1
z=0111

El primer bit de x y y es 1, por lo cual el primer bit de z es 0. El valor resulte de z es 7.

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:

tipodeobjeto nombrefunción (tipo nombreparametro1, tipo nombreparametro2){

cuerpo de la función

return objeto;
}

Por ejemplo:

int suma(int a,b){

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.

¿Cómo se usan los parámetros?

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:

int espacios (char mensaje[]){

108
int cont=0;
int i=0;
int largo = strlen(mensaje);
for(i;i<largo; i++){
if(mensaje[i] == ’ ’)
cont++;
}
return cont;
}

int main (void){

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:

int espacios (char);


int main (void){

/*Código de main*/

}
int espacios (char mensaje[]){

109
/*Código de espacios*/

6.2.3. Punteros como argumentos


En los ejemplos anteriores los objetos o variables que entran como argumentos no son
modificados por la función, en cambio la copia de sus valores es la que es modificada. Este
mecanismo actúa en toda función de C y se denomina mecanismo pass-by value. Para aclarar
este concepto se usa el siguiente ejemplo:

int multiplica(int numero){

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;

El resultado al ejecutar el ejemplo anterior sería:


Con la función numero= 15
En el main numero = 5 y resultado = 15
Para cambiar el valor de una variable después de ejecutar una función es necesario usar los
punteros. Con esto después de ejecutar la función en el main la variable cambiará de valor.
Modificando el ejemplo anterior para comprobar lo dicho resulta:
int multiplica(int *pnumero){

int *pnumero=3*(*pnumero);
printf("Con la función numero = %d", *pnumero);
return *pnumero;

}
int main(void){

int numero = 5;
int *pnum = &numero;
int multiplicado = 0;
multiplicado = multiplica(pnum);

110
printf("En el main numero= %d y resultado = %d",numero, multiplicado );
return 0;
}

El resultado al ejecutar el ejemplo anterior sería:

Con la función numero= 15


En el main numero = 15 y resultado = 15

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:

int *pnum = &numero;

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.

6.2.4. Retornar el valor de un puntero


Para poder retornar un puntero con una función es necesario declarar la función de la
siguiente manera:

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:

int *pagodeuda(int *deuda){

*deuda-=2000;
return deuda
}

int main (void){

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;

Esta función retornará el siguiente mensaje:

antes de pagar= 1000, después de pagar= 1000

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.

6.2.5. Tipos de variables según su ámbito


Algo muy importante para aprender a programar funciones en C es el ámbito de las variables
declaradas. De esto depende el uso que se le puede dar a una variable entre funciones.

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:

auto char letra;

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:

static int numero = 3;

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:

register int numero;

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){

//Valor con el que se inicializa la memoria dinámica


int i =1;

//posición inicial en el arreglo pnumeros


int n= 0;

//variable en la que se guardará el numero dado por el usuario


float numeros;

//creación de memoria dinámica para el puntero pnumeros


float *pnumeros = (float *)malloc(i);

//Variable que guardará respuesta del usuario


int respuesta = 0;

//Varible que guardará la suma de los números dados por el usuario


float suma;

//Mensaje
printf("Escriba un numero entero positivo\n");

//guarda lo escrito por el usuario en la variable numeros


scanf("%f", &numeros);

//En la posición n se guarda el valor de la variable numeros


pnumeros[n]= numeros;

//Loop continua si el valor de la variable respuesta es igual a 0


while (respuesta == 0)
{
//mensaje
printf("Si desea digitar otro numero escriba 0 si no escriba 1\n");

114
//guarda el valor dado por el usuario en la variable respuesta
scanf("%d", &respuesta);

//actualiza la memoria dandole capacidad para i bits


pnumeros = realloc(pnumeros,i); }

//condicional si respuesta es igual a 0


if (respuesta == 0){

//suma 1 a la variable i
i++;

//suma 1 a la variable n
n++;

//mensaje
printf("Escriba otro numero:\n");

//guarda el numero dado por el usuario en la variable numeros


scanf("%f",&numeros);

//En la posición n se guarda el valor de la variable numeros


pnumeros[n] = numeros;
}
}
//condicional si respuesta igual a 1
if (respuesta == 1){

//crea variable para recorrer el for


int j;

// for inicializado en 0 y termina si j es igual a i,


//por cada recorrido se aumenta 1 a j
for (j=0; j<i;j++){

//se recorre cada posición del arreglo pnumeros y


//se suma el valor de la posición
suma+= pnumeros[j];

//divide la suma en la cantidad de números dados


float promedio = suma/i;

//imprime el promedio
printf("el promedio de la suma de sus números es:
%f\n",promedio);

115
return 0;

Al ejecutar este código el resultado final será:

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

/*funcion que cuenta las palabras*/

//Entra como parámetro un arreglo de caracteres


int contpalabras(char str[]){

//variable usada en el recorrido for para cada posición del arreglo


int i =0;

//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];

//recorrido desde 0 hasta la ultima letra


for (i; i<longi; i++ ){

//letra en posición i en el arreglo del parámetro


char letra = str[i];

//letra en posición i+1


char sigletra= str[i+1];

//Condicional que identifica signos que separan parabas


if (letra == ’,’|letra==’.’ | letra== ’ ’ | letra == ’;’){

//se se encuentra un signo significa que acabo una palabra


cont++;
}

//Se omiten los espacios después de comas o puntos


if (letra == ’,’ & sigletra == ’ ’){
cont = cont-1;
}
if (letra == ’.’ & sigletra == ’ ’){
cont = cont-1;
}

//se omite el punto final


if ( ultim == ’.’){
cont= cont-1;
}

return cont;

/*función que separa la cadena de caracteres en palabras*/

//Entra como parámetros una cadena de caracteres y un puntero de un arreglo de caracteres


int separa(char strin[], char *mensaje[]){

//Longitud del arreglo, teniendo en cuenta la posición 0

117
int a = (strlen(strin)-1);

//indicadores de posicion en arreglos


int k =0;
int l =0;
int m=0;

//recorrido sobre la longitud del arreglo


for (k;k<a;k++){

//Arreglo de caracteres con capacidad de 100 bits


char palabra[100];

//Toma la letra en la posición k


char letra = strin[k];

//Letra en posición k+1


char sigletra = strin[k+1];

//Condicional para omitir signos de puntuación en las palabras


if (letra != ’\0’& letra != ’ ’& letra!= ’;’ & letra!= ’.’& letra!= ’,’){

//Se agregan los caracteres del arreglo strin al arreglo palabras


palabra[l]=strin[k];

//Aumenta una posición en arreglo palabras


l++;
}

//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{

//Se concatena cada caracter y se guarda como una palabra en la posición 0


//del arreglo mensaje
mensaje[m]=strdup(palabra);

//se resetea el arreglo palabras para guardar una nueva palabra


memset(palabra,’ ’,100);

//Para volver a posición 0 en arreglo palabras


l=0;

118
//Aumenta una posición el arreglo mensaje para guardar la siguiente palabra.
m++;

}
//Devuelve el número de palabras
return m;
}

/*Funcion que cuenta letras en cada palabra*/


//Entra como parámetro el puntero de un string
int contletras(char *palabra){

//Puntero que guarda el valor de la palabra para separar el


//elemento (palabra) en caracteres
char *palab=palabra;
int i=0;

//cuenta cada carácter omitiendo la separación de palabras en el arreglo


while (palab[i]!= ’ ’){

i++;
}

//Devuelve la cantidad de letras


return i;
}

/*función principal*/
int main (void){

//Puntero y variable tipo char


char *stri,cadena;
int i=0,j=1;

//creación de memoria dinámica para puntero según el tamaño del texto


stri =(char *)malloc(sizeof(char));

//Si no se escribe punto final la función separa no cuenta la últma palabra


printf("escriba un mensaje con punto final:\n");

//Mientras no se llegue a una terminación de linea


while (cadena != ’\n’){

//lee la entrada de teclado

119
cadena = getc(stdin);

//Cuadra la capacidad de memoria del arreglo stri


//según texto ingresado.
stri =(char *)realloc(stri,j*sizeof(char));

//le asigna a cada caracter a una posición del arreglo


stri[i]=cadena;
i++;
j++;
}
//el final debe ser el corte de línea
stri[i]=’\0’;

//puntero con la capacidad de memoria según la cantidad de palabras dada


//por la función contpalabras con argumento de entrada el arreglo en
//el que se guardó la entrada de teclado
char *mens[contpalabras(stri)];
printf("cantidad de palabras con con %d\n",contpalabras(stri));
printf("cantidad de palabras con sep %d\n",separa(stri,mens));
int k=0;
int l=0;

//Recorrido de 0 hasta la cantidad de palabras en el mensaje


for(k;k<contpalabras(stri);k++){

//Imprime el numero de letras en la palabra en posición k del arreglo mens


printf(" letras en %s es: %d\n",mens[k],contletras(mens[k]) );

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 main(int argc, char *argv[]){

int bor;

//Para inicializar el archivo


FILE *in;

//Nombre del archivo con el numero pi


char filename[100]="Pi_2500000.txt";

//Se suman los digitos decimales en esta variable


long long int suma;

// Variable que guarda el numero de cifras que quiere sumar el usuario


long long int n;

//variable de iteración
long long int i;

//Se guarda las unidades : numero 3


char tres;

//Se guarda el numero


char numero;

char num[2500000]="";

121
//Se adiciona al string los numeros del usuario
for(i=1; i<argc; i++){
strcat(num, argv[i]);
}

//Se cambia el numero a entero


n=atoi(num);

// Abre el archivo
in=fopen(filename,"r");

//Si no existe el archivo


if(!in){

//Imprime el mensaje
printf("Problemas al abrir el archivo %s\n",filename);
exit(1);
}

//Guarda el numero entero tres en la variable "tres"


fscanf(in,"%c",&tres);

//Para numeros mayores que 25000000 o menores que cero


if( n<0 || n>2500000 ){

//Se imprime el mensaje


printf("Numero ingresado no valido\n");
}
//De lo contrario
else{
//Para i menores que el numero ingresado por el usuario
for(i=0; i<n; i++){

//Se guarda el numero pi en la variable numero


fscanf(in,"%c",&numero);

//Se evita el punto que separa cifras decimales


if(numero!=’.’){
suma+=numero-’0’;
}
//Se reduce una iteración si se llega al punto que separa
//cifras decimales
else{
i--;
}

}
//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;
}

El resultado al ejecutar este código es:

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

En el siguiente capítulo se pretende resolver sistemas de ecuaciones lineales por medio de


la implementación de IPython. Para ello, puede ser útil recurrir a los capítulos 2 y 3, en los
que se presentan las generalidades de la plataforma IPython notebook. Con el fin de que los
métodos implementados sean comprendidos en su totalidad, se inicia el capítulo con una breve
exposición de las generalidades del algebra de matrices, junto con la importancia y generalidades
de los sistemas de ecuaciones lineales. Posteriormente, se presentarán los diferentes métodos
implementados en IPython para la resolución de los sistemas de ecuaciones. Por último, se
exponen las soluciones a algunos ejercicios que requieren la implementación de los métodos
presentados a lo largo del capítulo.

7.1. Sistemas de ecuaciones lineales


Una ecuación lineal es una ecuación en la que las incógnitas se encuentran elevadas a la
potencia uno y no existen incógnitas elevadas a una potencia mayor. Por lo tanto, una ecuación
lineal tiene la forma:

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:

a11 x1 + a12 x2 + ... + a1n xn = b1


a21 x1 + a22 x2 + ... + a2n xn = b2
a31 x1 + a32 x2 + ... + a3n xn = b3
..
.
am1 x1 + am2 x2 + ... + amn xn = bm

Para el sistema de ecuaciones anterior existen n incógnitas x y existen m ecuaciones lineales,


con términos independientes bm . Este sistema se puede expresar de forma matricial así:

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.

7.1.1. Suma y resta de matrices


Para llevar a cabo una suma entre dos matrices es necesario que ambas sean de las mismas
dimensiones, es decir, es necesario que ambas matrices sean del mismo tamaño. Si se cumple
esta condición, la suma entre dos matrices es equivalente a la matriz del mismo tamaño que las
matrices originales, formada por la suma de cada uno de los elementos que ocupan la misma
posición en ambas matrices. Así, la suma de una matriz A y una matriz B de dimensiones m × n
es equivalen a una matriz C en la que se cumple [9]:

cij = aij + bij


Con base en lo anterior, un ejemplo de suma entre dos matrices de dimensiones 2 x 2 es:
" # " # " #
2 5 3 7 5 12
+ =
11 3 4 5 15 8
Si se aplica la suma entre una matriz A y una matriz de las mismas dimensiones con valor
cero en todas las posiciones se obtiene la misma matriz A [9], ya que se suma cero a todos los
elementos de la matriz original.

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:

cij = aij − bij


Al igual que para la suma, al restar una matriz con ceros en todas las posiciones se obtiene
la matriz original. El siguiente ejemplo presenta el resultado de la resta entre dos matrices de
dimensiones 3x2:
" # " # " #
4 7 3 1 1 6
− =
3 4 1 11 2 −7

7.1.2. Multiplicación escalar y matriz transpuesta


La multiplicación escalar consiste en la multiplicación entre una matriz y una constante. El
resultado de un producto escalar es una matriz del mismo tamaño que la matriz original, pero
los elementos son el producto entre la constante y el elemento en la misma posición de la matriz
original [9]. Así, si se multiplica una matriz A por una constante r se obtiene como resultado
una matriz B en la que se 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

La notación para simbolizar la transpuesta de una matriz A es AT . En el caso en que A sea


igual a AT se dice que la matriz A es simétrica [9].

7.1.3. Multiplicación entre matrices


La multiplicación entre dos matrices puede realizarse sin la necesidad de que ambas matrices
sean del mismo tamaño. Sin embargo, la multiplicación no se realiza elemento a elemento, como
en el caso de la suma, la resta y el producto escalar. En contraste, al llevar a cabo el producto
entre una matriz A de dimensiones m × n y una matriz B de dimensiones n × r se obtiene una
matriz C de dimensiones m × r en la que cada elemento cmr es el resultado del producto punto
entre el vector formado por los elementos en la fila m de la matriz A y el vector formado por
los elementos en la columna r de la matriz B. Así, el elemento cmr cumple:

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í:

(a11 + a12 + · · · + a1n ) · (x1 + x2 + · · · + xn ) = a11 x1 + a22 x2 + · · · + a1n xn


Este mismo procedimiento se repite para obtener las m ecuaciones lineales que conforman la
matriz de dimensiones m × 1, con lo que se obtiene el sistema de ecuaciones lineales y se prueba
la igualdad de la ecuación 7.3.

7.1.4. Propiedades del álgebra de matrices


Las propiedades relacionadas con las operaciones elementales entre matrices son [9]:

A + B = B + A Ley conmutativa de la adición

(A + B) + C = A + (B + C) Ley asociativa de la adición

A + 0 = 0 + A = A Identidad para la adición

128
r(A + B) = rA + rB Ley distributiva

(rs)A = r(sA) Ley asociativa de la multiplicación escalar

(rA)B = A(rB) = r(AB) Cambio de escalar

A(BC) = (AB)C Ley asociativa de multiplicación matricial

IA = A Identidad para la multiplicación de matrices

A(B + C) = AB + AC Ley distributiva

(A + B)C = AC + BC Ley distributiva

Estas propiedades se cumplen para cualquier matriz o cualquier escalar relacionados con
operaciones elementales.

7.1.5. Importancia de los sistemas de ecuaciones lineales


La importancia de los sistemas de ecuaciones lineales radica en que [3]:

Se aplican a problemas de distintos campos como la ingeniería, en temas relacionados con


el flujo vehicular y circuitos eléctricos, en la economía, en temas como la curva de oferta
-demanda y el modelo económico de Leontief, y en la computación, a motores de búsqueda
y restauración de imágenes digitales.

Se aplican a la geometría analítica, el cálculo de varias variables, ecuaciones diferenciales


y estadística

Originan el desarrollo de la teoría en álgebra lineal.

7.2. Solución de sistemas de ecuaciones lineales


La solución de un sistema de ecuaciones consiste en encontrar los valores de las incógnitas
para las que se cumplen todas las ecuaciones que hacen parte del sistema. Para ello, se deben
aplicar operaciones entre las filas de la matriz que contiene los coeficientes del sistema de
ecuaciones lineales, es decir, la matriz A de la ecuación 7.2. Con estas operaciones se busca
obtener una matriz reducida que permita encontrar el valor de cada una de las incógnitas

7.2.1. Operaciones elementales entre filas


Una notación simplificada de un sistema de ecuaciones se obtiene con la matriz aumentada
del sistema. Cada ecuación está escrita en la forma estándar con el término constante a la
derecha. La matriz aumentada es similar a la matriz de los coeficientes del sistema de ecuaciones.
La única diferencia es que se adiciona a la matriz la columna de términos constantes, que en la
ecuación 7.2 es la columna de elementos denotados por b. La matriz aumentada de la ecuación
7.2 es:
 
a11 a12 ··· a1n b1

 a21 a22 ··· a2n b2 

 .. ..  (7.5)
. .
 
 
am1 am2 · · · amn bm

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]:

Intercambiar el vector fila i por el vector fila j en una matriz: fi → fj


Multiplicar el vector fila i en la matriz por un escalar s diferente de cero: fi → sfi
Adicionar al vector fila i de una matriz s veces el vector fila j: fi → fi + sfj

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

La matriz aumentada del sistema de ecuaciones anterior es:


 
0 1 −3 −5
 2 3 −1 7 
 
4 5 −2 10
Ahora inicia la reducción a la forma escalonada. Para ello, se intercambian las filas 1 y 2
con el fin de obtener un pivote en la primera fila y la primera columna de la matriz:
 
2 3 −1 7
 0 1 −3 −5 
 
4 5 −2 10
Ahora a la fila 3 se resta 2 veces la fila uno para obtener una columna con todos los elementos
igual a cero por debajo del pivote, con lo que se obtiene:
 
2 3 −1 7
 0 1 −3 −5 
 
0 −1 0 −4
Para obtener una matriz con ceros debajo de los pivotes se suma a la fila 3 la fila 2:
 
2 3 −1 7
 0 1 −3 −5 
 
0 0 −3 −9
Como se observa, los términos constantes también se ven afectados por las operaciones que
se realizan entre filas. La matriz obtenida permite solucionar ahora el sistema de ecuaciones,
pero es posible continuar reduciendo la matriz hasta obtener como únicas entradas distintas de
cero a los pivotes. Para ello, se deben asignar a los valores en las columnas por encima de los
pivotes el valor cero. Para empezar, se multiplica la fila 3 por −1
3 , con lo que se obtiene:
 
2 3 −1 7
 0 1 −3 −5 
 
0 0 1 3
Para obtener ceros encima del pivote de la fila 3 se aplican dos operaciones. Primero, a la
fila 1 se suma la fila 3. Después, a la fila 2 se suma tres veces la fila tres:
 
2 3 0 10
 0 1 0 4 
 
0 0 1 3

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

Así, la solución está dada por x = [x1 , x2 , x3 ] = [−1, 4, 3] [9].


En el ejemplo anterior se encontró una única solución para el sistema de ecuaciones. Para
un sistema Ax = b se cumple [9]:

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.

7.3. Métodos de solución de sistemas de ecuaciones lineales en


IPython
Para solucionar un sistema de ecuaciones se pueden implementar métodos computacionales.
En esta sección se presentan algunos métodos que se pueden llevar a cabo para la resolución de
los sistemas que se han expuesto a lo largo del capítulo.
Hay dos clases de métodos para la solución de ecuaciones algebraicas: los métodos directos y
los métodos indirectos, o iterativos. Los métodos directos transforman las ecuaciones originales
en ecuaciones equivalentes que tienen la misma solución pero que se pueden resolver con mayor
facilidad.
Los métodos indirectos, o iterativos, comienzan con una suposición de la solución de x y
luego repetidamente refinan la solución hasta alcanzar un criterio de convergencia. Los métodos
iterativos son generalmente menos eficientes que los métodos directos, debido al gran número
de iteraciones requeridas. Sin embargo, sí tienen ventajas computacionales significativos si la
matriz de coeficientes es muy grande y baja densidad de población, es decir, si la mayoría de
los coeficientes son cero.

7.3.1. Métodos directos


Los métodos directos que permiten la solución de un sistema de ecuaciones y que se detallarán
son tres:

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

Aij ← Aij − λAkj j = k, k + 1, · · · , nbi ← bi − λbk


Aik
donde λ = A kk
, con k = 1, 2, · · · , n − 1 y i = k + 1, k + 2, · · · , n.
La sustitución será, luego de la transformación igual a :
n
!
X 1
xk = bk − Akj xj , k = n − 1, n − 2, .., 1
j=k+1
Akk

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)

# Primera fase de eliminación, para los valores por debajo de la diagonal


# de pivotes

133
#Para valores de k entre 0 y el numero de columnas menos 1
for k in range(0,n-1):

#Y para valores de i entre k+1 y el numero de filas


for i in range(k+1,n):

if a[i,k] != 0.0: #Si el valor de la posición no es igual a cero

#Se calcula el factor lambda dado por el valor en la posición


sobre el valor en la fila pivote

lam = a [i,k]/a[k,k]

#El nuevo valor en la posición será el valor anterior menos el


factor lambda por el valor de la fila pivote.

a[i,k:n] = a[i,k:n] - lam*a[k,k:n]

#Esta operación se realiza a toda la fila, desde la diagonal


de pivotes hasta la ultima columna

#La resta se efectua tambien para las constantes en b


b[i] = b[i] - lam*b[k]

# Se despejan las incógnitas


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

Se aplica el código anterior a una matriz A con términos constantes b:

   
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

Gracias a la descomposición, se facilita la resolución del sistema de ecuaciones, ya que se


tienen las siguientes equivalencias:

Ax = b LU x = b

De estas ecuaciones de puede realizar la descomposición LU:

Ux = y (1) Ly = b (2)

Después de realizar la separación, se resuelve y para la ecuación 2 y luego se resuelve x por


sustitución.
El siguiente ejemplo implementa este método para solucionar el sistema de ecuaciones
lineales:
   
8 −6 2 28
A = −4 11 −7 −40
   
4 −7 6 33
Descomponiendo en las matrices LU
  
2 0 0 4 −3 1
A = LU = −1 2 0 0 4 −3
  
1 −1 1 0 0 2
Ahora se soluciona el sistema Ly = b. Como la matriz L es una matriz triangular superior,
se despeja primero la primera incógnita de la primera fila de la matriz y, conocido su valor, se
hallan los valores de las otras incógnitas en las filas ubicadas debajo por despeje:

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

La descomposición Dolittle consiste en descomponer la matriz A del sistema de ecuaciones


en las dos matrices LU, que deben cumplir ciertas restricciones: la matriz L debe ser una matriz
triangular inferior con unos en la diagonal, es decir, en las posiciones de los pivotes. Por otro
lado, la matriz U debe ser una matriz triangular superior:

   
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

Aplicando eliminación Gaussiana a A


f2 ← f 2 − L21 f 1 ; f3 ← f 3 − L31 f 1
 
U11 U12 U13
A0 =  0 U22 U23
 

0 +U22 L32 U23 L32 + U33
f3 ← f3 − L32 f2
 
U11 U12 U13
A00 =  0 U22 U23 
 
0 0 U33

Como se puede deducir de la reducción anterior, la descomposición Dolittle presenta dos


características importantes:

La matriz U es idéntica a la matriz triangular superior que resulta de la eliminación de


Gauss.

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

El código que permite solucionar un sistema de ecuaciones por medio de la descomposición


Dolittle es:

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

lam = a [i,k]/a[k,k] #Se calcula lambda

Se aplican operaciones entre filas


a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n]

#Se guardan los valores de lambda en la parte inferior de la matriz


triangular
a[i,k] = lam
return a #Se retorna la combinación entre L y U

#Función que resuelve el sistema LUx=b

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):

#El arreglo de constantes se modifica, restándole al valor original la


sumatoria de los valores de las posiciones multiplicadas por las
constantes k de cada fila

b[k] = b[k] - np.dot(a[k,0:k],b[0:k])


b[n-1] = b[n-1]/a[n-1,n-1]

for k in range(n-2,-1,-1):

#El valor de k es equivalente a restar del valor original la sumatoria


de valores en la fila por los valores de x correspondientes y dividir
entre el valor del pivote en la fila.

b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k]

return b #Se retorna la solución del sistema

Descomposición Choleski

La descomposición de Choleski A = LLT tiene dos limitaciones

Requiere que A sea simétrica.

El proceso de descomposición consiste en tomar raíces cuadradas de ciertas combinaciones


de los elementos de A. Se puede demostrar que para evitar las raíces cuadradas de números
negativos A debe ser definida positiva.

La descomposición de Choleski contiene aproximadamente n3 /6 operaciones más n cálculos


de raíces cuadradas. Esto es aproximadamente la mitad del número de operaciones requeridas
en descomposición LU. La eficiencia relativa de la descomposición de Choleski se debe a su
explotación de simetría.
Para llevar a cabo el método de descomposición Choleski, se debe descomponer la matriz
con los coeficientes del sistema de ecuaciones A en dos matrices. Una de estas matrices debe
ser la matriz transpuesta de la otra matriz:

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

Realizando la multiplicación matricial se obtiene:

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

Despejando las componentes de la matriz resultante se puede generalizar la solución:

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

a[k,k] = math.sqrt(a[k,k] - np.dot(a[k,0:k],a[k,0:k]))

except ValueError:
#Se arroja un mensaje de error

error.err(’Matrix is not positive definite’)

for i in range(k+1,n):

#Se halla la matriz L


a[i,k] = (a[i,k] - np.dot(a[i,0:k],a[k,0:k]))/a[k,k]

# la matriz L se vuelve una matriz triangular inferior


for k in range(1,n): a[0:k,k] = 0.0
return a #Se devuelve la matriz L

#Función que soluciona el sistema de ecuaciones por el método Choleski


def choleskiSol(L,b):
n = len(b) #Número de términos constantes

# Solución de [L]{y} = {b}


for k in range(n):
b[k] = (b[k] - np.dot(L[k,0:k],b[0:k]))/L[k,k]

# Solución de [L Transpuesta]{x} = {y}


for k in range(n-1,-1,-1):
b[k] = (b[k] - np.dot(L[k+1:n,k],b[k+1:n]))/L[k,k]
return b #Se retorna la solución del sistema

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].

7.3.4. Métodos indirectos


Los métodos iterativos o indirectos, comienzan con una estimación inicial de la solución x
y luego mejoran varias veces la solución hasta que el cambio en x se convierte en insignificante.
Debido a que el número requerido de iteraciones puede ser grande, los métodos indirectos son,
en general, más lentos que sus homólogos directos. Sin embargo, los métodos iterativos tienen
las siguientes dos ventajas que los hacen atractivos para ciertos problemas:

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

El método de Gauss-Seidel permite encontrar las soluciones a sistemas de ecuaciones por


medio de tres pasos:

Se inicia con una solución tentaiva x, o puede escogerse aleatoriamente.

Se evalúa de nuevo x utilizando la formula de iteración. Esto será un ciclo

Se repite el ciclo hasta que el cambio en x sea mínimo

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

Reescribiendo el sistema con los términos de la diagonal aparte, se obtiene


X
Aii xi + Aij xj = bi i = 1, 2, ..., n
j6=i

Resolviendo para xi
n
1  X 
xi = bi − Aij xj i = 1, 2, ..., n
Aii j6=i

Esta expresión es el esquema de iteración que sigue el método de Gauss-Seidel:


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

∆x(k) = |x(k−1) − x(k) |

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:

1. Se realizan k iteraciones w=1

2. Se calcula ∆x(k)

3. Se relizan p iteraciones más

4. Se calcula ∆x(k+p)

5. Se computa wopt

6. Se realizan n iteraciones con wopt

142
import numpy as np
import math

#Función que resuelve un sistema Ax=b por el método de Gauss-Seidel.


#La función iterEqs(x,omega), que retorna el x mejorado, debe ser especificada
por el usuario y tol es la tolerancia.

def gaussSeidel(iterEqs,x,tol = 1.0e-9):


omega = 1.0 #Factor de relajación
k = 10 #Numero de iteraciones
p = 1 #Iteraciones adicionales
for i in range(1,501):
xOld = x.copy()

x = iterEqs(x,omega) #Se obtiene el x mejorado

dx = math.sqrt(np.dot(x-xOld,x-xOld)) #Se calcula el dx

if dx < tol: return x,i,omega #Si dx es menor que la tolerancia


se retorna x, la iteración y el factor
de relajación

# se calcula el coeficiente de relajación después de k+p iteraciones

if i == k: dx1 = dx #Si el numero de iteraciones i es igual a k,


se le asigna a dx1 el valor de dx

if i == k + p: #Si el numero de iteraciones i es igual a k+p,


se le asigna a dx2 el valor de dx
dx2 = dx

#Se calcula el factor de relajación


omega = 2.0/(1.0 + math.sqrt(1.0 - (dx2/dx1)**(1.0/p)))

#Si con estos métodos no se obtiene resultado se imprime el mensaje


print(’Gauss-Seidel failed to converge’)

En el siguiente ejemplo se encuentran las soluciones de un sistema de ecuaciones con 20


incógnitas por medio del método de Gauss-Seidel. El sistema escrito en forma matricial es:

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

x1 = ω(x2 − xn )/2 + (1 + ω)x1


xi = ω(xi−1 + xi+1 )/2 + (1 + ω)xi
xn = ω(1 − x1 + xn−1 )/2 + (1 + ω)xn
EL código que permite hallar las soluciones del sistema de ecuaciones es:
import numpy as np

#Función que obtiene el x mejorado


def iterEqs(x,omega):
n = len(x) #Longitud del arreglo

#Se calcula x_0 con la formula anterior


x[0] = omega*(x[1] - x[n-1])/2.0 + (1.0 - omega)*x[0]

#Se calculan x_i y x_n-1 con las otras dos fórmulas


for i in range(1,n-1):
x[i] = omega*(x[i-1] + x[i+1])/2.0 + (1.0 - omega)*x[i]
x[n-1] = omega*(1.0 - x[0] + x[n-2])/2.0 + (1.0 - omega)*x[n-1]
return x #Se retorna el x mejorado

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

print("\nNumber of iterations =",numIter) #Se imprimen las iteraciones


print("\nRelaxation factor =",omega) #Se imprime l factor de relajación
print("\nThe solution is:\n",x) #Se imprime la solución
Al aplicar el código se obtiene en IPython:
(’\nNumber of iterations =’, 256)
(’\nRelaxation factor =’, 1.6988309117973837)
(’\nThe solution is:\n’, array([ -4.50000000e+00, -4.00000000e+00, -3.50000000e+00,
-3.00000000e+00, -2.50000000e+00, -2.00000000e+00,
-1.50000000e+00, -9.99999998e-01, -4.99999997e-01,
2.62928994e-09, 5.00000003e-01, 1.00000000e+00,
1.50000000e+00, 2.00000000e+00, 2.50000000e+00,
3.00000000e+00, 3.50000000e+00, 4.00000000e+00,
4.50000000e+00, 5.00000000e+00]))

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

# Se define la función que implementa la reducción por gauss Jordan


def gaussJordan(a,b):

#Numero de filas de matriz


n = len(b)

# Primera fase de eliminación, para los valores por debajo de la diagonal


de pivotes
for k in range(0,n-1): #Para valores de k entre 0 y el numero de columnas
menos 1
for i in range(k+1,n): #Y para valores de i entre k+1 y el numero de
filas
if a[i,k] != 0.0: #Si el valor de la posición no es igual a cero

#Se calcula el factor lambda dado por el valor en la posición


sobre el valor en la fila pivote

lam = a [i,k]/a[k,k]

#El nuevo valor en la posición será el valor anterior menos el


factor lambda por el valor de la fila pivote. Esta operación se
realiza a toda la fila, desde la diagonal de pivotes hasta la
ultima columna

a[i,k:n] = a[i,k:n] - lam*a[k,k:n]


b[i] = b[i] - lam*b[k] #La resta se efectua tambien para las
constantes en b

# Segunda fase de eliminación, para los valores por encima de la diagonal


de pivotes

for k in range(n-1,0,-1): #Para valores de k entre uno y la longitud de


las columnas menos 1
for i in range(k-1,-1,-1): #Y para valores de i entre 0 y k-1

if a[i,k] != 0.0: #Si el valor de la posición no es cero

#Se calcula el factor lambda dividiendo el valor en la posición


entre el valor en la fila pivote
lam = a [i,k]/a[k,k]

##El nuevo valor en la posición será el valor anterior menos el


factor lambda por el valor de la fila pivote. Esta operación se
realiza a toda la fila, desde la diagonal de pivotes hasta la
ultima columna

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

# Generar pivotes con valor 1


for k in range(0,n): #Para k entre cero y el numero de filas(igual al de
columnas)

b[k] = b[k]/a[k,k] #El valor de las constantes sera el valor anterior


dividido en el valor del pivote en esa fila
a[k,k]=a[k,k]/a[k,k] #El valor del pivote pasa a ser uno ya que se
divide entre sí mismo.

return b #Retorna la solución del problema

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():

a=np.array([[4.0,-2.0,1.0],[-2.0,4.0,-2.0],[1.0,-3.0,4.0]]) #Se crea la matriz


b=np.array([11.0,-16.0,17.0]) #Se asignan las constantes
gaussJordan(a,b) #Se implementa la función de gauss-Jordan

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:

array([ 1. , -2.4, 2.2])

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 · · ·


.. .. .. ..
 
. . . .

Escriba un programa en Python que resuelva el sistema de ecuaciones Ax = b por Descomposición


Dolittle con

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

#Función que resuelve el sistema de ecuaciones de la matriz de Hilbert por


descomposición Dolittle
def DolittleH(n):

a=np.zeros((n,n)) #Arreglo de numpy de dimensiones n x n con ceros inicialmente


for i in range(n): #Se itera para i menor que n
for j in range(n): #Se itera para j menor que n

a[i,j]=1.0/(i+j+1) #La posición dada del arreglo se llena con los


valores correspondientes para formar la matriz de
Hilbert

b=np.zeros(n) #Se crea un arreglo de tamaño n para para guardar las constantes

for i in range(n): #Para i menor que n


suma=0 #Se crea una variable suma, con valor cero
for j in range(n): #Para j menor que n

if i!=j: #Si i es diferente de j (se evita adicionar las posiciones


con iguales subíndices, es decir, las diagonales)
suma+=a[i,j] #Se suman todos los valores del arreglo a en la
fila i

b[i]=suma #A cada posición del arreglo de constantes se le asigna el


resultado de la sumatoria correspondiente a la fila i

for k in range(0,n-1): #Para k menor que n-1


for i in range(k+1,n): #Para i entre k+1 y n, excluyendo a n

if a[i,k] != 0.0: #Si el valor de la posición no es igual a cero

lam = a [i,k]/a[k,k] #Lambda es equivalente a la división del


valor de la posición entre el valor del
pivote, con índices iguales
a[i,k+1:n] = a[i,k+1:n] - lam*a[k,k+1:n] #A todos los valores en
la fila i a la derecha
del pivote, se resta
lambda por los valores
en la fila del pivote

#Con esto se consigue la matriz triangular superior semejante a

147
la que se obtiene con el método de Gauss

a[i,k] = lam #A la posición se le asigna el valor lambda, ya


que la matriz triangular inferior (i>k) es
equivalente a los lambdas

for k in range(1,n): #Para k entre uno y n, excluyendo a n

b[k] = b[k] - np.dot(a[k,0:k],b[0:k]) #El arreglo de constantes se


modifica, restandole al valor
original la sumatoria de los
valores de las posiciones
multiplicadas por las
constantes k de cada fila

b[n-1] = b[n-1]/a[n-1,n-1] #El valor de la ultima posición del arreglo


de constantes es la división entre el valor
original entre el valor de a en la ultima fila
y columna

for k in range(n-2,-1,-1): #Para k entre 0 y n-1, recorriendo el rango


inversamente:
b[k] = (b[k] - np.dot(a[k,k+1:n],b[k+1:n]))/a[k,k] #El valor de k es
equivalente a restar del valor
original la sumatoria de valores
en la fila por los valores de x
correspondientes y dividir entre
el valor del pivote en la fila.

return b #Se retorna el resultado

Para encontrar el n a partir del cual aparecen seis cifras significativas de precisión, se
implementa la función anterior en IPython:

>>> #Con n=5 se obtienen soluciones sin cifras decimales


>>> n=5
>>> DolittleH(n)

array([ -4., 41., -125., 161., -69.])

Ahora se procede aumentando el n para ver la forma en que cambian las soluciones de la
matriz Hilbert

>>> #A partir de n=6 aparecen soluciones con ocho cifras decimales


>>> n=6
>>> DolittleH(n)

array([ -5. , 71. , -335.00000002, 721.00000005,


-699.00000006, 253.00000002])

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)

array([ -6.00000001, 113.00000021, -755.00000208, 2401.00000813,


-3849.00001494, 3025.00001294, -923.00000426])

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.

8.1. Interpolación polinomial


La forma más simple de interpolar es un polinomio. La interpolación polinomial se puede
expresar en series de potencias y por medio de la interpolación de Lagrange, la interpolación
de Newton y la interpolación de Neville. Un "polinomio de orden N que pasa a través de N + 1
puntos es único. Esto significa que, independientemente de la fórmula de interpolación, todas las

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

Resolver este sistema de ecuaciones no es aconsejable por:

"Se necesita un programa que resuelva un conjunto de ecuaciones lineales"[17]

"La solución de la computadora quizá no sea precisa. (Realmente, las potencias de x1 en la


ecuación pueden ser números muy grandes, y si es así, el efecto de los errores por redondeo
será importante)"[17].

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]:

Figura 8.1: Polinomio interpolante [16]

Cuando se aplica interpolación polinomial se cumple:

El problema tiene solución única, es decir, un único polinomio [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]

e(x) = f (x) − g(x) (8.1)

En la ecuación 8.1, f (x) es la función exacta y g(x) es el polinomio de orden N generado


por la interpolación. Como g(x) es exacta en los puntos xi a partir de los cuales se genera la
interpolación, e(x) es equivalente a

e(x) = (x − x0 )(x − x1 ) · · · (x − xN )s(x) (8.2)

Ahora se elige un valor η que satisface x0 < η < xN y se define una nueva función como [17]

p(x) = f (x) − g(x) − (x − x0 )(x − x1 ) · · · (x − xN )s(η) (8.3)

Al unir las ecuaciones 8.1 y 8.2, la expresión anterior se puede reescribir así

p(x) = (x − x0 )(x − x1 ) · · · (x − xN )[s(x) − s(η)]

La función p(x) se anula en los N + 1 puntos xi de la retícula, con i = 0, 1, 2 · · · , N y en


x = η. Además, “...todas las raices de p0 (x) están entre las dos raíces extremas de p(x)”[17] y
“...todas las raices de p00 (x) están entre las dos raíces extremas de p0 (x), y así sucesivamente
”[17]. Por lo tanto, derivando p(x) N + 1 veces en la ecuación 8.3 se obtiene:

pN +1 (x) = f N +1 (x) − 0 − s(η)N !

La derivada (N +1)-ésima de la función g(x) se anula devido a que la interpolación polinomial


genera una función de orden N . Si la raíz de la función pN +1 (x) es ξ, se cumple

pN +1 (ξ) = f N +1 (ξ)s(η)N ! = 0

Despejando s(ξ) se obtiene

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 )

La función V0 (x) es un polinomio de orden N de x, que se anula para x = x1 , x2 , · · · , xN .


Al dividir la expresión anterior entre V0 (x0 ) se obtiene:

(x − x1 )(x − x2 ) · · · (x − xN )
V0 (x) =
(x0 − x1 )(x0 − x2 ) · · · (x0 − xN )

Asimismo, se puede escribir una función Vi como:

(x − x0 )(x − x1 ) · · · (x − xN )
Vi (x) =
(xi − x0 )(xi − x1 ) · · · (xi − xN )

La función Vi (x) es un polinomio de orden N y toma el valor de uno en x = xi y de cero en


x = xj , j 6= i [17]. Al multiplicar V0 = (x), V1 (x), · · · , VN (x) por f0 , f1 , · · · fN y se suman estos
valores, se obtiene un polinomio de orden N e igual a fi para cada valor de i (desde i = 0 hasta
i = N ) [17].
Por lo tanto, la fórmula de interpolación por el método de Lagrange de orden N se escribe:

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 )

Esta expresión es equivalente a la ecuación 8.4.


“El polinomio de interpolación de orden N que se ajusta a N + 1 puntos es único. Esto es
importante, ya que indica que todos los polinomios de orden N que se ajustan a un conjunto
dado de N + 1 puntos son matemáticamente idénticos, aun cuando sus formas sean distintas
”[17].
Para demostrar la unicidad del polinomio de interpolación se parte de una hipótesis nula,
según la que la interpolación de Lagrange no es polinomio único [17]. Según esta hipótesis,
existe otro polinomio de orden N , que pasa por los mismo N+1 que el polinomio dado por la
expresión 8.4. Si el polinomio generado por la interpolación de Lagrange es g(x) y el polinomio
que cumple la hipótesis nula es k(x), entonces se tiene que

r(x) = g(x) − k(x)


El polinomio r(x) debe ser un polinomio de orden menor o igual a N , ya que es la resta
entre dos polinomios de grado N . Ahora, como g(x) y k(x) coinciden en N + 1 puntos, la resta
de estos dos polinomios genera un polinomio con N + 1 raíces. Por lo tanto, r(x) debe ser un
polinomio de orden N + 1. Esto se contradice con el hecho de que r(x) debe ser un polinomio
de grado menor o igual al grado de g(x) y k(x). Por lo tanto, se demuestra que el polinomio
generado por la interpolación de Lagrange debe ser único.
Aunque el método de Lagrange es conceptualmente simple, no es un algoritmo eficiente.
Aumenta la dificultad en el cálculo a medida que aumenta el grado n. La tecnología actual
permite manejar polinomios de grados superiores sin grandes problemas, a costa de un elevado
consumo de tiempo de computación. Los métodos siguientes son más eficientes en cuanto al
número de operaciones requeridas para obtener un polinomio interpolador [2].
El siguiente ejemplo permite encontrar el polinomio que se obtiene al interpolar tres pares
de datos implementando la fórmula del método de Lagrange. Se quiere interpolar una función
en el rango 1 ≤ x ≤ 3. Los datos de x y de f(x) son:

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

Al realizar las operaciones se obtiene el siguiente polinomio:

L(x) = 6x2 − 11x + 6

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.

8.1.2. Método de Newton


El método de Newton consiste en el desarrollo de una tabla de diferencias con el conjunto
dado de datos [17]. Mediante esta tabla se pueden escribir los polinomios interpolados con mucha
facilidad y, con respecto al método de Lagrange, el método de Newton supera las siguientes
dificultades [17]:

Cantidad de cálculos grande para la interpolación

Interpolaciones para distintos valores de x requieren la misma cantidad de cálculos adicionales,


ya que no se pueden utilizar cálculos previos.

Dificultades para evaluar el error

Cuando se desea encontrar el polinomio de interpolante o se desean encontrar numerosos


valores de la interpolación, la formula de la interpolación por el método de Newton es de gran
utilidad.
El método de interpolación de Newton se basa en las relaciones de recurrencia que se obtienen
al aplicar factorización sobre el polinomio interpolado, que tiene la forma

Pn (x) = a0 + (x − x0 )a1 + (x − x0 )(x − x1 )a2 + · · · + (x − x0 )(x − x1 ) · · · (x − xn )an

Factorizando por ejemplo para n = 3


!
 
P3 (x) = a0 + (x − x0 ) a1 + (x − x1 ) a2 + (x − x2 ) a3 + (x − x3 )

se puede llegar a la siguiente relación de recurrencia

P0 (x) = a3

P1 (x) = a2 + (x − x2 )P0 (x)

P2 (x) = a1 + (x − x1 )P1 (x)

156
P3 (x) = a0 + (x − x0 )P2 (x)

Esta relación de recurrencia se puede generalizar para un n arbitrario:

P0 (x) = an Pk (x) = an−k + (x − xn−k )Pk−1 (x) k = 1, 2, · · · , n

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

y2 = a0 + (x2 − x0 )a1 + (x2 − x1 )(x2 − x0 )a2

..
.
yn = a0 + (xn − x0 )a1 + · · · + (xn − x0 )(xn − x1 ) · · · (xn − xn−1 )an

Para resolver los an , se definen las diferencias divididas como

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

..
.

∇n−1 yn −∇n−1 yn−1


∇n yn = xn −xn−1

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

La tabla de diferencias que facilita la interpolación polinomial es:

yi −y0 ∇yi −∇y1 ∇2 yi −∇2 y2 ∇3 yi −∇3 y3


xi yi ∇yi = xi −x0 ∇2 yi = xi −x1 ∇3 yi = xi −x2 ∇4 yi = xi −x3
x0 y0
x1 y1 ∇y1
x2 y2 ∇y2 ∇ 2 y2
x3 y3 ∇y3 ∇ 2 y3 ∇ 3 y3
x4 y4 ∇y4 ∇ 2 y4 ∇ 3 y4 ∇ 4 y4

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):

a[i] = (a[i] - a[k-1])/(xData[i] - xData[k-1])

Inicialmente, a contiene las coordenadas y de los datos, de manera que es idéntica a la


segunda columna de la tabla. Cada paso a través del bucle externo genera las entradas de la
siguiente columna, que se sustituye a los elementos correspondientes a. Por lo tanto, en el último
paso del bucle externo, a contiene los términos diagonales de la tabla (es decir, los coeficientes
del polinomio).
Para encontrar el valor de y(x) para un x específico, se implementa el código anterior para
determinar los coeficientes del polinomio generado por interpolación y, como se concocen las
relaciones entre los coeficientes y las variables, se encuentra el valor de y(x). Las dos funciones
que resuelven esto son:

#Evalua el polinomio de Newton en x, que es un parámetro de entrada.


#a es el vector de coeficientes del polinomio que se halla con coeffts()

def evalPoly(a,xData,x):

n = len(xData) - 1 #Grado del polinomio


p = a[n] #Ultimo coeficiente del polinomio

for k in range(1,n+1):

#Se evalua el polinomio


p = a[n-k] + (x -xData[n-k])*p

return p #Se retorna el valor de f(x)

#Función que encuentra un vector de coeficientes del polinomio hallado por el


#método de interpolación de Newton

def coeffts(xData,yData):

m = len(xData) #Numero de puntos de datos


a = yData.copy()

#Se hallan los coeficientes


for k in range(1,m):

158
a[k:m] = (a[k:m] - a[k-1])/(xData[k:m] - xData[k-1])

return a #Se retorna el vector de coeficientes

Por ejemplo, si se desean interpolar los siguientes datos por el método de Newton:

x 0,15 2,30 3,15 4,85 6,25 7,95


y 4,79867 4,49013 4,2243 3,47313 2,66674 1,51909
 
Adicionalmente, los puntos pertenecen a la función f (x) = 4,8 cos πx 20 , con lo que se desea
comparar los valores obtenidos con la solución exacta y(xi ) = f (xi ). Para ello, se imprimen los
valores de la interpolación y los valores exactos con el siguiente código:

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])

#Se guarda el vector de coeficientes en a


a = coeffts(xData,yData)

#Se imprime el encabezado de las columnas


print(" x yInterp yExact")
print("-----------------------")

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)

#Se encuentra la solución exacta


yExact = 4.8*math.cos(math.pi*x/20.0)

#Se imprimen las columnas


print("{:3.1f} {:9.5f} {:9.5f}".format(x,y,yExact))

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.

8.1.3. Método de Neville


El método de interpolación de Newton incluyó dos pasos: el cálculo de los coeficientes,
seguido por la evaluación del polinomio. Funciona bien si la interpolación se lleva a cabo varias
veces para diferentes valores de x utilizando el mismo polinomio. Si sólo hay un punto que
interpolar, un método que calcula el interpolador en un solo paso, como por ejemplo el algoritmo
de Neville, es una opción más conveniente.
El método de interpolación de Neville interpola un valor particular con polinomios de grado
cada vez más alto (iniciando en grado cero)[16]. Para ello, se tiene el polinomio Pk de grado k
que pasa a través de los puntos k + 1

P0 [xi ] = yi

(x − xi+1 )P0 [xi ] − (xi − x)P0 [xi+1 ]


P1 [xi , xi+1 ] =
xi − xi+1

(x − xi+2 )P1 [xi , xi+1 ] − (xi − x)P1 [xi+1 , xi+2 ]


P2 [xi , xi+1 , xi+2 ] =
xi − xi+2

..
.

(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:

(x − x3 )P2 [x0 , x1 , x2 ] − (xi − x)P2 [x1 , x2 , x3 ]


P3 [x0 , x1 , x2 , x3 ] =
x0 − x3

“ El problema en el análisis anterior es la gran cantidad de polinomios que se deben evaluar, el


algoritmo de Neville precisamente automatiza esta tarea usando cálculos anteriores para obtener
el nuevo cálculo. El algoritmo de Neville no calcula P (x) sino que evalúa varios polinomios
interpolantes de Lagrange en un valor dado.” [16].
El siguiente algoritmo trabaja con una matriz unidimensional y, que contiene inicialmente los
valores de y de los datos (la segunda columna de la tabla). Cada paso a través del bucle externo
calcula los elementos de y en la siguiente columna, que sustituyen las entradas anteriores. Al
final del procedimiento, y contiene los términos diagonales de la tabla. El valor del interpolador
(evaluado en x) que pasa a través de todos los puntos de datos es el primer elemento de y.

# 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):

#Se encuentra el polinomio y se evalua x

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-k] - xData[k:m])

161
return y[0] #Retorna el valor de y(x)

Por ejemplo, si se tienen los siguientes puntos:

x 4,0 3,9 3,8 3,7


y −0,06604 −0,02724 0,01282 0,05383

y se quiere determinar la raíz de y(x) = 0, se puede utilizar interpolación inversa, reemplazando


los valores de x y y para hallar el valor de x que se obtiene cuando y = 0:

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])

#Se aplica la función de interpolación inversa


neville(yData,xData,0)

Aplicando el código anterior se encuentra la raiz, que es igual a 3.8317035597236631.


La interpolación polinómica debe llevarse a cabo con un número factible menor al número
de datos. De tres a seis puntos del vecino más cercano producen buenos resultados en la mayoría
de los casos. Un interpolador de más de seis puntos puede ser erróneo ya que los puntos de datos
que están lejos del punto de interés no contribuyen a la exactitud de la interpolador.

8.2. Funciones Racionales


Algunos datos son mejores interpolar por funciones racionales que polinómicas. Una función
racional R(x) se puede expresar como el cociente de dos funciones polinómicas.

Pm (x) a1 xm + a2 xm−1 + · · · + am x + am+1


R(x) = =
Qn (x) b1 xn + b2 xn−1 + · · · + bn x + bn+1

En esta expresión m y n denotan los grados máximos de los polinomios en el numerador y


en el denominador, respectivamente [22]. Usualmente bn+1 = 1, por lo tanto hay que determinar
n + m + 1 coeficientes.
Es necesario que los coeficientes ai y bj de la función R(x) resuelvan el siguiente sistema de
ecuaciones lineales homogéneo [22]:

Pm (xi ) − R(xi )Qn (xi ) = 0 i = 1, 2, · · · , m + n + 1

Así, el sistema completo es:

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

El sistema de ecuaciones que se obtiene con los datos anteriores es:

a0 − b0 = 0
a0 + a1 − 2(b0 + b1 ) = 0
a0 + 2a1 − 2(b0 + 2b1 ) = 0

La solución al sistema de ecuaciones anterior es:

a0 = 0 b0 = 0 a1 = 2 b1 = 1

Por lo tanto, la expresión racional es:


2x
R(x) =
x
Si x es igual a cero, se presenta una indeterminación, por lo que la solución del sistema de
ecuaciones excluye el punto en el cuál x = 0. Al simplificar la expresión racional se obtiene:

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].

8.2.1. Función diagonal racional


La función racional diagonal es tal que m = n y la ventaja de usar esta función es que se
puede computar usando un algoritmo tipo Neville.
La función recursiva será

R[xi+1 , xi+2 , · · · , xi+k ] − R[xi , xi+1 , · · · , xi+k−1 ]


R[xi , xi+1 , · · · , xi+k ] = R[xi+1 , xi+2 , · · · , xi+k ]+
S

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 ]

k = −1 k=0 k=1 k=2 k=3


x1 0 R[x1 ] = y1 R[x1 , x2 ] R[x1 , x2 , x3 ] R[x1 , x2 , x3 , x4 ]
x2 0 R[x2 ] = y2 R[x2 , x3 ] R[x2 , x3 , x4 ]
x3 0 R[x3 ] = y3 R[x3 , x4 ]
x4 0 R[x4 ] = y4

Por ejemplo, se tienen los siguientes datos:

x 0 0,6 0,8 0,95


y 0 1,3764 3,0777 12,7062

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.

k = −1 k = 0 k=1 k=2 k=3


i=1 0 0 0 0 0,9544 1,0131
i=2 0,6 0 1,3764 1,0784 1,0327
i=3 0,8 0 3,0777 1,2235
i=4 0,95 0 12,7062

R[x2 ]−R[x1 ] x−x1 R[x2 ]−R[x1 ] 


R[x1 , x2 ] = R[x2 ] + S S= x−x2 1− R[x2 ]−R[x2 ,x1 ] −1

1,3764−0 0,5−0 1,3764−0 


R[x1 , x2 ] = 1,3764 + S S= 0,5−0,6 1− 1,3764−0 −1
R[x3 ]−R[x2 ] x−x2 R[x3 ]−R[x2 ] 
R[x2 , x3 ] = R[x3 ] + S S= x−x3 1− R[x3 ]−R[x3 ,x2 ] −1

3,0777−1,3764 0,5−1,3764 3,0777−1,3764 


R[x2 , x3 ] = 3,0777 + S S= 0,5−3,0777 1− 3,0777−0 −1

El código que implementa el método anterior es:

import numpy as np

#Función que evalua en x la diagonal de la interpolación racional.

def rational(xData,yData,x):

m = len(xData) #Numero de datos


r = yData.copy()
rOld = np.zeros(m)

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

if abs(x - xData[i+k+1]) < 1.0e-9:

return yData[i+k+1]

else:

#Se aplican las fórmulas anteriores

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]

return r[0] #Se retorna el valor de la función en x.

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.

x 0,1 0,2 0,5 0,6 0,8 1,2 1,5


y −1,5342 −1,0811 −0,4445 −0,3085 −0,0868 0,2281 0,3824

Para ello, se implementa el siguiente código:

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])

#Arreglo de numeros entre 0.1 y 1.55 distanciados por 0.05


x = np.arange(0.1,1.55,0.05)

#Tamaño del arreglo anterior


n = len(x)

y = np.zeros((n,2)) #Arreglo de nx2 con ceros

#Se implementa interpolación racional y


#polinomial

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])

#Se grafican las dos interpolaciones


plt.plot(xData,yData,’o’,x,y[:,0],’-’,x,y[:,1],’--’)

#Se adiciona label en el eje x


plt.xlabel(’x’)

#Se adiciona legenda


plt.legend((’Data’,’Rational’,’Neville’),loc = 0)

#Se muestra la gráfica


plt.show()

El resultado del código anterior se presenta en la figura 8.2.


Como se observa en la figura 8.2, las dos interpolaciones son muy similares. La interpolación
por el método de función racional aparece con una línea continua y el método de interpolación
de Neville aparece con una línea discontinua. Para valores de x menores que 1.2 las dos
interpolaciones son similares. Sin embargo, la interpolación polinomial tiende a oscilar más
entre los valores finales. Así, para los valores de x extremos la interpolación racional es más
adecuada.

166
Figura 8.3: Ilustración de la división por segmentos en la interpolación segmentaria cúbica

8.3. Interpolación segmentaria cúbica


Si hay más de un par de puntos de datos, una segmentación cúbica es difícil de superar
como un interpolador global. Es más confiable que la polinomial en el sentido de que tiene
menos tendencia a oscilar entre puntos de datos. Además, la interpolación de Lagrange o de
Newton con polinomios de orden alto tienden a generar errores mayores [17].
En la interpolación por segmentación cúbica "se usa un polinomio cubico en cada intervalo
entre dos puntos consecutivos. Un polinomio cubico tiene cuatro coeficientes, por lo que requiere
cuatro condiciones. Dos de ellas provienen de la restricción de que el polinomio debe pasar por
los puntos en los dos extremos del intervalo. Las otras dos son las condiciones de que la primera
y segunda derivadas del polinomio sean continuas en cada uno de los puntos dados."[17]
Las interpolaciones generadas con el método de interpolación segmentaria cúbica se aplican
en gráficas, como la que se presenta en la figura 8.3, y en métodos numéricos. Por ejemplo,
las funciones obtenidas por segmentación se usan como funciones de prueba relacionadas con el
método de Rayleigh-Ritz-Galerkin para resolver problemas de contorno de ecuaciones diferenciales
ordinarias y parciales [22].
Denotando la segunda derivada de la curva en el nudo i por ki , por continuidad requiere

00 00
fi−1,i (xi ) = fi,i+1 (xi ) = ki

k0 = kn = 0

Usando interpolación de Lagrange para dos puntos

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

remplazando estos valores en la segunda derivada se obtiene

00 ki (x − xi+1 ) − ki+1 (x − xi )
fi,i+1 (xi ) =
xi + xi+1

Al integrar esta expresión dos veces con respecto a x, se tiene

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 )

Imponiendo la condición fi,i+1 (xi ) = yi , el término que acompaña a B se cancela, ya que se


obtiene (xi − xi ). La expresión resultante es:

ki (xi − xi+1 )3
+ A(xi − xi+1 ) = yi
6(xi − xi+1 )

Al despejar A de esta expresión y simplificando los binomios (xi − xi+1 ) se obtiene

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

Reemplazando los valores obtenidos de A y B en la ecuación 8.5 se encuentra la expresión


que describe la curva:

" # " #
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 )

Esta condición es equivalente a

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

Este es un sistema de ecuaciones con 3 incógnitas que se puede resolver.


Si los datos están eventualmente espaciados por un intervalo h

6
ki−1 + 4ki + ki+1 = (yi−1 − 2yi + yi+1 )
h2

La primera etapa de la segmentación cúbica es resolver las curvaturas desconocidas (recordemos


que k0 = kn = 0). Esta tarea es llevada a cabo por la función “curvatures”. La segunda
etapa es interpolar x hallando fi,i+1 . Este paso se puede repetir cualquier número de veces
con diferentes valores de x utilizando la función “evalSpline". La función “findSegment” en
“evalSpline” encuentra el segmento de la curva que contiene x usando el método de bisección.
El resultado de esta función es el número de segmento; es decir, el valor de la subíndice i. El
código completo se presenta a continuación:

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

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

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

for k in range(1,n): #Para k entre 1 y n, excluyendo a n

#Se operan los valores de b


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): #Para k entre 0 y n-2

#Se hallan las soluciones


b[k] = (b[k] - e[k]*b[k+1])/d[k]

return b #Se retornan las soluciones

# 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):

n = len(xData) - 1 #Longitud de los datos en x menos 1

c = np.zeros(n) #Arreglo con n ceros


d = np.ones(n+1) #Arreglo con n+1 unos
e = np.zeros(n) #Arreglo con n ceros
k = np.zeros(n+1) #Arreglo con n+1 ceros

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

#Se hallan las curvaturas

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) #Se aplica la función para encontrar los valores de las


diagonales

170
LUsolve3(c,d,e,k) #Se resuelve el sistema

return k #Se retornan las curvaturas

#La función "findSegment" en "evalSpline" encuentra el segmento de la curva


que contiene x usando el método de bisección.
#Devuelve el número de segmento; es decir, el valor de la subíndice i.

def evalSpline(xData,yData,k,x):

def findSegment(xData,x):

iLeft = 0
iRight = len(xData)- 1

while 1:

#Si el valor a la derecha es mayor que a la izquierda,


# se devuelve el valor iLeft

if (iRight-iLeft) <= 1:
return iLeft

i =(iLeft + iRight)/2

if x < xData[i]:
iRight = i

else:
iLeft = i

i = findSegment(xData,x) #Se encuentra el segmento

h = xData[i] - xData[i+1] #Espacio del intervalo

#Se halla el numero del segmento

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 #Retorna el segmento

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)

#Se encuentran las curvaturas


k = curvatures(xData,yData)

#Se encuentra el valor de y para x=1.5

x=1.5
print("Para x=1.5, y",evalSpline(xData,yData,k,x))

#Se encuentra el valor de y para x=4.5

x2=4.5
print("Para x=4.5, y",evalSpline(xData,yData,k,x2))

Con el código anterior se obtiene:

(’Para x=1.5, y’, 0.76785714285714279)


(’Para x=4.5, y’, 0.76785714285714279)

8.4. Ejercicios
8.4.1. Lagrange
El ejercicio consiste en hallar el cero de la función y(x) de los siguientes datos

x 0 0,5 1 1,5 2 2,5 3,0


y 1,8421 2,4694 2,4921 1,9047 0,8509 −0,4112 1,5727

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:

%pylab inline #Importar numpy y matplotlib

#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):

n = len(xData) #Número de datos

172
p=0 #Variable para guardar el polinomio evaluado en x

for i in range(n):

#Variable para almacenar los productos


producto=1

for j in range(n):

#Se aplica la fórmula de Lagrange

if(i!=j): #Se omiten los datos con mismo subíndice

#Se encuentran los polinomios de Lagrange


producto*=((x-xData[j]) / (xData[i]-xData[j]))

p+=producto*yData[i] #Se suman los polinomios

return p #Retorna el valor del polinomio evaluado en x

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:

>>> xData = np.array([1.5,2.0,2.5,3.0])


>>> yData = np.array([1.9047,0.8509,-0.4112,-1.5727])
>>> lagrange(yData,xData,0)

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

#Función que permite interpolar los datos en x y en y para encontrar el valor


#y(x) para un valor x.

def neville(xData,yData,x):

m = len(xData) #Número de datos


y = yData.copy()

#Se aplica la formula para encontrar la diagonal de términos. Los nuevos


valores sustituyen los anteriores

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-k] - xData[k:m])

return y[0] #Retorna el valor en y de x.

#Se crean los arreglos de numpy

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

neville(xData,yData,0.7692) #Se aplica la función

Como resultado del código anterior se obtiene el valor en y de 2.557101391087488, correspondiente


a x = 0,7692.

174
9

Ajuste de curvas en Python

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.

9.2. Mínimos Cuadrados


En ocaciones distintos modelos de ciencia e ingienería expresan sus resultados en conjuntos
de (x1 , y2 ), (x2 , y2 )......(xn , yn ). De este modo un modelo acertado para el uso de ajuste por
mínimos cuadrados, usa la expresión :

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]

m = [1/D]Σni=1 (xi − (x))yi

b = y − mx

En donde x e y representan los valores medios de los datos xi e yi , respectivamente

x = 1/nΣni=1 [xi ]

y = 1/nΣni=1 [yi ]

El valor de D necesario para los cálculos previos se define como:

D = Σni=1 [(xi − (x))2 ]

Los errores absolutos correspondientes a la pendiente εm , y a la ordenada en el origen εb son:


s
Σni=1 [d2i ]
εm ≈ (1/D)
n−2

s
Σni=1 [d2i ]
εb ≈ [(1/n) + (x2 /D)]
n−2

En donde di = yi − mxi − b

Dado que la pendiente m y la ordenada b en el origen de cualquier recta se asocian comunmente


con variables y magnitudes de interes físico, tras la realización de las operaciones previamente
mostradas se obtendran valores de m y b junto con sus respectivos errores m ± εm y b ± εm
De este modo, en la representación grafica apareceran los puntos experimentales (xi , yi ), junto
con sus respectivos errores absolutos. La ecuación de recta (y = mx + b) ajustada or el método
de mínimos cuadrados aparecera superpuesta a los datos experimentales.
Ya que el objetivo de este texto es mostrar al usuario una guía para la realización de problemas
en forma de codigo para distintos lenguajes, se presentara a continuación un ejemplo con pasos
algebraicos basicos para el entendimiento del tema:

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

1.Grafique los puntos en un plano coordenado.

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

5.Use la fórmula para calcular la intercepción en y.

b = ȳ − mx̄ = 7,0 − (−1,1 ∗ 6,4) = 7,0 + 7,04 ≈ 14

6.Use la pendiente y la intercepción en y para formar la ecuación de la recta


que mejor se ajusta.

La pendiente de la recta es -1.1 y la intercepción en y es 14.0. Por lo tanto, la ecuación es :

y = −1,1x + 14,0.

Dibuje la recta en la gráfica de dispersión.

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 .

¿Cómo obtener un polinomio de interpolación?

Hay la existencia de un teorema que demuestra la existencia y unicidad del polinomio de


interpolación para cada conjunto de datos

Dados n + 1 puntos de R2 (x0 , y0 ) , (x1 , y1 ), ...., (xn , yn ) se estipula un sistema de ecuaciones


como el que se muestra a continuación:

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]

Aproximar la fución f (x)cos(x) usando los puntos o nodos x0 = −π/2, x1 = 0 y x2 = π/2.


De acuerdo a estos datos, podemos encontrar un polinomio de segundo grado para aproximar
la función indicada.

1. Se establece un sistema de ecuaciones con los puntos dados

Sabiendo que:

f (x0 ) = 0

f (x1 ) = 1

f (x2 ) = 0

Se establece el siguiente sistema de ecuaciones:

a0 + a1 −π −π 2



 2 + a2 ( 2 ) = 0



a0 + a1 0 + a2 02 = 1




a0 + a1 π2 + a2 ( −π 2

2 ) =0

2. Se plantea el sistema de ecuaciones en forma matricial

−π π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

4. Se interpola la solución de la matriz escalonada reducid por renglones y se obtiene el polinomio


de interpolación de grado 2

a0 = 1
a1 = 0
4
a2 = − 2
π

De modo que:
4 2
P(x) = 1 − [( )x ]
π2

5. Se interpola y comparan las gráficas

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()

De este modo se procedera a mostrar un ejemplo para el usos de este método.

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.

x −0,04 0,93 1,95 2,90 3,83 5,00 5,98 7,05 8,21


9,08 10,09
y −8,66 −6,44 −4,36 −3,27 −0,88 0,87 3,31 4,63 6,19
7,40 8,85
Desarrollo

#!/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)

Se procede imprimir la función obtenida

%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

El calor específico Cp de alumiio depende de la temperatura T como indica la siguiente tabla :

T(C) -250 -200 -100 0 100 300


Cp(kJ/Kg*k) 0.0163 0.318 0.699 0.870 0.941 1.04

Grafique la función resultante usando interpolacion polinomial y racional en el intervalo T=


-250C a T = 500C. Comente los resultados.

Primera Parte

def neville(xData,yData,x): #Funcion interpolacion neville


m = len(xData)
# variable definida como la longitud de la cantidad de objetos en x
y = yData.copy()
# el comando .copy’()’ devuelve una copia de los datos del arreglo
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-k]
return y[0]
#proceso neville
def rational(xData,yData,x):
m = len(xData)
r = yData.copy()
rOld = np.zeros(m)
for k in range(m-1):
for i in range(m-k-1):
if abs(x - xData[i+k+1]) < 1.0e-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]
return r[0]
#procedimiento racionales

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])

#For permitira recorrer todos los datos en n


#se le asigna a y una uncion y una posicion de orden
plt.plot(xData,yData,’o’,x,y[:,0],’-’,x,y[:,1],’--’)
plt.xlabel(’T(c)’)
plt.ylabel(’Cp(kJ/Kg*K)’)
plt.legend((’Data’,’Rational’,’Neville’),loc = 0)
plt.show()

9.3.2. Ejercicio 2
Usando los datos :

X 0 0.0204 0.1055 0.241 0.582 0.712 0.981


Y 0.385 1.04 1.79 2.63 4.39 4.99 5.27

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

RE 0.2 2 20 200 2000 200000


CD 103 13.9 2.72 0.800 0.401 0.433

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

se ajuste a los siguientes datos, usando mínimos cuadrados.

X 0.5 1 1.5 2.0 2.5


Y 0.49 1.60 3.36 6.44 10.16

Primera Parte

def minimos (x,y):


n = len(x)
#Defino longitud
lnx = log(x)
lny = log(y)
#defino variables logaritmicas
xprom = ((1.0/n)*np.sum(lnx))
yprom = ((1.0/n)*np.sum(lny))
#defino variables promedio
sumy = np.sum(lny)
xx = np.sum(lnx*lnx)
xy = np.sum(lnx*lny)
# Defino funciones vectoriales
f1= ((yprom*xx)-(xprom*xy))/(xx-(n*xprom*xprom))
f2= (xy-(xprom*sumy))/(xx-(n*xprom*xprom))
f3= np.exp(f1)
#defino operaciones

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)

Explicación punto 5: La función de mínimos sirve para la función algebraica


y = a + bx
Sin embargo para
f (x) = axb
Se declaran variables que den como resultado arreglos logarítmicos para las entradas de x y.
De modo que quede la expresión
ln(y) = ln(a) + b(ln(x))
De esta manera se aplican las condiciones de minimizar los datos sobre los valores de log. Con
la variable f3 busco sacarle la exponencial a a para cancelar el log.

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

En lugar de una linea recta, la función de ajuste ahora representa a un plano:


f (x, y) = a + bx + cy

Demuestre que las ecuaciones para los coeficientes son :

 
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

Para Ajustar los siguientes datos:

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

para un f(x,y)=a+b+cy a= 1.57010321449 b= -0.550868216008 c= 0.101760717779

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.

10.0.1. Búsqueda incremental


Este método se basa en la suposición de que al los valores de la función a cada lado de la
raíz tienen diferentes signos. Por ejemplo: 1

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

from numpy import sign


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
f2 = f(x2)
else:
return x1,x2

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.

si f (a)f (c) = 0 ya se encontró una raíz

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 :

-Después de la primera iteración ∆x se reduce a ∆x/2

-Después de la segunda iteraciones x/2 se reduce a ∆x/22


-Por lo tanto con n iteración ∆x = /2 n lo cual debe ser igual a ε. De esta manera,
despejando del n el resultado sera:

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.

El código en numpy para este método es:

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

La parte del código:

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.

x1: Límite inferior del intervalo (Según lo explicado . a ").

x2: límite superior del intervalo (Según lo explicado "b").

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’)

Luego, se establece el número de iteraciones, con la fórmula explicada previamente y este


es usado para definir el rango del recorrido que hará la bisección. Dentro de este recorrido
se crea el valor x3 (ç. en la explicación previa) es decir la mitad del intervalo y se evalua
en la función.Con las líneas de código:

if (switch == 1) and (abs(f3) > abs(f1)) and (abs(f3) > abs(f2)):


return None

El programa se asegura que el valor absoluto de la función evaluada en x3 sea menor al


valor absoluto de la función evaluada en x1 y x2 y por lo tanto que exista una raíz en ese
intervalo.
Si lo dicho anteriormente se cumple el programa procede a establecer si x3 es una raíz,
si no lo es compara los signos de f3 con f2 (valor de la función evaluada en el extremo
superior del intervalo) si no son iguales, según el teorema de Bolzano hay una raíz entre
estos valores y por lo tanto el programa reduce a la mitad el intervalo al establecer x3
como límite inferior del intervalo con las siguientes líneas de código:

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 .

10.0.3. Método Ridder (Falsa Posició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).

El método se describe con el usos de dos valores de variables independiente x1 y x2 , los


cuales estan en dos lados opuestos de la raíz buscada. El método comienza evaluando la
función en el punto medio x3 . Posteriormente se encuentra una unica función exponencial
de forma :
ea x
Donde x puede ser x1 , x2 , x3 ....xn

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:

Como se indico previamente x3 corresponde al punto medio de los valores agrupados.Dicho


esto se introduce la siguiente función:

G(x) = f (x)e( (x − x1 ) ∗ Q)

Donde la constante Q se identifica al requerir los puntos (x1 , g1 ) , (x2 , g2 ) y (x3 , g3 )


formando asi una linea recta como se indica a la derecha de la imagén. Entrando en
detalles podemos ver que de la ecuación previa es posible estipular lo siguiente:

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

Lo que implica que:


1
f3 e( hQ) = (f1 + f2 e( 2hQ))
2

Lo que corresponde una la ecuación cuadrática en e( hQ), cuya solución es:


q
f3 ± f32 − f1 f2
e( hQ) =
f2

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

10.0.4. Métodos De Newton-Raphson para sistemas de n ecuaciones no


lineales

El método de Newton-Raphson es un método iterativo que permite aproximar la solución


de una ecuación en forma f(x)=0. Empezando con una estimación inicial de la respuesta
x0 , y a su vez construyendo una sucesión de aproximaciones de forma recurrente mediante
la fórmula:[18]

f (xi )
x( i + 1) = xi −
f 0 (xi )

Se procedera a Mostrar un ejemplo para la aplicación algebraica del método. Considere


la ecuacion:

206
1
ex =
x

Se puede observar que la incognita x es imposible de depejar, sin embargo representando


las curvas y = ex , y = x1 en el intervalos x ∈ [0, 4] es notorio que la ecuación solo tiene
una solución en dicho intervalo.
Al aplica el método de Newton-Raphson, se siguen los siguientes pasos:

1. Se expresa la ecuación de forma f(x)=0, y se identifica la función


1
f (x) = ex −
x

2. Se calcula la derivada de la función


1
f 0 (x) = ex +
x2

3. Se construye la fórmula de recurrencia

e( xi ) − 1/xi
x( i + 1) = xi −
e( xi ) − 1/x2j

4. Se toma una estimación lineal para la solución Si x0 = 1,0

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

Tomando como solución x = 0,567143

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

f (xi+1 ) = f (xi ) + f 0 (xi )(xi+1 − xi ) + O(xi+1 − xi )2

207
Si xi+1 es una raíz, fx+1 = 0

La fórmula de Newton Raphson

f (xi )
xi+1 = xi −
f 0 (xi )

Dicho esto el modulo que corresponde al método de Newton-Raphson para python es


:

def newtonRaphson(f,df,a,b,tol=1.0e-9):

from numpy import sign

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’)

El método de Newton-Raphson implica como parametros de función una raíz en un rango


[a.b] para un f(x) y su respectiva derivada. El parametro tol implica una variable que de
no ser modificado al llamar la función tomara ese valor.

208
A modo de ejemplo se presentara las raíces para una función f(x):

x4 − 6,4x3 + 6,45x2 + 20,538x − 31,752

Defino las funciones

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)

Respuesta: la raiz para la funcion definida es : 4.0

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

La trayectoria de un satélite orbitando la tierra es:


C
R=
1 +  ∗ sin(θ − α)

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

#30 grados = pi/6


#pos[0]=c
#pos [1] = e
#pos [2] = alf
# primeras ctes = R(km)
#eq = 0

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

print ("Los valores de las variables de R=c/(1+(e*sin(theta+alfa))) son: ")


print (’R min =’,R)
print (’theta =’,t)
print (’a=’,a)
print (’c=’,c)
print (’e=’,e)

Imprime

Los valores de las variables de R=c/(1+(e*sin(theta+alfa))) son:


(’R min =’, 6553.2391070067752)
(’theta =’, 1.2300123288444442)
(’a=’, 0.34078399795045233)
(’c=’, 6819.2937932078721)
(’e=’, 0.040598959057762597)

10.1.2. Ejercicio 2

Use el método de bisección para computar:


√3
75

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

La raíz de f(x)= x3 − 75 es x = 4,2172

10.1.3. Ejercicio 3

Determine todas las raíces de:

x4 + 0,9x3 − 2,3x2 + 3,6x − 2,52 = 0

Primera Parte

from numpy import sign

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

The roots are:


-3.00000000037
2.09999999963

10.1.4. Ejercicio 4

Halle las raíces de:


xsin(x) + 3cos(x)

con el método de Ridder en el intervalo de (-6,6).

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

(’root =’, -4.7123889803846462)


(’root =’, -3.2088387319811895)
(’root =’, 1.5707963267948533)

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

fi : La i-esima frecuencia natural


m : la masa de la viga
L : La longitud de la viga
E : La constante elástica
I : El momento de inercia de las sección transversal

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

bis1 = bisection(fun, 0.0, 4.0, tol = 1.0e-4)


bis2 = bisection(fun, 3.0, 6.0, tol = 1.0e-4)
#Define las raices con determinadas condiciones
#las defleccione;por ende lospuntos se entienden viendo la grafica

print ("Las frecuencias y raices respectivas son")


print("Frec 1 =", funcmin(bis1), "Raiz 1=", bis1)
print("Frec 1 =", funcmin(bis2), "Raiz 1=", bis2)
#imprime las frecuencias con las raices calculadad

218
x=linspace(-6,6,1000)
y=fun(x)
plot(x,y)
#grafica de funcion
#Se observan puntos de defleccion, para ver raices

Imprime

Las frecuencias y raices respectivas son


(’Frec 1 =’, 0.0, ’Raiz 1=’, 1.875091552734375)
(’Frec 1 =’, 0.0, ’Raiz 1=’, 4.6940460205078125)

219
220
11

Métodos de derivación

11.1. Métodos de diferencias finitas


Con estos métodos se aproxima la derivada de una función mediante diferencias; esto da como
resultado expresiones algebraicas de las derivadas parciales con las cuales se puede solucionar.
Estas diferencias se construyen al sustituir cada operador diferencial por su respectiva ecuación
de diferencias.

Teniendo en cuenta que la expresión matemática para f 0 (x) es:

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)

Expandiendo f (x) alrededor de x, siendo x = x0 + h y con h = x − x0 como la longitud


del intervalo en el que se hace la aproximación:

f 00 (ξ)h2
f (x0 + h) = f (x0 ) + hf 0 (x0 ) + (11.1)
2

x0 < ξ < x 0 + h

Se despeja f 0 (x0 ) de la ecuación 11.1:

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

Si en la ecuación 11.2, h > 0, la ecuación corresponde al análisis progresivo de la derivada y es


representado por la siguiente gráfica:

Figura 11.1: Obtenida de [23]

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.

La siguiente tabla muestra hasta la cuarta derivada usando este caso:

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á:

f (x)−4f (x+h)+6f (x+2h)−4f (x+3h)+f (x+4h)


f (4) (x) = h4

Trasladando lo anterior a un método iterativo, se toma x como xi , y como h es la distancia


entre los puntos es decir xi+1 − xi . Por lo tanto la primera derivada sería:

−f (xi )+f (xi +(xi+i −xi ))


f 0 (xi ) = xi+1 −xi

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 ):

f (x) − f (x0 ) f 00 (x0 )h f 000 (ξ)h2


f 0 (x0 ) = − − (11.3)
h 2! 3!

Los tres puntos usados son:

x0 , x1 = x0 + h y x2 = x0 + 2h

En la ecuación 11.3 se reemplaza x por x1

f (x0 +h)−f (x0 ) f 00 (x0 )h f 000 (ξ)h2


f 0 (x0 ) = h − 2! − 3!

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

Multiplicando 11.4 por -2 y restandole esto a 11.5 da como resultado :

f (x0 + 2h) − 2f (x0 + h) = −f (x0 ) + f 00 (x0 )h2 (11.6)

Despejando f 00 (x0 )

f (x0 ) − 2f (x0 + h) + f (x0 + 2h)


f 00 (x0 ) = (11.7)
h2

Reemplazando 11.7 en 11.4

−3f (x0 ) + 4f (x0 + h) − f (x0 + 2h)


f 0 (x0 ) = + Oh2 (11.8)
2h

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)

print ’primera derivada en 2.36 es:’,a, ’|’,’segunda derivada en 2.36 es:’,b,

#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.

Siguiendo el ejemplo anterior se puede computar la k-ésima derivada.

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.

11.1.2. Diferencias finitas regresivas

Si en la ecuación 11.2 h < 0 se da el análisis regresivo de la aproximación finita, el cual es dado


por la ecuación:

f (x) − f (x − h)
f 0 (x) = (11.9)
h

Esta ecuación representa el primer caso de diferencias finitas regresivas.

La representación gráfica de este análisis es:

Figura 11.4: Obtenida de [23]

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á:

f (x0 − 2h) − 4f (x0 − h) + 3f (x0 )


f 0 (x0 ) = + Oh2 (11.10)
2h

226
La tabla, hasta la cuarta derivada, en el primer caso será:

Figura 11.5: Primer caso regresivo

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 .

Entonces la cuarta derivada en este caso será:

f (x)−4f (x−h)+6f (x−2h)−4f (x−3h)+f (x−4h)


f (4) (x) = h4

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.

De manera iterativa la primera derivada en el punto xi será:

f (xi )−f (xi −(xi −xi−1 ))


f 0 (xi ) = xi −xi−1

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)

#La longitud de arreglo es 4, pero las posiciones se cuentan desde 0


#por lo tanto la posición del último dato será 3
i=n-1

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:

Figura 11.6: Segundo caso regresivo

La segunda derivada construida con la tabla anterior será:

2f (x)−5f (x−h)+4f (x−2h)−f (x−3h)


f 00 (x) = h2

Pasando la ecuación anterior a modo iterativo:

2f (xi )−5f (xi−1 )+4f (xi−2 )−f (xi−3 )


f 00 (x) = xi −xi−1

Lo anterior se puede aplicar a numpy de la siguiente manera:

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

11.1.3. Diferencias finitas centrales

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) f (x)−f (x−h)


h − Oh + h + Oh

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.

La representación gráfica de este método es:

Figura 11.7: Obtenida de [23]

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

(Ambas ecuaciones desprecian el termino del error)

Sumando la ecuación 11.4 y la ecuación 11.12:

f (x + h) + f (x − h) = 2f (x) + h2 f 00 (x) (11.13)

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.

f 00 (x)(2h)2 (2h)3 f 000 (x) (2h)4 f (4) (x)


f (x + 2h) = f (x) + f 0 (x)2h + + + .. (11.15)
2 3! 4!

f 00 (x)(2h)2 (2h)3 f 000 (x) (2h)4 f (4) (x)


f (x − 2h) = f (x) − f 0 (x)2h + − + .. (11.16)
2 3! 4!

Restando las dos ecuaciones tenemos:

8h3 f 000 (x)


f (x + 2h) − f (x − 2h) = 4hf 0 (x) + (11.17)
3
Sumando 11.15 y 11.16:

4h4 f (4) (x)


f (x + 2h) + f (x − 2h) = 2f (x) + 4h2 f 00 (x) + (11.18)
3

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:

f (x + 2h) − 4f (x + h) + 6f (x) − 4f (x − h) + f (x − 2h)


f (4) (x) = + Oh2 (11.20)
h4

La tabla en la que se encuentran representadas estas derivadas es:

230
Figura 11.8: Derivadas finitas centradas

El siguiente ejemplo muestra la aplicación a numpy de la ecuación anterior.Se debe encontrar


la segunda derivada de la función e−x en x = 1 con h = 0,64.

#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.

11.2. Extrapolación Richardson


Este método permite una mayor precisión en la búsqueda de la derivada de función ya que tiene
en cuenta el término del error (O) dado en las ecuaciones anteriores como resultado de expandir
los términos f (x + h) y f (x − h) como series de Taylor y al crear la expresión de diferencias
centradas:
f (x+h)−f (x−h)
2h

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

Donde k1 , k2 , .. kn son independientes de h.

Esta extrapolación se basa en la dependencia de h de la función del error.

Al denotar Dk (h) como la aproximación de orden O(h2k ) a f 0 (x) se tiene:

f 0 (x) = Dk (h) + c1 h2k + c2 h2k+2 + ..., (11.21)

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

Multiplicando la ecuación 11.22 por 4k y restándola de la ecuación 11.21 resulta:

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
:

f 0 (x) = Dk+1 (h)


Ejemplo:

Hallar la primera derivada (k = 1) de e−x en x = 1.Esto se resuleve utilizando la figura 11.9


donde se toma h = 0,64 y D1 (h) = 0,380610. Por lo tanto h/2 = 0,32 y D1 (h/2) = 0,371035. El
resultado está dado por:
4(0,371035)−(0,380610))
f 0 (1) = 3 = 0,367843

11.3. Derivadas por interpolación


Para hallar la derivada de un conjunto de datos de x y f (x) los métodos de interpolación pueden
ser útiles. La derivada se halla con la derivada del interpolador. La ventaja de este método
sobre el método de diferencias finitas es que la distancia entre los datos (xi − xi+1 ) no debe
ser, necesariamente, regular. Para una mejor aproximación a los datos se usa la interpolación
mediante segmentación cúbica; la cual debido a su grado (cúbica) sólo permite hallar la primera
y segunda derivada de los datos.

Fórmula de interpolación segmentaria cúbica:

" # " #
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

Para hallar la primera derivada de lo anterior se usa la siguiente expresión:


" # " #
0 ki 3(x − xi+1 )2 ki+1 3(x − xi )2 yi − yi+1
fi,i+1 (x) = − (xi − xi+1 ) − − (xi − xi+1 ) +
6 xi − xi+1 6 xi − xi+1 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

Un ejemplo que permitirá entender el uso de este método es:

Dado los datos

x 1,5 1,9 2,1 2,4 2,6 3,1


y 1,0628 1,3961 1,5432 1,7349 1,8423 2,0397

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)

Por lo tanto, se cumple la siguiente relación

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

Los métodos de integración numérica son el resultado de integrar polinomios de interpolación


[17]. Por lo tanto, los distintos métodos de interpolación generan diversos métodos de integración.
El método de integración de Newton-Cotes se basa en las fórmulas de interpolación polinomial
de Newton y de Lagrange, descritas en el capítulo 8. Las cuadraturas de Gauss se basan en
los métodos de interpolación polinomial que usan raíces de un polinomio ortogonal, como los
polinomios de Legendre [17].
Para iniciar este capítulo, se describirán las fórmulas de Newton-Cotes para la integración
numérica. Para ello, se presentan los métodos de integración del trapecio y la regla de Simpson.
Posteriormente, se expone el método de cuadratura de Gauss y se presentan las tablas que se
implementan para llevar a cabo integrales definidas. Para finalizar el capítulo, se desarrollan
algunos ejercicios relacionados con la solución de integrales definidas y con temas de los capítulos
anteriores, como los métodos de derivación y el ajuste de curvas. Por lo tanto, durante la revisión
de los ejercicios puede ser de utilidad recurrir a los capítulos 9 y 11.

12.2. Fórmulas de integración de Newton-Cotes

12.2.1. Regla del trapecio

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]

En la figura 12.2 se presenta gráficamente la división en trapecios de una función f (x). La


función está en el intervalo [a, b], que se divide en n trapecios con amplitud h. El área encerrada
por cada trapecio es Ii y se obtiene con la siguiente expresión

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

Al simplificar esta expresión se obtiene

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

#Inew es la integral realizada con 2^k paneles.

#Los parámetros de entrada son la función a integrar f, el intervalo [a,b],


#un valor inicial de la integral y el número de paneles k

def trapezoid(f,a,b,Iold,k):

#Si el numero de particiones es 1


if k == 1:
Inew = (f(a) + f(b))*(b - a)/2.0

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

#Se aplica la formula


for i in range(n):
sum = sum + f(x)
x = x + h
Inew = (Iold + h*sum)/2.0

return Inew #Se retorna la integral I_k

Por ejemplo, para llevar a cabo la siguiente integral


Z π

x cos(x)
0

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:

#Se importa el módulo para la raiz y el coseno de la función


import math

#Se define la función


def f(x):
return math.sqrt(x)*math.cos(x)

#Empieza la integral en cero


Iold = 0.0

for k in range(1,21): #No necesariamente es todo el rango

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

break #Se sale del bucle

Iold = Inew

#Se imprime el valor de la integral


print("Integral= %.6f"%Inew)

#Se imprime el número de paneles


print("nPanels = %d"%2**(k-1))

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

donde h = (b − a)/N y xi es el punto medio entre xi y xi+1 [17]. Al definir el promedio de la


segunda derivada como

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

Con este resultado, la ecuación 12.6 se puede reescribir como [17]

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].

12.2.2. Integración Romberg

La integración de Romberg combina la regla trapezoidal con la extrapolación de Richardson


descrita en el capítulo 11. Con esta combinación se busca disminuir el error en cada iteración
realizada con la regla del trapecio.
Primero, se debe construir una matriz R, donde la primera columna van a ser las integrales
calculadas por la regla del trapecio:

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]

Eh ' Ch2 E2h ' C(2h)2 = 4Ch2

Por lo tanto, el error obtenido con la regla del trapecio es

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.

4j−1 Ri,j−1 − Ri−1,j−1


Ri,j = i > 1, j = 2, 3, · · · , i (12.9)
4j−1 − 1
Para que el método de integración Romberg sea más eficiente, no se construye la matriz R
completa. Lo que se realiza es reemplazar los datos de la columna anterior para que la matriz
siempre tenga solo una columna. Por ejemplo, al aplicar la ecuación 12.8 para hallar los elementos
de la segunda columna de la matriz R, se reemplazan los elementos en la matriz así:
" #
R10 = R2,2
R20 = R2,1

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

Para implementar el método de integración Romberg en un cuaderno de IPython, se puede


escribir el siguiente código:

import numpy as np

#Función para realizar integrales por el método de integración Romberg.

#La integral se realiza para la función f, en el intervalo [a,b] y con una


#tolerancia de 1x10^-6

245
#Se retorna la integral y el numero de paneles usados

def romberg(f,a,b,tol=1.0e-6):

#Se construye la matriz

def richardson(r,k):

for j in range(k-1,0,-1):

#Se aplica la fórmula de integración Romberg

const = 4.0**(k-j)
r[j] = (const*r[j+1] - r[j])/(const - 1.0)

return r #Se retorna la matriz con la integral

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

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) #Se retorna el valor de la integral

r_old = r[1]

#Se imprime un mensaje si aún no se ha retornado algún valor


#para la integral

print("Romberg quadrature did not converge")

Por ejemplo, se desea encontrar el valor de la siguiente integral:

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

#Se define la función que se desea integrar

def f(x):
return 2.0*(x**2)*math.cos(x**2)

#Se guarda el resultado de la integral en la variable I y el numero de


#paneles en la variable n

I,n = romberg(f,0,math.sqrt(math.pi))

#Se imprime el resultado de la integral y el numero de paneles usados

print("Integral =",I)
print("numEvals =",n)

Como resultado, se obtiene en IPython

(’Integral =’, -0.89483146950441261)


(’numEvals =’, 64)

Al utilizar el método de integración Romberg se reduce el número de cálculos realizados y, por


lo tanto, se genera menos gasto computacional con respecto a la regla del trapecio.

12.3. Integración Gaussiana


R
Las fórmulas de Newton para aproximar una integral f (x)dx funcionan mejor si f (x) es una
función suave, tal como un polinomio. Esto también es cierto para la integración gaussiana. Con
el método de integración Gaussiana se desean encontrar unas cuotas de tal manera las raíces
de los polinomios de Legendre generan un área equivalente al área de la curva de la función.
La ilustración de la aproximación que se realiza por medio de este método de integración se
presenta en la figura 12.3. Como se puede observar, el área resultante entre el trapecio y la
curva de la función permite que las dos áreas sean equivalentes.
La fórmula de integración de Gauss tiene la misma forma que la fórmula de Newton

n
X
I= wi f (xi )
i=0

donde, como antes, I representa la aproximación a la integral. La diferencia radica en la forma


en que los pesos wi y abscisas nodales xi son determinadas. En Newton los nodos de integración

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.

Figura 12.3: Aplicación del método de integración de Gauss

Zb Zb n
X
f (x)dx = w(x)Pm (x)dx = Wi Pm (xi ) m ≤ 2n + 1
a a i=0

En la expresión anterior, la integral se expresa en términos de polinomios de Legendre Pm (x).


Aplicar el algoritmo relacionado con este método de integración es sencillo, ya que, dependiendo
del numero de divisiones que se realizan entre el intervalo, se utilizan propiedades de ortogonalidad
y los pesos wi y abscisas nodales xi se encuentran fácilmente en tablas.

Las cuadraturas de Gauss presentadas hasta ahora se denominan cuadraturas de Gauss-Legendre


porque se basan en la ortogonalidad de los polinomios de Legendre. “Existen cuadraturas
análogas con base en los polinomios de Hermite, de Laguerre [Froeberg] y de Chebyshev, las
cuales reciben los nombres de cuadraturas de Gauss-Hermite, Gauss-Laguerre y Gauss-Chebyshev,
respectivamente” [17]. Para el caso de las cuadraturas de Gauss-Legendre se cumple

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

Por ejemplo, las cuadraturas de Gauss-Legendre permiten resolver la siguiente integral:

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
−∞

y están dadas por [17]

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.

Figura 12.5: Cuadraturas de Gauss-Hermite

Por otro lado, las cuadraturas de Gauss-Laguerre son adecuadas para [17]
Z ∞
e−x f (x)dx
0

y están dadas por

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

y están dadas por

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

Figura 12.7: Cuadraturas de Gauss con singularidad logarítmica

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.

Figura 12.9: Articulación que gira con velocidad angular constante

La siguiente tabla relaciona los ángulos θ y β de la unión:

θ(◦ ) 0 30 60 90 120 150


β(◦ ) 59,96 56,42 44,10 25,72 −0,27 −34,29

Suponiendo que el miembro AB de la articulación gira con velocidad constante angular dθ dt =



1rad/s, se busca calcular dt en rad/s en los valores tabulados de θ. Para ello, se debe implementar
la interpolación segmentaria cúbica.

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

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:

#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
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

#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
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

# 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):

n = len(xData) - 1 #Longitud de los datos en x menos 1


c = np.zeros(n) #Arreglo con n ceros
d = np.ones(n+1) #Arreglo con n+1 unos
e = np.zeros(n) #Arreglo con n ceros
k = np.zeros(n+1) #Arreglo con n+1 ceros

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

#Evalua la segmentación cúbica en x. Las curvaturas se encuentran con la


#función curvatures()

def evalSpline(xData,yData,k,x):

#Se encuentra el segmento


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

#Se encuentran las derivadas


i = findSegment(xData,x)
h = xData[i] - xData[i+1]

#La función sin derivar


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

#La primera derivada


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

#La segunda derivada


y2=((x - xData[i+1])/h )*k[i] \
- ((x - xData[i])/h )*k[i+1]
return y,y1,y2 #Se retornan las derivadas

254
Ahora lo único que resta es imprimir las derivadas:

#Datos de theta y beta


theta = np.array([0,30,60,90,120,150])
beta = np.array([59.96,56.42,44.10,25.72,-0.27,-34.29])

#Se encuentran las curvaturas


k=curvatures(theta,beta)

#Se hallan las derivadas y se imprime su valor

#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]

Con esto, el resultado que se imprime en el cuaderno de IPython es

derivada de beta con respecto al tiempo en theta=0 es: -0.0505055821372


derivada de beta con respecto al tiempo en theta=30 es: -0.252988835726
derivada de beta con respecto al tiempo en theta=60 es: -0.52353907496

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

12.4.2. Prueba de tensión

La relación entre el estrés σ y la tensión  de algunos materiales biológicos en tensión uniaxial


es


= a + bσ
d

donde a y b son constantes y dσ


d es llamado módulo tangencial. La siguiente tabla muestra los
resultados de una prueba de tensión para tales materiales

 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:

#Datos de tension y estres


tension=np.array([0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0])
estres=np.array([0,0.252,0.531,0.840,1.184,1.558,1.975,2.444,2.943,3.5,
4.115])

#Se hallan las curvaturas


k=curvatures(tension,estres)

#Arreglo de numpy para guardar las derivadas


y=np.zeros(len(estres))
cont=0

for i in tension:
#Se encuentran las derivadas
derivadas=evalSpline(tension,estres,k,i)

#Se guardan en el arreglo y


y[cont]=derivadas[1]

#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))

return a,b #Se retornan los coeficientes

#Se guardan las constantes


a,b=mincuadrados(estres,y)

#Se imprimen las constantes


print "a es ", a
print "b es ", b

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

12.4.3. Integración Romberg

Este ejercicio consiste en evaluar


Z π/4
dx

0 sin x
Se debe implementar el método de integración Romberg. Por lo tanto, el código que resuelve la
integral es

#Función que implementa la regla trapezoidal


#Iold es la integral en k-1 iteraciones, (integral anterior)

def trapezoid(f,a,b,Iold,k):

if k == 1: #Si el numero de paneles es 1

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

#Se aplica la formula


for i in range(n):
sum = sum + f(x)
x = x + h
Inew = (Iold + h*sum)/2.0

return Inew #Se retorna la integral i_k

#Función que retorna la integral y el numero de paneles usados


#Se implementa el método de integración de Romberg

def romberg(f,a,b,tol=1.0e-6):

#Se construye la matriz


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)
return r

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]

#Si no se ha retornado el valor de la integral, se imprime el mensaje

print("Romberg quadrature did not converge")

import math

258
#Función que se desea integrar

def f(x):

return 2/math.sqrt(1- x**4)

#Se obtienen la integral y el numero de evaluaciones


I,n = romberg(f,0,math.sqrt(math.sin(math.pi/4)))

#Se imprimen los datos anteriores


print("Integral =",I)
print("numEvals =",n)

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:

(’Integral =’, 1.7911613389539645)


(’numEvals =’, 64)

12.4.4. Péndulo simple

El periodo de un péndulo simple con longitud L está dado por


s
L
τ =4 h(θ0 )
g

donde g es la aceleración gravitacional y θ0 representa la amplitud angular. El valor de h(θ0 )


está dado por
Z π/2

h(θ0 ) = q
0 1− sin2 (θ 0 /2) sin

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:

#Función h evaluada en theta=15

def h15(x):
return 1/math.sqrt(1-((math.sin(math.pi/24)**2)*(math.sin(x)**2)))

#Función h evaluada en theta=30

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)))

#Se obtienen las integrales por el método Romberg


I15,n = romberg(h15,0, math.pi/2)
I30,n = romberg(h30,0, math.pi/2)
I45,n = romberg(h45,0, math.pi/2)

#h evaluada en theta=0
h0=math.pi/2

#Se imprimen las integrales, es decir, el valor de h


print "Integral h(0)=",h0
print "Integral h(15)=",I15
print "Integral h(30)=",I30
print "Integral h(45)=",I45

Los valores de h obtenidos son

Integral h(0)= 1.57079632679


Integral h(15)= 1.57755165307
Integral h(30)= 1.59814200257
Integral h(45)= 1.63358630909

Para comparar estos valores con h(0), se divide cada uno de los valores obtenidos entre el valor
h(0) = π/2:

#Se imprimen las funciones h sobre el valor de h en theta=0 para


#compararlos

print "h(15)/h(0)=",I15/h0
print "h(30)/h(0)=",I30/h0
print "h(45)/h(0)=",I45/h0

Con esto se obtiene

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

Ecuaciones diferenciales de primer


orden

Resuelva: y 0 = F (x, y) con la condicion inicial y(a) = α

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) = α

Una ecuación diferencial de orden n:


y ( n) = f (x, y, y 0 , ..., y ( n − 1)

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)

Lo que equivale a las ecuaciones diferenciales de primer orden de la siguiente manera:

263
y00 = y1

y10 = y2
y20 = y3
..
.

yn0 = f (nx, y0 , y1 , ..., y( n − 1))

El nuevo requerimiento para conocer la solución correspondiente, es saber las n condiciones


auxiliares. Si estas condiciones se especifican para un mismo valor de x, se dice que el problema
corresponde a un problema de valor inicial. De esta manera las condiciones auxiliares, llamadas
iniciales tienen la siguiente forma:

y0 (a) = α0

y1 (a) = α1
..
.

y( n − 1)(a) = α( n − 1)

Si yi es definido con diferentes valores de x, el problema pasa a ser un problema de contorno

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.

Por otro lado las ecuaciones:


y 00 = −y
y(0) = 1
y(π) = 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 )

13.2. Problemas de valor inicial. Teorema de existencia y unicidad


de soluciones.

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)

Las condiciones complementarias se denominan a su vez condiciones iniciales. El término “condiciones


iniciales” proviene de problemas en donde interviene el tiempo, se conoce el valor de la variable
dependiente o de alguna de sus derivadas en el instante inicial t=0. Lo anterior implica que una
solución de un problema de valor inicial es una función que satisface tanto la ecuación diferencial
como todas las condiciones complementarias. Ejemplo

265
Para una ecuación xy 0 = y1 cuya condición inicial es y(0) = 1, tiene como soluciones

y = 1 + cx

Donde c es una constante arbitraria.

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

Definida al menos en un intervalo que contiene al punto x0 .

13.3. Métodos numéricos para ecuaciones de primer orden

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

De este modo par un problema de valor inicial

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

Dichas expresiones servirán para calcular n valores aproximados de la solución y1 , y2 , ....yn de la


ecuación diferencial,con la condición y(x0 ) = y0 .A la longitud h de cada sub intervalo [xi , xi + 1]
se le llama paso.

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.

13.3.1. Método de Euler

Para la resolución a la ecuación difernecial de primer orden:[25]


dx
= f (t, x)
dt

Con una condición inicial de que en un momento t0 la posición x0 es:

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)

Debido a que se puede predecir la función y en x + h a partir de la información disponible


en x puede ser utilizado para mover la solución hacia delante en pasos de h, a partir de los
valores iniciales dados de x y y.
El error es causado por la aproximación en la series de taylor, de tal manera que se puede
expresar como:
1
E = h2 †00 () = O(h2 ), x <  < x + h
2

Como se deben realizar n ecuaciones, existe un error acumulado:


xn − x0
Eacc = nE = E = O(h)
h

‘ Para la resolución del problema se escribe una función denominada euler con las condiciones:[25]

Función f(t,x)

Condición inicial de que en el instante t0 la posición es x0

Instante final tf

Número de pasos de integración n

El método de Euler raramente se utiliza en la práctica debido a su ineficiencia computacional.


Suprimiendo el error de truncamiento a un nivel aceptable requiere un muy pequeño h, lo que
resulta en muchos pasos de integración acompañados por un aumento en el error de redondeo.
El valor del método reside principalmente en su simplicidad.
Esta función implementa el método de integración de Euler. Puede manejar cualquier número de
ecuaciones diferenciales de primer orden. Se requiere que el usuario que proporcione la función
F(x, y) que especifica las ecuaciones diferenciales en forma de arreglo

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

Con condiciones iniciales:

t=0

x=0

De modo que:
Zt
x−0= Cos(t)dt
0

x = Sin(t)

Tomando un intervalo h = π6 , se podrá construir la siguiente tabla.


dx
t dt = Cos(t) x=euler x = Sin(t)
0 1 0 0
π/6 0.866 0.523 0.5
π/3 0.5 0.977 0.866
π/2 0 1.239 1
2π/3 -0.5 1.239 0.866
5π/6 -0.866 0.977 0.5
π 0.523 0
Esta tabla ilustra el modo de aplicar el método de Euler para una ecuación diferencial de
primer orden. Para aplicar el método de Euler precisamos de un paso h mínimo, incluso así los
errores se van acumulando y al cabo de cierto tiempo la diferencia entre el valor exacto y el
calculado es grande.

Ejemplo 2

Escriba un script para definir la función euler

## 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)

Escriba un script para definir la función imprimir X y Y obtenidas a partir de


la integración numérica. La cantidad de datos es controlado por el parámetro de
freccuencia

## 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

Para unos saltos de h = 0,05 desde un x = 0 a un x = 2. Ploteé computacionalmente1 y


conjunto con:
y = 100x − 5x2 + 990(e− 0,1x − 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

x = 0.0 # Start of integration


xStop = 2.0 # End of integration
y = np.array([0.0, 1.0]) # Initial values of {y}
h = 0.05 # Step size
X,Y = integrate(F,x,y,xStop,h)
yExact = 100.0*X - 5.0*X**2 + 990.0*(np.exp(-0.1*X) - 1.0)
plt.plot(X,Y[:,0],"o",X,yExact,"-")
plt.grid(True)
plt.xlabel("x"); plt.ylabel("y")
plt.legend(("Numerical","Exact"),loc=0)
plt.show()

271
Imprime

13.3.2. Método Runge Kutta

La idea basia de los métodos de Runge-Kutta es sustituir el problema de valor inicial:

y; = f (x, y)

y(x0 ) = y0

Por la ecuación integral equivalente:


Zy Zx
dy = f (x, y(x)) dx
y0 x0

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

Método Runge Kutta de segundo orden

El método se basa en la expansión en series de Taylor, manteniendo terminos de orden mayor


solucionando ecuaciones diferenciales de primer orden.
Asumimos una solución de la forma

y(x + h) = y(x) + c0 F(x, y)h + c1 F[x + ph, y + qhF(x, y)]h

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

Haciendo una expansión en series de Taylor

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

Eligiendo el método de Euler

 
y(x + h) = y(x) + F x + h/2, y + h/2F(x, y) h

Esta fórmula se puede evaluar simplificando

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

El método de Runge-Kutta de cuarto orden se obtiene a partir de la serie de Taylor a lo largo


de las mismas líneas que el método de segundo orden. Debido a que la derivación es bastante
larga y no muy instructiva, nos saltamos. La forma final de la fórmula de integración depende
de nuevo de la elección de los parámetros; es decir, no hay única fórmula de cuarto orden de
Runge-Kutta. La versión más popular, que se conoce simplemente como

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

Ecuaciones diferenciales: Problemas


de contorno

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:

y 00 = f (x, y, y 0 ), y(a) = α y(b) = β (14.1)

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

En estas condiciones se especifica el valor de la función en cada punto de la frontera de la


región determinada. Un ejemplo de este tipo de condiciones se ve en el problema 14.1.

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:

y 00 = f (x, y, y 0 ), y 0 (a) = α y 0 (b) = β

Las condiciones en estos problemas pueden ser también una mezcla de las condiciones de Drichlet
y de Neumman.Es decir de la forma:

y 00 = f (x, y, y 0 ), y 0 (a) = α y(b) = β

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.

14.1. Método de shooting

Este método se basa en solucionar el problema de condiciones con un parámetro libre de u. Se


debe hallar el valor aproximado de u tal que la solución encontrada cumpla las condiciones de
frontera del problema original.

Se tiene un problema de la siguiente forma:

dn y dy d2 y dn−1 y
dxn =f (x, y, dx , dx2 , .... dxn−1 ) con x en [a, b]

Con r condiciones de contorno en x = a:

dy d2 y dr−1 y
y(a) = α1 , dx (a) = α2 , dx2
(a) = α3 ,.... dxr−1
(a) = αr

Y con s condiciones de contorno en x = b:

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

dr y dr+1 y dr+2 y dn−1 y


dxr (a) = u1 , dxr+1
(a) = u2 ... dxr+2
(a) = u3 ... dxn−1
(a) = us

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.

Por lo tanto si tenemos el problema:

y 00 = f (x, y, y 0 ) , y(a) = α , y(b) = β

Se transforma a:

y 00 = f (x, y, y 0 ) , y(a) = α , y 0 (a) = u

Con lo cual se puede tratar el problema como un problema de condiciones iniciales.

Computacionalmente el valor de u se halla mediante prueba y error. Si la solución dada por u


cumple la condición de frontera y(b) = β se ha hallado la solución definitiva. De lo contrario se
continua buscando un valor de u que cumpla la condición.

De manera sistemática, el proceso de encontrar u, se basa en encontrar la raíz de y.Esto se


debe a que el problema de valor inicial depende de u, el valor de y(b) es una función de u:

y(b) = θ(u)

Por lo tanto u es una raíz tal que:

r(u) = θ(u) − β = 0

El término r(u) es el residuo de frontero (diferencia entre el valor computado x = b).


Ya que u es una raíz se puede usar cualquier método, explicado en el capítulo de raíces, para
hallar su valor.En este caso se usará el método de Ridder.

Los pasos computacionales que sigue el método de shooting son:

1. Especificar los valor u1 y u2 donde se pueda encontrar la pendiente para y 0 (a).

2. Solucionar el problema transformado con u1 utilizando runge-kutta4 (método explicado


en el capítulo anterior).

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:

y 00 (x) = 3yy 0 = 0 , y(0) = 0 , y(2) = 1

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:

Figura 14.1: Obtenida de [14]

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.

El método de Shooting en código de Python, para el ejemplo anterior es:

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])

def r(u): # Boundary condition residual


X,Y = integrate(F,xStart,initCond(u),xStop,h)
y = Y[len(Y) - 1]
r = y[0] - 1.0
return r

def F(x,y): # First-order differential equations


F = np.zeros(2)
F[0] = y[1] #y[1]=y’ y[0]=y
F[1] = -3.0*y[0]*y[1] #y’’=-3yy’
return F

xStart = 0.0 # Start of integration


xStop = 2.0 # End of integration
u1 = 1.0 # 1st trial value of unknown init. cond.
u2 = 2.0 # 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)

print "x y[0] y[1]"


for i in range(len(X)):
print X[i]," ", Y[i]

El resultado de lo anterior será:

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]

Otro ejemplo del método de Shooting es:

Resolver

y 000 = 2y 00 + 6xy y(0) = 2 y(5) = y 0 (5) = 0

Haciendo el cambio de variable y0 = y, y1 = y 0 , y2 = y 00

   
y00 y1
0  0 
F(x, y) = y = y1  =  y2


y20 2y2 + 6xy0

y0 (0) = 2 y0 (5) = 0 y1 (5) = 0

Falta y2 (5) =?

Solución:

import math
import numpy as np

def integrate(F,x,y,xStop,h,tol=1.0e-6):

a1 = 0.2; a2 = 0.3; a3 = 0.8; a4 = 8/9; a5 = 1.0


a6 = 1.0

c0 = 35.0/384.0; c2 = 500.0/1113.0; c3 = 125.0/192.0


c4 = -2187.0/6784.0; c5 = 11.0/84.0

d0 = 5179.0/57600.0; d2 = 7571.0/16695.0; d3 = 393.0/640.0


d4 = -92097.0/339200.0; d5 = 187.0/2100.0; d6 = 1.0/40.0

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)

dy = c0*k0 + c2*k2 + c3*k3 + c4*k4 + c5*k5


E = (c0 - d0)*k0 + (c2 - d2)*k2 + (c3 - d3)*k3 \
+ (c4 - d4)*k4 + (c5 - d5)*k5 - d6*k6
e = math.sqrt(np.sum(E**2)/len(y))
hNext = 0.9*h*(tol/e)**0.2

# Accept integration step if error e is within tolerance


if e <= tol:
y = y + dy
x = x + h
X.append(x)
Y.append(y)
if stopper == 1: break # Reached end of x-range
if abs(hNext) > 10.0*abs(h): hNext = 10.0*h

# Check if next step is the last one; if so, adjust h


if (h > 0.0) == ((x + hNext) >= xStop):
hNext = xStop - x
stopper = 1
k0 = k6*hNext/h
else:
if abs(hNext) < 0.1*abs(h): hNext = 0.1*h
k0 = k0*hNext/h

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

14.2. Diferencias finitas


Para solucionar un un problema de frontera mediante diferencias finitas, se debe crear una malla
del dominio. Una malla del dominio es un conjunto de puntos, con una distancia fija entre estos,
en los cuales se buscará la solución a la ecuación.La malla para los problemas de una dimensión,
los cuales serán el caso de este tema, es de la siguiente manera:

Figura 14.3: Obtenida de: [26]

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).

14.2.1. Diferencias finitas con condiciones de Dirichlet

Para explicar este caso se usa el siguiente ejemplo:

u00 (x) = f (x), 0≤x≤1 u(0) = uα u(1) = uβ


Siguiendo los pasos indicados en la introducción, primero se debe crear la malla del dominio; la
cual en este caso será:

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

Empezando con i = 1 y tomando x0 = u(0) el sistema de ecuaciones es:

uα −2u1 +u2
f (x1) = h2

u1 −2u2 +u3
f (x2) = h2

.......

un−2 −2un−1 +uβ


f (xi ) = h2

Las ecuaciones escritas en la forma matricial Ax=b son:

 −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.

14.2.2. Diferencias finitas con condiciones de Neumman

Al igual que en el caso anterior, se explicará el procedimiento para solucionar un problema de


contorno con condiciones de Neumman a partir diferencias finitas con un ejemplo:

u00 (x) = f (x), 0≤x≤1 u0 (0) = α u0 (1) = β

Donde α y β son constantes.

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 −2un−1 +un


h2
= f (xn−1 )

un+1−un−1
h =β

Resolviendo el sistema de ecuaciones se obtiene la solución al problema.

14.2.3. Diferencias finitas con condiciones mixtas

Con este tipo de condiciones se supone que al ser x = a, se tiene:

αu0 (a) − βu(b) = λ

Donde α 6= 0 y usando el punto de malla ficticio se tiene:

α u1 −u
2h
−1
+ βun = λ

Lo cual es igual a:

2β 2hλ
u−1 = u1 + α un − α

Tomando estos términos como diferencias finitas centradas en x = x0 se tiene:


" #
−2 2β 2u1 2λ
h2
+ αh un + h2
= f0 + αh

o
" #
−1 β u1 f0 λ
h2
+ αh un + h2
= 2 + αh

Con lo cual los coeficientes de la matriz se vuelven simétricos.

Ahora, para explicar este caso se toma el siguiente ejemplo:

u(0) = uα u0 (1) = cte

Se expresa la condición de Neumman como una diferencia centrada:


u(xi +h)−u(xi −h)
u0 (x) = 2h

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−2 −2un−1 +un


h2
= f (xn−1 )

un+1 −un−1
2h = cte

Al resolver este sistema de ecuaciones se encuentran las soluciones al problema.

El siguiente problema ejemplifica la solución de un problema de contorno con condiciones mixtas:

Resuelva la siguiente ecuación diferencial con valores en la frontera

y 00 = −4y + 4x y(0) = 0 y 0 (π/2) = 0

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.

Las ecuaciones y la matriz en este caso serán:

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

# Creación de matríz y condiciones

import numpy as np
import math

def equations(x,h,m): # Set up finite difference eqs.


h2 = h*h
d = np.ones(m + 1)*(-2.0 + 4.0*h2)
c = np.ones(m)
e = np.ones(m)
b = np.ones(m+1)*4.0*h2*x
d[0] = 1.0
e[0] = 0.0
b[0] = 0.0
c[m-1] = 2.0
return c,d,e,b

xStart = 0.0 # x at left end


xStop = math.pi/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"
for i in range(m + 1):
print(’{:14.5e} {:14.5e}’.format(x[i],y[i]))

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:

y 00 + (1 − 0,2x)y 2 = 0 y(0) = 0 y(pi/2) = 1

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’)

def initCond(u): # Init. values of [y,y’]; use ’u’ if unknown


return np.array([0.0, u])

def r(u): # Boundary condition residual


X,Y = integrate(F,xStart,initCond(u),xStop,h)
y = Y[len(Y) - 1]
r = y[0] - 1.0 #f(pi/2)=1
return r

def F(x,y): # First-order differential equations


F = np.zeros(2)
F[0] = y[1] #y[1]=y’

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)

print "x y[0] y[1]"


for i in range(len(X)):
print X[i]," ", Y[i]

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]

2.Resuelva el siguiente problema de frontera mediante el método de Shooting:

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

def r(u): # Boundary condition residual


X,Y = integrate(F,xStart,initCond(u),xStop,h)
y = Y[len(Y) - 1]
r = y[0] + 1.0 # por cond de frontera f(2)=-1
return r

def F(x,y): # First-order differential equations


F = np.zeros(2)
F[0] = y[1]
F[1] = -2*y[1]-3*(y[0]**2)
return F

#Pendiente entre condiciones de frontera = -0.5


#por lo tanto u1 debe ser mayor etonces u1=-0.4
xStart = 0.0 # Start of integration
xStop = 2.0 # End of integration
u1 = -0.4 # 1st trial value of unknown init. cond.
u2 = -1.0 # 2nd trial value of unknown init. cond.
#-1 por pendiente negativa entre condiciones

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)

print "x y[0] y[1]"


for i in range(len(X)):
print X[i]," ", Y[i]

3.Resuelva el siguiente problema de frontera mediante diferencias finitas:

y 00 + 2y + y = 0 y(0) = 0 y(1) = 1

Compare con la solución exacta: y = xe1−x

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:

(1 − h)yi−1 + (−2 + h2 )yi + (1 + h)yi+h = 0

Utilizando la notación matricial Ax=b y con el vector b como :

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)

xStart = 0.0 # x at left end


xStop = 1.0 # x at right end
m = 10 # Number of mesh spaces

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])))

El resultado de lo anterior es:

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

4.Resuelva el siguiente problema de frontera mediante diferencias finitas:

x2 y 00 + xy 0 + y = 0 y(1) = 0 y(2) = 0,638961

Compare con la solución exacta: y = sin(lnx)

En este punto se usan las ecuaciones para las condiciones de Dirichlet:

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

Utilizando la notación matricial Ax=b y con el vector b como :

b = (0, ......., 0,638961)

Se soluciona la ecuación:
(con los métodos GaussElimin, newtonRaphson2, LUdecomp3 y LUsolve3 ya declarados)

def equations(x,h,m): # Set up finite difference eqs.


h2 = h*h
c = np.ones(m)*( x[1:]**2 - x[1:] * h / 2 )
# x[1:] omite el primer valor de x
d = np.ones(m + 1)*( h2 - 2 * x**2 )
#x con todos los valores de x
e = np.ones(m)*( x[0:-1]**2 + x[0:-1] * h / 2 )
#[0:-1] omite el ultimo valor de x
b = np.zeros(m+1)
d[0] = 1.0 # EC 1 y_1=0
e[0] = 0.0
d[m] = 1.0#Ec 2 y_2=0.638961
c[m-1] = 0.0
#vector b
b[0]=0.0
b[m]= 0.638961
return c,d,e,b

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])))

El resultado de lo anterior es:

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

Ecuaciones diferenciales parciales

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:

La ecuación de Schrödinger en dos dimensiones

∂2u ∂2u ∂u
− 2
− 2 + f (x, y)u = i
∂x ∂y ∂t

donde u es la función de onda

Ecuación de Poisson en tres dimensiones

∂2u ∂2u ∂2u


+ 2 + 2 = −4πρ(x, y, z)
∂x2 ∂y ∂x

donde u es el potencial gravitacional y ρ es una distribución de densidad arbitraria

Ecuación de difusión en una dimensión

∂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

Ecuación de onda en dos dimensiones

∂2u ∂2u ∂2u


+ = A
∂x2 ∂y 2 ∂t2

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]:

∂2φ ∂2φ ∂2φ ∂φ ∂φ


A 2
+ B + C 2
+D +E + Fφ = S (15.1)
∂x ∂x∂y ∂y ∂x ∂y

En la ecuación 15.1 x y y son las variables independientes y A, B, C, D, E, F y S son funciones


de x y y.
Las ecuaciones diferenciales parciales de segundo orden se clasifican con base en el resultado
de la operación B 2 − 4AC entre las funciones A, B y C. Por ejemplo, una ecuación diferencial
parcial parabólica es aquella en la que se cumple

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.

15.2. Diferencias finitas

El método de diferencias finitas es el mismo que se aplicó en la resolución de ecuaciones


diferenciales ordinarias en el capítulo 14. Así, lo que se busca es discretizar las ecuaciones
diferenciales por medio de las expansiones en series de Taylor de las derivadas. Esto permite
simplificar las ecuaciones diferenciales parciales hasta obtener una expresión que se soluciona
fácilmente computacionalmente. Las siguientes subsecciones permiten solucionar la ecuación de
difusión y la ecuación de onda en una dimensión por medio de diferencias finitas

15.2.1. Ecuación de difusión

La ecuación de difusión en una dimensión es

∂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:

∂u u(x, t + ∆t) − u(x, t)


≈ (15.3)
∂t ∆t
Asimismo, para la segunda derivada parcial con respecto a la posición, el tiempo permanece
constante y se obtiene

∂2u u(x + ∆x, t) − 2u(x, t) + u(x − ∆x, t)


2
≈ (15.4)
∂x (∆x2 )

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

Para el espacio se divide la región de tal forma que

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

∂u u(xi , tj+1 ) − u(xi , tj )



∂t ∆t

303
Asimismo, la ecuación 15.4 que presenta la segunda derivada con respecto a la posición se escribe

∂2u u(xi+1 , tj ) − 2u(xi , tj ) + u(xi−1 , tj )


2

∂x (∆x2 )

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

∂2u uji+1 − 2uji + uji−1



∂x2 (∆x2 )

Al reemplazar la derivada temporal y la segunda derivada espacial en la ecuación de difusión


se obtiene

uji+1 − 2uji + uji−1 uj+1


i − uji
=
(∆x2 ) ∆t

Al despejar se obtiene para uj+1


i

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.

15.2.2. Ecuación de onda

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)

donde r = c∆t/∆x y la condición de estabilidad es que r se menor a uno

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

#La onda empieza como una exponenecial.


#Esta es x en t=0

u_initial = exp(-((x-0.3)*(x-0.3))/0.01)

Ahora se grafica la condición inicial en IPython

plot(x,u_initial)

Con estas condiciones se obtiene la figura 15.1.


Para solucionar la ecuación de onda, se deben generar las condiciones iniciales y se debe
implementar la ecuación 15.8. El siguiente código realiza estas tareas:

#Se encuentran la primera iteración para las condiciones de frontera fijas


delta_x = x[1]-x[0]
delta_t = 0.0005
c = 1.0
r = c * delta_t / delta_x #r debe ser menor que 1

print r

#condiciones de frontera fijas

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 aplica la fórmula para u


for i in range(1,n_points-1):

u_future[i] = u_initial[i] + (r**2/2.0) *


(u_initial[i+1] - 2.0 * u_initial[i] + u_initial[i-1])

#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:

%pylab #Para generar la animación fuera de IPython

306
plt.ion()
n_time = 350
fig = plt.gcf()

#Bucle para generar la animación

for j in range(n_time):

for i in range(1,n_points-1):

#Se aplica la formula

u_future[i] = (2.0*(1.0-r**2))*u_present[i] - u_past[i] + (r**2)


*(u_present[i+1] + u_present[i-1])

#Se crea variable para guardar el valor anterior de u


u_past = u_present.copy()

#Se crea una variable para guardar el valor presenta de u


u_present = u_future.copy()

#Valores de los ejes


ax = plt.axes(xlim=(0, 1), ylim=(-1, 1))

#Se grafican los resultados


ax.plot(x,u_present)
plt.show()
plt.pause(0.0001)
plt.clf()

plt.close()
plt.ioff()

La animación obtenida presenta la evolución de la función a lo largo del tiempo. Algunas


imágenes que representan la secuencia de la animación son las que aparecen en las figuras
15.2, 15.3, 15.4 y 15.5.

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

Figura 15.2: 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

Figura 15.5: Evolución de la función en la ecuación de onda

309
15.3. Fundamentos de la dinámica de fluidos computacional

La ecuación de Navier-Stokes permite describir el movimiento de un fluido y se obtiene aplicando


principios de conservación de la termodinámica y de la mecánica a un fluido. Esta ecuación es

~
∂V
+ (V ~ = − 1 ∇p + ν∇2 V
~ · ∇)V ~
∂t ρ

donde V ~ es el campo de velocidades descrito por el movimiento del fluido, ρ es la densidad, p


es la presión y ν es la viscosidad, que es una constante.
La ecuación de Navier-Stokes se escribe en una dimensión como:

∂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

Para conocer el comportamiento de un fluido a partir de la ecuación de Navier-Stokes es de gran


utilidad escribir cada una de las derivadas anteriores en términos de series de Taylor y aplicar el
método de diferencias finitas expuesto anteriormente en el capítulo 14 y en la sección anterior.
Con este método se discretiza el tiempo y el espacio, lo que permite encontrar la evolución de
la función Vx en el tiempo y en el espacio.
Por lo tanto, en esta sección se estudian los procesos relacionados con el movimiento de fluidos.
Para ello, se exponen los siguientes procesos:

Convección lineal

Convección no lineal

Difusión

Difusión y convección no lineal: ecuación de Burger

15.3.1. Convección lineal

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:

uj+1 − uji uj − uji−1


i
= −c i
∆t ∆x

Solucionando la ecuación anterior para uj+1


i se obtiene:

∆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

#80 puntos en x y 300 en t


n_x = 80
n_t = 300

#80 puntos entre 0 y 2


x = linspace(0, 2.0, n_x)

u = ones(n_x)

#Se obtiene delta x y delta t


dx = x[1]-x[0]
dt = 0.001
c = 1.0

#Para formar un cuadrado como condición inicial


u[where((x<1.25) & (x>0.75))] = 2.0

Ahora se grafica la condición inicial con matplotlib:

plot(x,u)

El resultado es la gráfica que presenta en la figura 15.6


Ahora se procede a realizar la animación en IPython de la evolución de la función a lo largo del
tiempo:

#Para mostrar la animación fuera de IPython


%pylab
import matplotlib.pyplot as plt
plt.ion()

311
Figura 15.6: Grafica con forma cuadrada según las condiciones iniciales

#Bucle para el tiempo


for n in range(n_t):

fig = plt.gcf()
u_past = u.copy() #Función en el tiempo pasado

#Bucle para la posición


for i in range(1,n_x-1):

u[i] = u_past[i] - c*dt/dx*(u_past[i]-u_past[i-1]) #Se aplica la formula u_i

#Valores de los ejes


ax = plt.axes(xlim=(0, 2), ylim=(0.8,2.2))

#Se generan las gráficas


ax.plot(x,u_past)
plt.show()
plt.pause(0.0001)
plt.clf()

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 imagen resultante es la figura 15.8.

Figura 15.8: Resultado final de la ecuación de convección lineal

15.3.2. Convección no lineal

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

x = linspace(0, 2.0, n_x)


u = ones(n_x)

dx = x[1]-x[0]
dt = 0.001

#Se genera un cuadrado como condición inicial

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.

Sin embargo, la evolución de la función u a lo largo del tiempo es diferente a la evolución en la


convección lineal. El factor u en el segundo término genera un cambio en la ecuación que rige
el movimiento por convección no lineal y para obtener la animación la fórmula cambia.

El código que permite generar la animación es:

%pylab
import matplotlib.pyplot as plt
plt.ion()

for n in range(n_t): # bucle sobre el tiempo

u_past = u.copy()

for i in range(1,n_x): #bucle sobre el espacio

#Se aplica la fórmula de diferencias finitas


u[i] = u_past[i] - u_past[i]*dt/dx*(u_past[i]-u_past[i-1])

ax = plt.axes(xlim=(0, 2), ylim=(0.8,2.2))


ax.plot(x,u_past)
plt.show()
plt.pause(0.0001)
plt.clf()

plt.close()
plt.ioff()

La animación que se genera consiste en la secuencia de imágenes representada por la figura


15.10 y el resultado final es el que se presenta en la figura 15.9, obtenido con matplotlib:

%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

La ecuación diferencial parcial que se desea resolver ahora es la ecuación de 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

∂2u uji+1 − 2uji + uji−1



∂x2 (∆x)2

Al reemplazar las derivadas en la ecuación de difusión se obtiene:

uj+1 − uji uj − 2uji + uji−1


i
= ν i+1
∆t (∆x)2

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

x = linspace(0, 2.0, n_x)


dx = x[1]-x[0]

dt = sigma*dx**2/nu #dt se define usando sigma


alpha = dt/dx**2
print dt

u = ones(n_x)

u[where((x<1.25) & (x>0.75))] = 2.0

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()

for n in range(n_t): #bucle sobre el tiempo


u_past = u.copy()

for i in range(1,n_x-1): #bucle sobre el espacio

u[i] = nu * alpha * u_past[i+1] + (1.0 - 2.0*nu*alpha)*u_past[i] + nu*alpha*u_

ax = plt.axes(xlim=(0, 2), ylim=(0.8,2.2))


ax.plot(x,u_past)
plt.show()
plt.pause(0.0001)
plt.clf()

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)

Figura 15.11: Resultado final de la ecuación de difusión

15.3.4. Ecuación de Burger

La ecuación de Burger es una ecuación diferencial parcial que se escribe como

∂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

x = linspace(0, 2.0*pi, n_x)


dx = x[1]-x[0]

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).

Figura 15.13: Gráfica de u contra x según las condiciones iniciales

La animación que presenta la evolución de la función u según la ecuación de Burger se genera


con el siguiente código:

%pylab
import matplotlib.pyplot as plt
plt.ion()
u_past = zeros(n_x)

for n in range(n_t): # bucle sobre el tiempo

u_past = u.copy()

for i in range(0,n_x-1): #bucle sobre el espacio

u[i] = u_past[i] - u_past[i]*dt/dx*(u_past[i]-u_past[i-1]) + nu


* alpha * (u_past[i+1] -2.0*u_past[i]+u_past[i-1])

u[-1] = u_past[-1] - u_past[-1]*dt/dx*(u_past[-1]-u_past[-2]) + nu *


alpha * (u_past[0] -2.0*u_past[-1]+u_past[-2])

ax = plt.axes(xlim=(0, 7), ylim=(-1,1))

321
ax.plot(x,u_past)
plt.show()
plt.pause(0.0001)
plt.clf()

plt.close()
plt.ioff()

La secuencia de imágenes que representa la evolución de la función u con base en la ecuación


de Burger es la que aparece en la figura 15.15. Además, el resultado final de la animación se
presenta en la figura 15.14.

Figura 15.14: Resultado de la animación de la ecuación de Burger

15.4. Derivadas parciales en dos dimensiones


En un espacio de dos dimensiones se divide cada dimensión y se obtienen puntos con coordenadas:

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.

15.4.1. Convección lineal 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

Como antes, se resuelve la expresión anterior para la incógnita un+1


i,j :

∆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

y las condiciones de frontera son


(
x = 0, 2
u = 1 for
y = 0, 2

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:

from mpl_toolkits.mplot3d import Axes3D #librería requerida para


#graficas 3D

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)) ##

#Se asignan las condiciones iniciales

#Tanto en y, como en x, la funcion vale 0.5


u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 #Se genera una condición cuadrada
#en x y en y

#Se grafican las condiciones iniciales


fig = pyplot.figure(figsize=(11,7), dpi=100) #el parametro figsize
#se puede ajustar para
#distintos tamaños

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

Por lo tanto, el siguiente código permite visualizar la solución:

u = numpy.ones((ny,nx))
u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2

for n in range(nt+1): #bucle a través del tiempo

325
Figura 15.16: Grafica en tres dimensiones de las condiciones iniciales

un = u.copy() #Se copian las condiciones iniciales


row, col = u.shape

for j in range(1, row):

for i in range(1, col):

#Funcion evaluada en el futuro


u[j,i] = un[j, i] - (c*dt/dx*(un[j,i] - un[j,i-1]))-
(c*dt/dy*(un[j,i]-un[j-1,i]))

u[0,:] = 1
u[-1,:] = 1
u[:,0] = 1
u[:,-1] = 1

#Se obtiene la gráfica


fig = pyplot.figure(figsize=(11,7), dpi=100)
ax = fig.gca(projection=’3d’)
surf2 = ax.plot_surface(X,Y,u[:])

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

for n in range(nt+1): ##loop across number of time steps

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

fig = pyplot.figure(figsize=(11,7), dpi=100)


ax = fig.gca(projection=’3d’)
surf2 = ax.plot_surface(X,Y,u[:])

El resultado del código anterior es la figura 15.17 nuevamente.

327
15.4.2. Convección en dos dimensiones

Ahora se soluciona la convección en dos dimensiones, representada por el siguiente par de


ecuaciones diferenciales acopladas:

∂u ∂u ∂u
+u +v =0
∂t ∂x ∂y

∂v ∂v ∂v
+u +v =0
∂t ∂x ∂y

Al discretizar estas ecuaciones por los métodos vistos se obtiene

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

Al reordenar la primera ecuación y solucionar para un+1


i,j se obtiene:

∆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

Por lo tanto, u y v valen 1 en x igual a cero y en x igual a dos


Por lo tanto, el siguiente código permite obtener la solución al par de ecuaciones de convección
con las condiciones dadas:

from mpl_toolkits.mplot3d import Axes3D


from matplotlib import pyplot
import numpy
%matplotlib inline

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)

u = numpy.ones((ny,nx)) ##create a 1xn vector of 1’s


v = numpy.ones((ny,nx))
un = numpy.ones((ny,nx))
vn = numpy.ones((ny,nx))

#Se asignan las condiciones iniciales

u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 #u(.5<=x<=1 && .5<=y<=1 ) is 2

v[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 ##u(.5<=x<=1 && .5<=y<=1 ) is 2

for n in range(nt+1): #bucle a lo largo del tiempo

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

Para graficar la solución se escribe el siguiente código:

329
Figura 15.18: Grafica en tres dimensiones de las condiciones iniciales

from matplotlib import cm #cm = "colormap" para cambiar el color


fig = pyplot.figure(figsize=(11,7), dpi=100)
ax = fig.gca(projection=’3d’)
X,Y = numpy.meshgrid(x,y)

ax.plot_surface(X,Y,u, cmap=cm.coolwarm);

El resultado obtenido es la figura 15.18.

15.4.3. Difusión en dos dimensiones

La ecuación diferencial parcial de difusión en dos dimensiones se escribe como:

∂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

Al despejar el término un+1


i,j de la ecuación anterior se obtiene

ν∆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)

u = numpy.ones((ny,nx)) ##create a 1xn vector of 1’s


un = numpy.ones((ny,nx)) ##

#Se asignan las condiciones iniciales

u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 #u(.5<=x<=1 && .5<=y<=1 ) is 2

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);

La grafica que se obtiene con el código anterior se presenta en la figura 15.19.


Para llevar a cabo las iteraciones y obtener la solución de la ecuación de difusión en dos
dimensiones se implementa el siguiente código:

#Función realizada durante nt tiempos

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.

15.4.4. Ecuación de Burgers en dos dimensiones

La ecuación de Burger puede generar soluciones discontinuas si la condición inicial es suave. El


par de ecuaciones diferenciales parciales acopladas que se desea resolver ahora es:

332
Figura 15.20: Grafica en tres dimensiones de la ecuación de difusión para nt=10+

Figura 15.21: Grafica en tres dimensiones de la ecuación de difusión para nt=14+

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

Al discretizar la primera expresión se obtiene

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

Asimismo, al discretizar las segunda ecuación se obtiene

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:

from mpl_toolkits.mplot3d import Axes3D


from matplotlib import cm
from matplotlib import pyplot
import numpy
%matplotlib inline

#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)

u = numpy.ones((ny,nx)) #crear un vector de unos de 1xn


v = numpy.ones((ny,nx))
un = numpy.ones((ny,nx))
vn = numpy.ones((ny,nx))
comb = numpy.ones((ny,nx))

#Se asignan las condiciones iniciales

u[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 # u(.5<=x<=1 && .5<=y<=1 ) is 2


v[.5/dy:1/dy+1,.5/dx:1/dx+1]=2 # u(.5<=x<=1 && .5<=y<=1 ) is 2

#Se grafican las condiciones


fig = pyplot.figure(figsize=(11,7), dpi=100)
ax = fig.gca(projection=’3d’)
X,Y = numpy.meshgrid(x,y)
wire1 = ax.plot_wireframe(X,Y,u[:], cmap=cm.coolwarm)
wire2 = ax.plot_wireframe(X,Y,v[:], cmap=cm.coolwarm)

335
Figura 15.23: Gráfica en tres dimensiones de las condiciones iniciales

El resultado del código anterior es la figura 15.23.


Para solucionar el par de ecuaciones de Burger en el cuaderno de IPython se debe implementar
el siguiente código:

for n in range(nt+1): #bucle a lo largo del tiempo


un = u.copy()
vn = v.copy()

u[1:-1,1:-1] = un[1:-1,1:-1] - dt/dx*un[1:-1,1:-1]*(un[1:-1,1:-1]-\


un[1:-1,0:-2])-dt/dy*vn[1:-1,1:-1]*(un[1:-1,1:-1]-un[0:-2,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])

v[1:-1,1:-1] = vn[1:-1,1:-1] - dt/dx*un[1:-1,1:-1]*(vn[1:-1,1:-1]-\


vn[1:-1,0:-2])-dt/dy*vn[1:-1,1:-1]* \
(vn[1:-1,1:-1]-vn[0:-2,1:-1])+nu*dt/dx**2*(vn[1:-1,2:]-\
2*vn[1:-1,1:-1]+vn[1:-1,0:-2])+ \
nu*dt/dy**2*(vn[2:,1:-1]-2*vn[1:-1,1:-1]+vn[0:-2,1:-1])

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

Para graficar el resultado obtenido, se adicionan las siguientes líneas de código:

fig = pyplot.figure(figsize=(11,7), dpi=100)


ax = fig.gca(projection=’3d’)
X,Y = numpy.meshgrid(x,y)
wire1 = ax.plot_wireframe(X,Y,u)
wire2 = ax.plot_wireframe(X,Y,v)

El resultado del código anterior se presenta en la figura 15.24.

15.5. Ejercicios

Con estos ejercicios se pretende resolver la ecuación de difusión de calor

∂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 generan 70 valores entre 0 y 1


x = linspace(0, 1.0, n_x)

#Se define la función para t=0


u = 4*x*(1-x)

#Se define dx
dx = x[1]-x[0]

#Se define dt
dt = 0.0001
c = 1.0

#Se calcula alfa


alfa=dt/(dx**2)

print alfa

#Se asignan las condiciones iniciales: u(0,t)=u(1,t)=0


u[0]=u[len(u)-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.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

#Se genera la animación

plt.ion()

for n in range(n_t): # bucle sobre el tiempo

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

#Se aplica la fórmula de diferencias finitas


u[i] = alfa*u_past[i+1] + (1- 2*alfa)*u_past[i] + alfa*u_past[i-1] #Se aplica

#Valores de los ejes


ax = plt.axes(xlim=(0, 1), ylim=(-0.8,1.0))
ax.plot(x,u_past)
#se grafica
plt.show()
#Se define el tiempo de pausa
plt.pause(0.05)
plt.clf()

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 generan 210 puntos aleatorios entre -5 y 5


x = linspace(-5.0, 5.0, n_x)

#Se define la función para t=0


u = exp(-x**2)

#Se define dx
dx = x[1]-x[0]

#Se define dt
dt = 0.001
c = 1.0

#Se calcula alfa


alfa=dt/(dx**2)
print alfa

#Se imponen condiciones de frontera


u[0]=u[len(u)-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

#Se genera la animación

plt.ion()

for n in range(n_t): # bucle sobre el tiempo

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

#Se aplica la función de diferencias finitas


u[i] = alfa*u_past[i+1] + (1- 2*alfa)*u_past[i] + alfa*u_past[i-1] #Se aplica

#Valores de los ejes


ax = plt.axes(xlim=(-5, 5), ylim=(-0.8,1.0))
ax.plot(x,u_past)
#se grafica
plt.show()
plt.pause(0.5)
plt.clf()

plt.close()
plt.ioff()

341
342
16

Procesamiento de imágenes

16.1. Introducción a OpenCV


OpenCV es una librería de visión computarizada de código abierto disponible en http://
SourceForge.net/projects/opencvlibrary que está escrita en C y en C++. Esta librería
está disponible para Windows, Linux y Mac OS X con desarrollo activo de interfaces en Matlab,
Python, Ruby y otros lenguajes [4].
Con OpenCV se busca proporcionar una infraestructura de visión simple de utilizar que ayude a
las personas a construir aplicaciones de visión sofisticadas rápidamente. La librería de OpenCV
contiene alrededor de 500 funciones que abarcan áreas en visión como inspección de fábrica
de productos, imágenes médicas, seguridad, interfaz de usuarios, calibración de cámaras, visión
estéreo y robótica [4].
Adicionalmente, OpenCV fue diseñado para obtener eficiencia computacional, con un enfoque
en aplicaciones en tiempo real [4]. OpenCV está escrito en C optimizado y puede aprovechar
procesadores multinúcleo, que combinan dos o más microprocesadores independientes en un
circuito integrado.
Desde su lanzamiento en 1999, OpenCV ha sido usado en múltiples productos aplicados y en
esfuerzos de investigación. Estas aplicaciones incluyen la unión de imágenes satelitales y de
mapas web, alineación de imágenes de exploración, reducción de ruido de imágenes, análisis
de objetos, sistemas de seguridad y de detección, supervisión automática y segura, calibración
de cámaras, aplicaciones militares y en vehículos terrestres, aéreos no tripulados y submarinos.
Inclusive, OpenCV ha sido implementado en reconocimiento de sonidos y música, donde las
técnicas de reconocimiento de visión son aplicados a imágenes de espectrogramas de sonido.
Para implementar OpenCV en linux, es necesario instalarlo adicionando las siguientes líneas de
código en la terminal:

sudo apt-get install libopencv-*


sudo apt-get install python-opencv

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.

16.2. Origen de OpenCV


OpenCV se originó como resultado de una iniciativa de investigación de Intel para avanzar en
aplicaciones intensivas de CPU. Con este fin, Intel lanzó numerosos proyectos relacionados con
el trazado de rayos en tiempo real y de visualización en tres dimensiones. Uno de los autores que
trabajaba para Intel en ese tiempo visitó universidades y encontró códigos de infraestructuras
de visión computarizada abierta que se utilizaban al interior de grupos universitarios como
el MIT Media Lab. Con este código, cada estudiante nuevo tenía la posibilidad de iniciar sus
aplicaciones en visión [4].
Así, OpenCV se concibió como una forma de hacer universalmente disponible la infraestructura
de visión computacional. OpenCV inició en el laboratorio de investigación de Intel con colaboración
del grupo Software Performance Libraries y con conocimientos básico de implementación y
optimización en Rusia [4]. El objetivo inicial era enviar código implementado y especificaciones
algorítmicas a miembros del equipo de librería Intel ruso.
El jefe de los miembros del equipo ruso fue Vadim Pisarevsky, quien manejó, codificó y optimizó
gran parte de OpenCV. Con él, Victor Eruhimov ayudó al desarrollo de la primera infraestructura
y Valery Kuriakin manejó el laboratorio ruso y apoyó la propuesta. Al comienzo, los objetivos
de OpenCV eran [4]:

Investigar en visión avanzada proporcionando código optimizado para la infraestructura


de visión básica.
Difundir el conocimiento de visión, proporcionando una infraestructura común en la
que los desarrolladores se puedan basar, por lo que el código sería fácilmente legible
y transferible.
Proporcionar avances en aplicaciones comerciales basadas en visión, generando código
gratis portable y optimizado.

Al permitir aplicaciones de visión computarizadas se incrementaría la necesidad por procesadores


rápidos. La conducción actualizada a procesadores más rápidos haría generar más ingresos para
Intel que vender software. Esta puede ser la explicación del por qué este código es generado por
un proveedor de hardware y no por una compañía de software [4].
OpenCV se ha descargado más de dos millones de veces y este número está creciendo a un
promedio de 26000 descargas por mes. El grupo de usuarios se aproxima a los 20000 miembros.
OpenCV recibe numerosas contribuciones de usuarios y el desarrollo central ha salido de Intel.
La línea de tiempo de OpenCV se observa en la figura 16.1. Esta librería ha sido afectada
por multiples acontecimientos y por cambios en el manejo y en la dirección. Por lo tanto,
existieron tiempos en los que nadie en Intel trabajaba en OpenCV. Sin embargo, los procesadores
multinúcleo y las nuevas aplicaciones de visión computacional permitieron que OpenCV se
recuperara [4].
En este momento, OpenCV es un área activa de desarrollo en numerosas instituciones. Lo que se
presentará en las siguientes secciones son algunas utilidades básicas de esta librería en IPython.

344
Figura 16.1: Línea de tiempo de OpenCV

16.3. Abrir imágenes con 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:

img = plt.imread(’messi5.jpg’) #Se obtiene la imagen


plt.imshow(img) #Se muestra la imagen
plt.xticks([]), plt.yticks([]) # esconder valores de los ejes
plt.imsave(’messigray.png’,img) #Guardar la imagen
plt.show() #Mostrar la imagen

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

img = cv2.imread(’messi5.jpg’) #Se obtiene la imagen con OpenCV


b,g,r = cv2.split(img) #Se obtienen los colores, b=azul, g=verde,
#r=rojo

img2 = cv2.merge([r,g,b]) #Se intercambian los colores azul y rojo


plt.figure(figsize=(10,10))
plt.subplot(121);plt.imshow(img)#Se observa la imagen con
#color distorsionado

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

y en y que delimiten la región de interés. Asimismo, la imagen original se puede modificar a


través de los elementos que conforman el arreglo bidimensional.
Por ejemplo, el siguiente código selecciona la región de la figura 16.2 que contiene el balón y
modifica el arreglo bidimensional de la imagen para visualizar el balón a la izquierda del jugador.
Las coordenadas en x que delimitan la región del balón son x = 280 y x = 340. Las coordenadas
en y son y = 330 y y = 390. Por lo tanto, el código que modifica la imagen y permite visualizarla
es:

ball = img2[280:340, 330:390] #Se selecciona la región del balón


img2[273:333, 100:160] = ball #Se adiciona un segundo balón
#a la izquierda del jugador

plt.imshow(img2)
plt.show()

El resultado se observa en la figura 16.4.

16.4. Mezcla de imagen

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:

g(x) = (1 − α)f0 (x) + αf1 (x))

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

dst = α · img1 + β · img2 + γ


Para este ejemplo, γ se toma como cero:

img1 = cv2.imread(’luna.jpg’) #Imagen 1


img2 = cv2.imread(’tree.jpg’) #Imagen 2

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

Figura 16.6: Mezcla de dos imágenes

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:

flags = [i for i in dir(cv2) if i.startswith(’COLOR_’)]


print flags

Los códigos de conversión que se imprimen son

[’COLOR_BAYER_BG2BGR’, ’COLOR_BAYER_BG2BGR_VNG’, ’COLOR_BAYER_BG2GRAY’,


’COLOR_BAYER_BG2RGB’, ’COLOR_BAYER_BG2RGB_VNG’, ’COLOR_BAYER_GB2BGR’,
’COLOR_BAYER_GB2BGR_VNG’, ’COLOR_BAYER_GB2GRAY’, ’COLOR_BAYER_GB2RGB’,
’COLOR_BAYER_GB2RGB_VNG’, ’COLOR_BAYER_GR2BGR’, ’COLOR_BAYER_GR2BGR_VNG’,
’COLOR_BAYER_GR2GRAY’, ’COLOR_BAYER_GR2RGB’, ’COLOR_BAYER_GR2RGB_VNG’,
’COLOR_BAYER_RG2BGR’, ’COLOR_BAYER_RG2BGR_VNG’, ’COLOR_BAYER_RG2GRAY’,
’COLOR_BAYER_RG2RGB’, ’COLOR_BAYER_RG2RGB_VNG’, ’COLOR_BGR2BGR555’,
’COLOR_BGR2BGR565’, ’COLOR_BGR2BGRA’, ’COLOR_BGR2GRAY’, ’COLOR_BGR2HLS’,
’COLOR_BGR2HLS_FULL’, ’COLOR_BGR2HSV’, ’COLOR_BGR2HSV_FULL’,
’COLOR_BGR2LAB’, ’COLOR_BGR2LUV’, ’COLOR_BGR2RGB’, ’COLOR_BGR2RGBA’,
’COLOR_BGR2XYZ’, ’COLOR_BGR2YCR_CB’, ’COLOR_BGR2YUV’,
’COLOR_BGR2YUV_I420’, ’COLOR_BGR2YUV_IYUV’, ’COLOR_BGR2YUV_YV12’,
’COLOR_BGR5552BGR’, ’COLOR_BGR5552BGRA’, ’COLOR_BGR5552GRAY’,
’COLOR_BGR5552RGB’, ’COLOR_BGR5552RGBA’, ’COLOR_BGR5652BGR’,
’COLOR_BGR5652BGRA’, ’COLOR_BGR5652GRAY’, ’COLOR_BGR5652RGB’,
’COLOR_BGR5652RGBA’, ’COLOR_BGRA2BGR’, ’COLOR_BGRA2BGR555’,
’COLOR_BGRA2BGR565’, ’COLOR_BGRA2GRAY’, ’COLOR_BGRA2RGB’,
’COLOR_BGRA2RGBA’, ’COLOR_BGRA2YUV_I420’, ’COLOR_BGRA2YUV_IYUV’,
’COLOR_BGRA2YUV_YV12’, ’COLOR_COLORCVT_MAX’, ’COLOR_GRAY2BGR’,
’COLOR_GRAY2BGR555’, ’COLOR_GRAY2BGR565’, ’COLOR_GRAY2BGRA’,
’COLOR_GRAY2RGB’, ’COLOR_GRAY2RGBA’, ’COLOR_HLS2BGR’,
’COLOR_HLS2BGR_FULL’, ’COLOR_HLS2RGB’, ’COLOR_HLS2RGB_FULL’,
’COLOR_HSV2BGR’, ’COLOR_HSV2BGR_FULL’, ’COLOR_HSV2RGB’,
’COLOR_HSV2RGB_FULL’, ’COLOR_LAB2BGR’, ’COLOR_LAB2LBGR’,
’COLOR_LAB2LRGB’, ’COLOR_LAB2RGB’, ’COLOR_LBGR2LAB’, ’COLOR_LBGR2LUV’,
’COLOR_LRGB2LAB’, ’COLOR_LRGB2LUV’, ’COLOR_LUV2BGR’, ’COLOR_LUV2LBGR’,
’COLOR_LUV2LRGB’, ’COLOR_LUV2RGB’, ’COLOR_M_RGBA2RGBA’, ’COLOR_RGB2BGR’,
’COLOR_RGB2BGR555’, ’COLOR_RGB2BGR565’, ’COLOR_RGB2BGRA’,
’COLOR_RGB2GRAY’, ’COLOR_RGB2HLS’, ’COLOR_RGB2HLS_FULL’,
’COLOR_RGB2HSV’, ’COLOR_RGB2HSV_FULL’, ’COLOR_RGB2LAB’, ’COLOR_RGB2LUV’,
’COLOR_RGB2RGBA’, ’COLOR_RGB2XYZ’, ’COLOR_RGB2YCR_CB’, ’COLOR_RGB2YUV’,
’COLOR_RGB2YUV_I420’, ’COLOR_RGB2YUV_IYUV’, ’COLOR_RGB2YUV_YV12’,
’COLOR_RGBA2BGR’, ’COLOR_RGBA2BGR555’, ’COLOR_RGBA2BGR565’,

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’]

Cualquiera de estos códigos de conversión de color se escribe al interior de la función cvtColor()


como lo muestra el siguiente ejemplo:

#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()

En este caso se implementó el código de conversión cv2.COLOR_BGR2HSV que genera el resultado


presentado en la figura 16.7. A la izquierda de la figura se observa la imagen original y a la
derecha se observa el cambio de color.

351
Figura 16.7: Cambio de color en una imagen

16.6. Umbral de 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)

titles = [’Original Image’,’BINARY’,’BINARY_INV’,’TRUNC’,’TOZERO’,’


TOZERO_INV’]
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
plt.figure(figsize=(10,10))
for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],’gray’)
plt.title(titles[i])
plt.xticks([]),plt.yticks([])

plt.show()

352
Figura 16.8: Estilos de umbral con OpenCV

El resultado del código anterior se visualiza en la figura 16.8.


Al aplicar cv2.THRESH_BINARY se obtiene la imagen original a blanco y negro, pero se reduce
el ruido de la imagen al interior de la luna. Asimismo, con cv2.THRESH_BINARY_INV se reduce
el ruido al interior de la imagen, pero se intercambian los colores negro y blanco. Por otro lado,
cv2.THRESH_TRUNC permite obtener detalles de la figura ya que los contrastes entre los colores
son mayores. Con cv2.THRESH_TOZERO se obtiene la imagen origina con una reducción del ruido.
Sin embargo, aún se perciben detalles en la imágen. Por último, con cv2.THRESH_TOZERO_INV
se elimina el ruido al interior de la luna, pero se pueden visualizar los detalles en la imágen
alrededor del satélite. La figura 16.9 representa cada uno de estos estilos de umbral.
Estos estilos de umbral generan un valor global para modificar la imagen de entrada. Sin
embargo, si la imagen tiene diferentes condiciones de iluminación en diferentes áreas se obtienen
mejores resultados con un umbral adaptativo, que genera un algoritmo para encontrar un umbral
en pequeñas regiones de la imagen.
Al igual que para los estilos de umbral anteriores, al implementar un umbral adaptativo se debe
utilizar la función cv2.threshold(). Existen dos tipos de métodos adaptativos que se pueden
utilizar:

353
Figura 16.9: Estilos de umbral con OpenCV

cv2.ADAPTIVE_THRESH_MEAN_C encuentra el valor umbral por medio de la media de


la zona de la imagen alrededor.

cv2.ADAPTIVE_THRESH_GAUSSIAN_C encuentra el valor unmbral por medio de la


suma ponderada de los valores alrededor, donde los pesos son una ventana gaussiana.

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)

titles = [’Original Image’, ’Global Thresholding (v = 127)’,


’Adaptive Mean Thresholding’, ’Adaptive Gaussian
Thresholding’]
images = [img, th1, th2, th3]

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.

16.7. Binarización Otsu

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()

El resultado se visualiza en la figura 16.11.


La primera fila de imágenes en la figura 16.11 corresponden a la imagen original, el histograma
y el resultado de un umbral global de izquierda a derecha. La segunda fila corresponde a
estas mismas imágenes pero como resultado de una binarización Otsu. El ruido se reduce
considerablemente al aplicar la binarización Otsu. La última fila corresponde a un filtro gaussiano
previo a la binarización Otsu y los resultados son mejores que todos los métodos anteriores.
La figura 16.12 representa los mismos resultados obtenidos con la figura 16.11, lo que indica
que la binarización Otsu es un método bastante confiable, que permite reducir el ruido en una
imagen bimodal considerablemente. En la última fila de imágenes de la figura 16.12 se puede
observar que el resultado de aplicar un filtro gaussiano es que el histograma se divide en dos
picos evidentes, con lo que la binarización genera mejores resultados.

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

El escalado consiste en modificar el tamaño de la imagen. OpenCV viene con la función


cv2.resize() para este propósito. El tamaño de la imagen se puede especificar manualmente, o
se puede especificar un factor de escala. Se utilizan diferentes métodos de interpolación. entre los
que son preferibles cv2.INTER_AREA para realizar contracciones de imágenes y cv2.INTER_CUBIC
(lento) y cv2.INTER_LINEAR para relizar un zoom en la imagen. Por defecto, el método de
interpolación a usar es cv2.INTER_LINEAR para cualquier transformación de escala.
Existen dos formas de cambiar el tamaño de una imagen en IPython, que son:

img = cv2.imread(’messi5.jpg’)
print shape(img)

res1 = cv2.resize(img,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC)

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:

height, width = img.shape[:2]


res = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)

Con este código se imprime inicialmente el tamaño original de la imagen y, posteriormente, se


imprime el tamaño de la imagen escalada con el método cv2.resize():

(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

El siguiente código genera una traslación en una imagen:

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

La rotación de una imagen para un ángulo θ se logra mediante la matriz de transformación de


la forma
" #
cos θ − sin θ
M=
sin θ cosθ

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 θ

Para encontrar esta matriz de transformación, OpenCV proporciona la función cv2.getRotationMatrix2D.


Esta función genera una rotación sin generar algún cambio en la forma de la imagen.
Por ejemplo, si se quiere rotar la imagen de Messi se implementa el siguiente código:

M = cv2.getRotationMatrix2D((cols/2,rows/2),30,1) #centro, angulo, escala


dst = cv2.warpAffine(img,M,(cols,rows))
plt.imshow(dst, cmap=’gray’)
plt.xticks([]), plt.yticks([]) #ocultar valores de los ejes
plt.show()

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

16.8.4. Transformación afín

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

pts1 = np.float32([[50,50],[200,50],[50,200]]) #Puntos de la imagen


#original

pts2 = np.float32([[10,100],[200,0],[100,250]]) #Puntos de la imagen


#rotada

M = cv2.getAffineTransform(pts1,pts2) #Se crea la matriz de


#transformación

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()

A la izquierda de la figura 16.15 se visualiza la imagen original y a la derecha se observa el


resultado de la transformación afín. En esta última imagen las líneas que eran paralelas en la
imagen original continúan siendo paralelas en la imagen resultante.

16.8.5. Transformación de perspectiva

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

pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]]) #Puntos


#iniciales

363
Figura 16.16: Trasformación de perspectiva en una imagen

pts2 = np.float32([[0,0],[170,0],[0,300],[300,300]]) #Nuevas posiciones

M = cv2.getPerspectiveTransform(pts1,pts2) #Se crea la matriz de


#transformación

dst = cv2.warpPerspective(img,M,(300,300)) #Se cambia la perspectiva


plt.figure(figsize=(10,10))
plt.subplot(121),plt.imshow(img),plt.title(’Input’)
plt.subplot(122),plt.imshow(dst),plt.title(’Output’)
plt.show()

A la izquierda de la figura 16.16 se observa la imagen original del sudoku. A la deracha de la


figura se presenta el resultado de la transformación de perspectiva. Como se puede observar,
las líneas rectas en la imagen original continúan siendo rectas después de la transformación
de perspectiva. Como resultado de la transformación, parece que se observa la imagen original
desde la esquina inferior derecha.

16.9. Suavizado de imágenes

16.9.1. Convolución en dos dimensiones

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’)

kernel = np.ones((5,5),np.float32)/25 #Se crea la matriz de unos de 5x5


#y se divide por 25

dst = cv2.filter2D(img,-1,kernel) #Convolución de la imagen y la matriz


#del kernel

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()

El resultado del código anterior se presenta en la figura 16.17. A la derecha de la figura se


observa la imagen suavizada y a la izquierda la imagen original.

16.10. Desenfoque de la imagen

La borrosidad de la imagen se logra mediante la convolución de la imagen con un filtro de núcleo


de paso bajo. Esta técnica es útil para eliminar el ruido en las imágenes. En realidad, con este
método se elimina el contenido de alta frecuencia (por ejemplo: ruido, bordes) de la imagen y
se obtienen bordes borrosos cuando se trata de aplicar el filtro.
OpenCV proporciona principalmente cuatro tipos de técnicas de desenfoque. Estas técnicas son
promediar, el filtrado de Gauss, el Median Blur y el filtrado bilateral. En esta sección se describe
cada uno de estos métodos.

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’)

blur = cv2.blur(img,(5,5)) #Ahorra el paso de generar la matriz del kernel

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

16.10.2. Filtrado de Gauss

Con el método de filtrado de Gauss se implementa un kernel gaussiano en vez de un filtro de


caja. Para ello, se implementa la función cv2.GaussianBlur(). Se debe especificar el ancho
y alto del kernel, que debe contener elementos positivos e impares. Adicionalmente, se debe
especificar la desviación estándar en X y en Y (σx y σy , respectivamente). Si solo se especifica
σx , σy es equivalente a σx por defecto.
El filtrado de Gauss es altamente eficaz en la eliminación de ruido de la imagen. El kernel se
puede crear con la función cv2.getGaussianKernel() si se desea. Esta función devuelve la
matriz de coeficientes de un filtro gaussiano según la siguiente ecuación:

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

16.10.3. Median Blur

La técnica de desenfoque de Median Blur se puede implementar gracias a la función de OpenCV


cv2.medianBlur(). Esta función calcula la mediana de todos los píxeles encerrados por el
kernel y el píxel central se sustituye con este valor de la mediana. Esto es altamente eficaz en
la eliminación de ruido de sal y pimienta.
Una cosa interesante a destacar es que, en los filtros de Gauss y la caja, el valor filtrado para
el elemento central puede ser un valor que puede no existir en la imagen original. Sin embargo,
este no es el caso en el Median Blur, ya que el elemento central siempre se sustituye por un
valor de píxel en la imagen. Esto reduce el ruido de manera efectiva.
Para llevar a cabo el Median Blur se puede implementar el siguiente código:

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()

El resultado de este código se visualiza en la figura 16.20. Nuevamente, a la izquierda se observa


la imagen original y a la derecha se observa la imagen resultante al aplicar el Median Blur.

16.10.4. Filtro bilateral

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.

16.11. Transformaciones morfológicas


Las transformaciones morfológicas son algunas operaciones sencillas basadas en la forma de la
imagen. Se llevan a cabo normalmente en las imágenes binarias. Dos operadores morfológicos
básicos son la erosión y la dilatación. En esta sección se presentan los dos junto con sus formas
variantes como la apertura, cierre, gradiente, Top hat, Black hat y elemento estructurante

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

erosion = cv2.erode(img,kernel,iterations = 4) #imagen, kernel, numero de


#iteraciones

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()

El resultado del código anterior se observa en la figura 16.23. A la izquierda de la figura se


observa la imagen original y a la derecha se observa la imagen generada por la erosión. Como
se puede observar, los círculos se separan y reducen su área.

371
Figura 16.23: Erosión de una imagen

16.11.2. Dilatación

Esta transformación es lo opuesto a la erosión. La dilatación consiste en que un pixel de la


imagen original que esté rodeado por al menos un pixel con valor 1 adquiere este valor. El área
sobre la cual se busca el pixel de valor 1 está dado por el kernel. Por lo tanto, el tamaño de la
región blanca o la imagen en primer plano aumenta.
La dilatación se realiza después de una erosión para eliminar el ruido en una imagen. Además,
si se aplica la dilatación se pueden conectar objetos anteriormente espaciados.
El código que permite aplicar la transformación de dilatación es:

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

Figura 16.25: Apertura 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()

El resultado se visualiza en la figura 16.25. A la izquierda se observa la imagen original y a la


derecha se observa el resultado de aplicar una apertura. El ruido en forma de regiones blancas
se elimina como consecuencia de la transformación.

373
Figura 16.26: Cierre de una imagen

16.11.4. Cierre

El cierre es inverso a la de apertura, seguido de la dilatación se realiza una erosión. Es útil en


el cierre de agujeros pequeños en el interior de los objetos en primer plano, o pequeños puntos
negros en el objeto.
El siguiente código permite aplicar un cierre en una imagen con OpenCV:

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.

16.11.5. Gradiente morfológico

El gradiente morfológico es la diferencia entre la dilatación y la erosión de la imagen. Este


gradiente se utiliza para encontrar los límites o bordes de una imagen. Se debe aplicar algún
tipo de filtrado antes de calcular la pendiente, ya que es muy sensible al ruido.
Esta transformación consiste en restar la imagen original y la imagen dilatada, de forma que
solo quedan los bordes de la imagen. El siguiente código permite realizar la transformación con
OpenCV:

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

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, 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(gradient,cmap="gray"),plt.title(’Gradiente’)
plt.xticks([]), plt.yticks([])
plt.show()

La figura 16.27 presenta el resultado del Gradiente morfológico. A la izquierda se observa la


imagen original y a la derecha se observa la transformación.

16.11.6. Top Hat

Es la diferencia de la imagen original y la apertura de la imagen. En él se destacan las vías


estrechas entre las diferentes regiones.
El código que permite aplicar esta transformación es:

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

A la izquierda de la figura 16.28 se observa la imagen original y a la derecha se observa el


resultado de la transformación.

16.11.7. Black hat

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()

El resultado se observa en la figura 16.29. A la izquierda se observa la imagen original y a la


derecha se observa el Black Hat.

16.11.8. Elemento estructurante

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))

El kernel que se imprime en el cuaderno es:

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))

El arreglo resultante es:

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()

El resultado del código anterior se visualiza en la figura 16.30. A la izquierda de la figura se


presenta el resultado obtenido al aplicar una erosión a la imagen con un kernel cuadrado. En la
imagen central se visualiza el resultado obtenido al implementar un kernel con forma de elipse.
Por último, a la derecha de la figura se observa la imagen resultante al aplicar una erosión con
un kernel en forma de cruz.

378
17

Procesamiento de imágenes 2

17.1. Gradiente de una imagén

17.1.1. Operador Sobel

El operador Sobel es utilizado en procesamiento de imágenes, especialmente en algoritmos de


detección de bordes.Básicamente es un operador diferencial discreto que deduce una aproximación
del gradiente de una función que expresa la intensidad de una imagen, De esta manera cada
punto de la imagen a procesar por el operador Sobel resultara ser tanto el vector gradiente
respectivo como la norma de éste vector.

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

Pudiendo a su vez calcular también la dirección del gradiente:

Gy
θ = arctan( )
Gx

Donde Θ es 0 para bordes verticales con puntos más oscuros al lado izquierdo.

Dicho lo anterior se mostrara un ejemplo correspondiente a la utilización de este operador,


pues como se sabe el resultado del operador sobel es un mapeo bidimencional del gradiente de
cada punto, el cual puede ser procesado y ser visto como una imagen, con las áreas de gradiente
elevado (equivalentes a bordes) en negro y con los demás como blanco (el fondo de la imagen
generada).

Figura 17.1: Ejemplo imágen bajo el operador Sobel

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.

Para la resolución de un ejemplo en python se expresara de la siguiente manera:

1. Importe los paquetes necesarios para el trabajo.

%pylab inline
import cv2
import numpy as np
from matplotlib import pyplot as plt

2. Código para utilizar el operador

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

Se calcula el laplaciano de la imagen dada por la relación,

∂2f ∂2f
∆f = +
∂x2 ∂y 2

Por diferencias finitas

f (x − h, y) + f (x + h, y) + f (x, y − h) + f (x, y + h) − 4f (x, y)


∆f (x, y) ≈ ,
h2
El kernel sería
 
0 1 0
kernel = 1 −4 1
 
0 1 0

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.

Según el cálculo vectorial, un operador laplaciano o simplemente laplaciano es un operador


diferencial elíptico de segundo orden, denotado como 4, usado comúnmente en ciertos problemas
de minimización de ciertas magnitudes sobre un cierto dominio.

El operador laplaciano al se un detector de orden basado en las derivadas de segundo orden


responde a las transiciones de intensidad, sin embargo no es común que se utilice en en la
práctica para la detección de bordes, esto es debido a:

Los operadores basados en la primera derivada son sensibles al ruido en imágenes.


El Laplaciano aún lo es más.

Los bordes generados son dobles.

No se encuentra información direccional de los ejes detectados.

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]

Convolucionar la imagen original con un filtro gaussiano.

Calcular las derivas sobre la imagen suavizada.

382
Al ser ambas operaciones lineales se pueden combinar de distintas maneras como:

Suavizado de la imagen y cálculo de la 2 derivada.

Convoluciń de la imagen original utilizando el laplaciano del Gaussiano (Operador


LoG).

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.

Dicho esto las funcionalidades del operador Laplaciano permite:[24]

La función gaussiana suaviza o difumina los ejes.

La segunda derivada de la imagen difuminada es calculada. Se detectan los cruces


por cero en los bordes.

El proceso de difuminación es ventajoso ya que:

• El laplaciano podrá ser infinito en los bordes de la imagen sin suavizar.


• La posición de los bordes se mantiene.

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.

A modo de ejemplo se mostrara la forma de escribir el operador en python

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

17.2. Detección de bordes

En principio la definición de bordes corresponde a:

Variaciones fuertes de la intensidad que corresponden a las fronteras de los objetos


visualizados

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.

2. Encontrar Gradiente intensidad de la imagen


La imagen suavizada se filtra utilizando el operador Sobel, tanto en dirección horizontal
y vertical para obtener primera derivada en la dirección horizontal (Gx ) y la dirección
vertical (Gy ). A partir de estas dos imágenes, podemos encontrar la pendiente y
dirección del borde para cada píxel de la siguiente manera:
q
Edge_Gradient (G) = G2x + G2y

!
Gy
Ángulo (Θ) = tan−1
Gx

La dirección del gradiente es siempre perpendicular a los bordes.

3. Supresión del No -máximo


Después de conseguir la magnitud y dirección del gradiente, un análisis completo de

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.

El punto A está en el borde (en la dirección vertical). La dirección del gradiente


es perpendicular al borde. Los puntos B y C están en direcciones de gradiente. Así
que el punto A está marcada con el punto B y C para ver si se forma un máximo
local. Si es así, se considera para la próxima etapa, de lo contrario, se suprime (se
pone cero).
En resumen, el resultado que se obtiene es una imagen binaria con "bordes delgados".

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.

Así que lo que finalmente obtenemos son bordes fuertes en la imagen.

17.3. Deteción de borde Canny en OpenCV

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.

Hay dos clases de imágenes 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.[? ]

De esta manera el código a usar en Python es:

img = cv2.imread(’messi5.jpg’)
lower_reso = cv2.pyrDown(higher_reso)

Dando como resultado

De igual manera es posible visualizar imagenes con una resolución menor con el uso de:

387
higher_reso2 = cv2.pyrUp(lower_reso)

Dando como resultado

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

Iniciando con el codigo para encontrar contornos, serian las lineas:

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)

Antes de iniciar un trabajo con contornos se debe tener en cuenta:

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

2. La función findContours modifica la imagen de origen. Así que si la imagen de origen


que desee, incluso después de encontrar los contornos, hay que almacenarlo en alguna
otra variable.

3. En OpenCV, la búsqueda de contornos es como encontrar un objeto blanco de fondo


negro.

389
Como dibujar contornos

Para esto, hay una función , cv2.drawContours ().

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)

Figura 17.3: Contornos a 3 pixeles

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

img = cv2.drawContours(im, contours, -1, (0,255,0), 3)

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 ’] .

Para que el código funcione se deberia imprimir lo siguiente

area = cv2.contourArea(cnt)
print int(M[’m00’]),area

Imprime

17433 17433.0

17.3.5. Perímetro

También se denomina longitud de arco . Se puede encontrar a cabo utilizando la función


cv2.arcLength ( ) . El segundo argumento especifica si la forma es un contorno cerrado ( si
se aprueba’true’) , o simplemente una curva .

Para que el código funcione se deberia imprimir lo siguiente

perimeter = cv2.arcLength(cnt,True)
print perimeter

393
Imprime
532.0

394
18

Procesamiento de imágenes 3

18.1. Histogramas

Un histograma es una herramienta gráfica que permite analizar la distribución de un conjunto


particular de datos. Cuando se realiza el histograma de una imagen los datos que muestra la
distribución equivalen a la intensidad de cada de píxel de una imagen. Los histogramas de una
imagen se distribuyen según el siguiente rango:

Figura 18.1

Donde la intensidad 0 se da en el color negro y la intensidad 255 se da en el color blanco.

Para entender mejor cómo funciona un histograma de una imagen se puede utilizar el siguiente
ejemplo obtenido de [1]:

Se tiene la imagen de un mosaico de 40 cuadros: 12 de color azul, 10 blancos, 10 amarillos


y 8 rojos. Por lo tanto el histograma agrupara en columnas la cantidad de cuadros por color.

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.

18.1.1. Cómo interpretar el histograma de una imagen

El histograma de una imagen permite identificar si una imagen es sobreexpuesta, subexpuesta


o si tiene una correcta iluminación. Una imagen oscura tendrá un histograma agrupado hacia
la izquierda, en una imagen clara estará agrupado hacia la derecha y si la imagen tiene una
iluminación el histograma mostrará una distribular similar a una distribución Gaussiana.

Figura 18.3: Histograma de una imagen subexpuesta. Obtenida de [1]

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]

18.1.2. Cálculo de un histograma en OpenCV

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:

Imagen: es la imagen de la fuente de tipo unit8 o float32. Se debe escribir entre


corchetes "[img]".

Canales: Canales: Es el índice de canal para el que se calcula el histograma. Por


ejemplo, si la entrada es una imagen en escala de grises, su valor es [0]. Si es una
imagen a color, puedes pasar a [0], [1] o [2] para el cálculo del histograma del canal
azul, verde o rojo, respectivamente. Al igual que la imagen, también se escribe entre
corchetes.

Máscara: la máscara de imagen. Para encontrar el histograma completo de la imagen


se da su valor como "None". Pero si se quiere encontrar el histograma de una región
particular de la imagen, hay que crear una máscara para seleccionar los valores de
los píxeles.

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].

Los parámetros se escriben en la función en el siguiente orden:

cv2.calcHist(imagen, canales, máscara, histSize, ranges[, hist[, accumulate]])

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()

El resultado de lo anterior será:

Figura 18.6: Ejemplo de histograma con OpenCV

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’)

#Recorrido de cambia el canal del histograma


for i,col in enumerate(color):

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 resultdo de lo anterior será:

Figura 18.7: Historama por canales

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

#Imagen con máscara


masked_img = cv2.bitwise_and(img,img,mask = mask)

#Creación de histograma sin máscara


hist_full = cv2.calcHist([img],[0],None,[256],[0,256])

#Creación de histográma con máscara


hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

plt.subplot(221), plt.imshow(img, ’gray’)


plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(mask,’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(masked_img, ’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])

plt.show()

El resultado al ejecutar el código anterior es:

Figura 18.8

Figura 18.9: Histograma con máscara

400
En azul se grafica el histograma de la imagen completa y en verde el histograma de imagen con
máscara.

18.1.3. Cálculo de un histograma con Numpy

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()

El resultado de lo anterior será:

Figura 18.10: Ejemplo de histograma con Numpy

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.

18.1.4. Ecualización de un histograma con OpenCV

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.

Para realizar la ecualización de un histograma se puede usar la frecuencia relativa la cual


representa la probabilidad de ocurrencia para un nivel de gris [10] o la frecuencia absoluta y se
aplica la siguiente ecuación:

#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.

Figura 18.11: Obtenida de [10]

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.

Figura 18.13: Original (derecha) Ecualizada (izquierda)

El histograma ecualizado es:

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()

Figura 18.14: Histograma de imagen ecualizado

Se puede apreciar que el histograma acumulado se aproxima a una recta.

404
18.1.5. CLAHE (Contrast Limited Adaptive Histogram Equalization)

Cómo se puede observar en el ejemplo anterior, al ecualizar el histograma de la imagen se


mejoran los detalles estructurales de algunas zonas de iluminación, mientras en otras se pierde
detalle. Eso sucede ya que ese tipo de ecualización considera el contraste global de la imagen.
Otro ejemplo de este tipo de ecualización es:

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()

Figura 18.15: Original (derecha) y ecualizada (izquierda)

Al considerar el contraste global, el histograma no se limita a una región particular y por lo


tanto la imagen ecualizada tiene perdida de detalles en las zonas con mayor brillo. Para evitar
esto se usa la ecualización adaptativa; la cual divide la imagen en "baldosas"(8 x 8 por defecto
en OpenCV) y por cada baldosa realiza la ecualización. Si la baldosa seleccionada tiene ruido
este será amplificado; esto puede ser evitado limitando el contraste. El límite de contraste en
OpenCV, por defecto, es de 40. Si la frecuencia de una baldosa supera el límite de contraste los
píxeles se recortan y se distribuyen uniformemente antes de aplicar la ecualización. Después de
la ecualización, para eliminar la división de baldosas, se aplica la interpolación bilineal.

Para aplicar este tipo de ecualización se debe ejecutar el siguiente código:

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))


cl1 = clahe.apply(img)
plt.figure(figsize=(15,15))
plt.subplot(121), plt.imshow(img, ’gray’)
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(cl1, ’gray’)
plt.xticks([]), plt.yticks([])

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.

18.2. Filtrado en el dominio de la frecuencia

Las transformaciones de imagen que se han realizado, en los procedimientos anteriores, se


hacen sobre el dominio espacial, es decir trabajar con las intensidades (en niveles de gris) y
la posición de cada píxel. Estas transformaciones también se pueden realizar sobre el dominio
de la frecuencia mediante la transformada de Fourier; la cual representa la distribución de
frecuencias de la imagen. La transformada de Fourier es una representación de la imagen como
una suma de exponenciales complejas de distintas amplitudes, frecuencias y fases.

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.

Debido a lo anterior, la transformada de Fourier en dos dimensión se define como la siguiente


ecuación:

∞ ∞
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

La transformada de Fourier inversa de forma discreta es:

−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

En esta ecuación 0 < u < M − 1 y 0 < v < N − 1.


Para lograr la transformación de la imagen se re-transforma al dominio espacial. Esto se hace
mediante la convolución de la transformada inversa de Fourier por una función H(u, v) la cual
es un filtro atenuador de frecuencias.

G(u, v) = F (u, v) ∗ H(u, v)

18.2.1. Filtros pasa-baja, pasa-alta y paso-banda

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.

Con el siguiente Código se halla la magnitud del espectro de una imagen:

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()

Al implementar este código el resultado será:

407
Figura 18.17: Magnitud del espectro de la imagen

Para aplicar el filtro pasa-alta, sobre la imagen, se usa el siguiente código:

rows, cols = img.shape


crow,ccol = rows/2 , cols/2
fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
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(img_back, cmap = ’gray’)
plt.title(’Image after HPF’), plt.xticks([]), plt.yticks([])

plt.show()

El resultado será:

Figura 18.18: Imagen con filtro pasa-alta

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

# apply mask and inverse DFT


fshift = fshift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
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(img_back, cmap = ’gray’)
plt.title(’Image after DPF’), plt.xticks([]), plt.yticks([])
plt.show()

El resultado al implementar el código será:

Figura 18.19: Caption

18.3. Comparación de plantillas


Con OpenCv es posible encontrar en una imagen el área que sea igual o similar a una imagen
plantilla. Eso se hace mediante la función cv2.matchTemplate(). Para ejecutar esta función
son necesarios dos componentes:

Imagen fuente: Imagen en la que se busca el área de la plantilla.


Imagen plantilla: Imagen que se va a comparar con la imagen fuente.Esta imagen es
de menor tamaño que la imagen fuente.

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).

OpenCV tiene 6 métodos que permiten realizar este procedimiento:

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

T 0 (x0 , y 0 ) = T (x0 , y 0 ) − (w·h)·


P 1
T (x00 ,y 00 )
x00 ,y 00

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]

# All the 6 methods for comparison in a list


methods = [’cv2.TM_CCOEFF’, ’cv2.TM_CCOEFF_NORMED’, ’cv2.TM_CCORR’,
’cv2.TM_CCORR_NORMED’, ’cv2.TM_SQDIFF’, ’cv2.TM_SQDIFF_NORMED’]

for meth in methods:


img = img2.copy()
method = eval(meth)

# Apply template Matching


res = cv2.matchTemplate(img,template,method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum


if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)

cv2.rectangle(img,top_left, bottom_right, 255, 2)

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()

El resultado de todos los métodos , excepto del método TM_CCORR_NORMED, es :

411
Figura 18.20: Resultado de comparacion de plantilla

El método TM_CCORR_NORMED no consiguió encontrar la plantilla en la imagen fuente y


por lo tanto se deduce que este es el método menos preciso para el reconocimiento de plantillas
en otra imagen. El resultado de este método fue:

Figura 18.21: Comparación de plantilla con TM_CCORR_NORMED

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

El código para solucionar este problema es:

#Se importan los modulos


%pylab inline
from matplotlib import pyplot as plt
import numpy as np
import cv2 #Se importa openCV

#Se obtiene la imagen


img=cv2.imread("monedas.png")

#Se cambia el color de la imagen a un tono gris


gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#Se aplica Filtrado de Gauss


blur = cv2.GaussianBlur(gray, (7, 7), 0)

#Se aplica detección de bordes Canny


edges = cv2.Canny(blur,70,300)

#Se visualiza la imagen


plt.imshow(edges,’gray’)
plt.show

#Se encuentran los contornos de la figura


cnts = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]

413
#Se imprimen los contornos encontrados
print("contornos encontrados: ",len(cnts))

#arreglos para guardar el area y perimetro de las monedas de mil


mil=[]
pmtmil=[]
#arreglos para guardar el area y perimetro de las monedas de quinientos
quin=[]
pmtquin=[]
#arreglos para guardar el area y perimetro de las monedas de doscientos
dos=[]
pmtdos=[]
#arreglos para guardar el area y perimetro de las monedas de cien
cien=[]
pmtcien=[]
#arreglos para guardar el area y perimetro de las monedas de cincuenta
cinc=[]
pmtcinc=[]
# bucle sobre los contornos
for (i, c) in enumerate(cnts):
#se dibujan los contornos
((x, y), _) = cv2.minEnclosingCircle(c)
#color azul para los contornos=(0,0,255)
cv2.drawContours(img, [c], -1, (0, 0, 255), 2)
area=cv2.contourArea(c) #Se encuentra el area
perimeter = cv2.arcLength(c,True) #Se encuentra el perimetro
if area>=4501 and area<=4718: #Si el area corresponde a una moneda de mil
mil.append(area) #Se agrega el area al arreglo correspondiente
pmtmil.append(perimeter) #Se agrega el perimetro al arreglo correspondiente

elif area>=3800 and area<=4500: #Si el area es de una moneda de quinientos


quin.append(area) #Se agrega el area al arreglo correspondiente
pmtquin.append(perimeter) #Se agrega el perimetro al arreglo correspondiente

elif area>=3200 and area<=3799: #Si el area es de una moneda de doscientos


dos.append(area) #Se agrega el area al arreglo correspondiente
pmtdos.append(perimeter) #Se agrega el perimetro al arreglo correspondiente

elif area>=2800 and area<=3190: #Si el area es de una moneda de cien


cien.append(area) #Se agrega el area al arreglo correspondiente
pmtcien.append(perimeter) #Se agrega el perimetro al arreglo correspondiente

elif area>=1900 and area<=2300: #Si el area es de una moneda de cincuenta


cinc.append(area) #Se agrega el area al arreglo correspondiente
pmtcinc.append(perimeter) #Se agrega el perimetro al arreglo correspondiente

#Se imprimen los resultados


print "monedas de mil: ",len(mil)
print "area promedio: ",average(mil)

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)

#Se observa la imagen original con los contornos


plt.imshow(img,’gray’)
plt.show()

Resultado:

(’contornos encontrados: ’, 16)


monedas de mil: 1
area promedio: 4690.5
perimetro promedio: 259.521859169
////////////////////////
monedas de quinientos: 3
area promedio: 3982.66666667
perimetro promedio: 237.022700906
////////////////////////
monedas de doscientos: 6
area promedio: 3643.83333333
perimetro promedio: 227.03251334
////////////////////////
monedas de cien: 2
area promedio: 3068.25
perimetro promedio: 208.116268516
////////////////////////
monedas de cincuenta: 4
area promedio: 2083.625
perimetro promedio: 172.413472295
////////////////////////
Monedas totales: 16

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.

[3] Y. Betancourt Gonzalez. Ambiente computacional para apoyar la enseñanza de la


resolución de sistemas de ecuaciones lineales en la educación superior. Master’s thesis,
Centro de Investigación y de Estudios Avanzados del Instituto Politécnico Nacional,
2009.

[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.

[6] Open CV.

[7] Open CV. Image pyramids retrieved from https://opencv-python-tutroals.readthedocs.io/en/latest/p

[8] Edgar Linares-UPAEP. Linares, e. (2010). interpolacion polinomial. presentation,


http://es.slideshare.net/elinares/ajuste-polinomial-5977108.

[9] J Fraleigh and R Beauregard. Linear algebra. 1990.

[10] S. Galvez. Filtrado de imágenes y ecualización de histograma. (2015). retrieved from


https://www.u-cursos.cl/usuario/b4eb6d37062854338662ba7470704112/mib log/r/el7008t area1 .pdf.

[11] G.L.Squires. Practical physics, volume 2. McGraw-Hill,London, 1968.

[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.

[15] Mark Lutz. Programming python. .O’Reilly Media, Inc.", 2010.

[16] Walter Mora. Interpolación polinomial.

417
[17] Shoichiro Nakamura and Óscar Alfredo Palmas Velasco. Métodos numéricos aplicados con
software. Number QA297 N34. Prentice-Hall Hispanoamericana Mexico, 1992.

[18] Francisco Palacios. Resolución aproximada de ecuaciones: Método de newton-raphson.

[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.

[23] Universidad de Antioquia.

[24] Varpa. Metodos de gradiente. retrieved from http://www.varpa.org/ mgpenedo/cursos/ip/tema7/nodo72 .html.

[25] www.sc.ehu.es.

[26] S. (2004). ZoZaya. Método de las diferencias finitas y su aplicación a la electrostática.

418

También podría gustarte