Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Curso de Computacion Cientifica-V3.2 2016-09-15
Curso de Computacion Cientifica-V3.2 2016-09-15
Publicación 2016-09-15
3. Análisis de errores 29
3.1. Tipos de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2. Propagación de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3. Sensibilidad y Acondicionamiento de un problema . . . . . . . . . . . . . . . . . 32
3.4. Estabilidad y Exactitud de un algoritmo . . . . . . . . . . . . . . . . . . . . . . . 32
3.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4. Programas ejecutables 35
4.1. Reutilizando el código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2. Definiendo funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.3. Entrada de datos por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4. Definiendo un módulo personal . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
I
4.5. Estructura de un programa o script y normas de escritura . . . . . . . . . . . . . . 43
4.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5. Control de flujo 47
5.1. El bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2. El bucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.3. Sentencias condicionadas if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.4. Declaraciones break y continue y sentencia else en bucles . . . . . . . . . . . . . 56
5.5. Atrapando los errores. Sentencia try-except . . . . . . . . . . . . . . . . . . . . . 57
5.6. Una aplicación interesante: Raíces de ecuaciones algebraicas cualquiera . . . . . . 59
5.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
II
10.9. Guardando las figuras creadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10.10. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
III
16.4. Cálculo de derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
16.5. Expansión de series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
16.6. Integración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
16.7. Ecuaciones algebraicas y álgebra lineal . . . . . . . . . . . . . . . . . . . . . . . 166
16.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
IV
Curso de Computación Científica, Publicación 2016-09-15
Índice general 1
Curso de Computación Científica, Publicación 2016-09-15
2 Índice general
CAPÍTULO 1
En pocas palabras, consiste en el diseño y análisis de algoritmos para resolver problemas matemá-
ticos en muchos campos, especialmente en Ciencia e Ingeniería. Los algoritmos son la traducción
y síntesis, en operaciones algebraicas sencillas, de la solución a problemas de cálculo de diversa
complejidad.
La Física como Ciencia Experimental y Observacional. El objetivo de la física es entender los
procesos de la naturaleza. Esta comprensión se alcanza a partir de una modelización matemática
de las observaciones y/o medidas experimentales. El físico reúne información sobre una serie de
magnitudes relevantes en un fenómeno natural, bajo la forma de una o varias medidas u observa-
ciones, expresadas como cantidades de una cierta unidad, acompañadas de sus correspondientes
incertidumbres e intenta producir un modelo que reproduzca el fenómeno natural.
El modelo matemático. Es un símil matemático que pretende reflejar cierta parte del universo y/o
su evolución en el tiempo. Para ser válido, o mejor para ser útil, debe tener dos valores funda-
mentales: el de la representación y el de la predicción. El físico debe ser capaz de condensar la
información obtenida a partir de numerosas observaciones de algún evento en el universo (datos)
en una forma reducida (modelo) y ser capaz de evaluar, a partir de él, los posibles resultados futuros
de otras observaciones a la vez que entender y explicar resultados obtenidos con anterioridad. La
mayor parte de los modelos implican funciones continuas: longitud, tiempo, temperatura, presión,
corriente eléctrica, energía, etc... que no pueden evaluarse matemáticamente de forma exacta.
Los cálculos en los modelos NO pueden resolverse exactamente. Por lo tanto debemos buscar:
1. Un resultado aproximadamente correcto o suficientemente cercano al valor correcto, es de-
cir, encontrar algoritmos que converjan rápidamente hacia la solución verdadera (en el lími-
te).
2. La forma de evaluar la aproximación de los resultados es decir, estimar la exactitud y/o
precisión de las soluciones aproximadas.
La simulación numérica o computacional. La simulación numérica es la representación y emu-
3
Curso de Computación Científica, Publicación 2016-09-15
Generalmente el procedimiento que se sigue para la resolución de problemas incluye los siguientes
pasos:
1. Desarrollar un modelo matemático del problema.
2. Desarrollar un algoritmo para resolver las ecuaciones de dicho modelo.
3. Implementar el algoritmo en un lenguaje de computación creando un programa o script.
4. Hacer funcionar el programa en un computador.
5. Representar los resultados de la computación.
6. Interpretar y evaluar los resultados y sus errores.
En este curso de introducción a la Computación Científica vamos a aprender a desarrollar estos
puntos e insistir especialmente en el 3, 4, 5 y 6.
Desarrollar el primer paso consiste en realizar una modelización matemática. Un problema mate-
mático se dice que está bien planteado si:
la solución existe,
es única y,
depende de forma continua de los datos del problema; es decir, que una pequeña variación
en los datos iniciales no causa grandes cambios en el resultado.
Pero, no siempre es posible obtener problemas bien planteados y entonces se habla de problemas
pobremente (o mal) planteados (” ill-conditioned problems”). Aún en problemas bien planteados
pueden aparecer problemas de perturbaciones en la computación que deben evitarse con algoritmos
estables.
Veremos a lo largo del curso que una estrategia básica en la solución de cualquier problema en
Física, en general en Ciencia o en Ingeniería, que requiera de computación es:
Reemplazar un problema difícil o complejo por otro más fácil o simple que tenga la misma solución
o, al menos, una solución ‘muy cercana’ a la del problema original.
En general, lo podemos conseguir, por ejemplo, reemplazando según sea el problema que se tenga
planteado:
Funciones complicadas por otras más simples (polinomios, series de Taylor, etc...).
Matrices complejas en general, por otras más simples (diagonales, trangulares, etc...).
Problemas no lineales por otros lineales aunque sean más largos y tediosos.
Ecuaciones diferenciales por sistemas de muchas ecuaciones algebraicas (de diferencias fi-
nitas).
Sistemas de orden alto por otros de orden bajo.
Procesos infinitos por otros finitos (integrales por sumatorios, derivadas por diferencias fini-
tas, etc...).
Por lo tanto, normalmente debemos: Encontrar un problema alternativo al problema planteado, o
una serie de ellos, que sea más sencillo de solucionar, y que la transformación de uno en otro sea
tal que preserve la solución del problema original, en algún sentido.
Idealmente, la solución al problema alternativo aproximado no siempre coincide con la del ori-
ginal, pero generalmente podremos aproximarla tanto como queramos pagando el precio de más
almacenamiento de datos y/o más trabajo de cálculo. Por lo tanto, debemos estar siempre atentos a
la hora de estimar la exactitud y precisión de la solución aproximada y establecer la convergencia
hacia la solución verdadera del problema original en el límite apropiado.
Hay muchas fuentes de aproximación que se suelen utilizar en la computación en diferentes fases
del desarrollo de la solución al problema planteado. Por ejemplo, algunos de ellos son:
1. ANTES de empezar el cálculo o computación,
Construyendo el MODELO. Si el problema es muy complejo, siempre podemos olvi-
dar o despreciar algunos detalles (obviamente, de poca importancia) de la física del
problema (fricción, viscosidad, aislamiento, etc...)
Tomando los DATOS. Siempre podemos realizar más y/o mejores MEDIDAS u OB-
SERVACIONES que eviten o minimicen ruido o sesgos, o también completar una
muestra insuficiente
Preparando COMPUTACIONES PREVIAS. Tratar de mejorar la precisión y exacti-
tud en los cálculos previos necesarios para los datos de entrada y que solamente son
aproximadas.
2. DURANTE el proceso de computación, en los procesos de
TRUNCAMIENTO, usando un número finito de términos en una serie en vez de toda
ella,
DISCRETIZACIÓN, por ejemplo usando diferencias finitas en los valores de las varia-
bles, en vez de derivadas,
REDONDEO, cuando calculamos (a mano, o usando la calculadora o el computador)
la representación de los números reales y las operaciones aritméticas están limitadas
intrínsecamente por la precisión finita de ellos.
La expresión de un resultado científico siempre debe hacerse utilizando solamente sus cifras sig-
nificativas y/o indicando el error en el resultado. Para ello debemos conocer o estimar el posible
error en tal resultado.
El sentido o significado de un error está relacionado con la magnitud de la cantidad que se mide o
se computa. De esta forma se define:
Error absoluto = VALOR APROXIMADO - VALOR VERDADERO.
Error relativo = ERROR ABSOLUTO / VALOR VERDADERO.
A veces se define como error absoluto el valor absoluto de la definición anterior. Normalmente
no conocemos el valor verdadero, de forma que simplemente acotaremos o estimaremos el error
en vez de computarlo exactamente. Como consecuencia, el error relativo se toma generalmente
respecto al valor aproximado si no conocemos el verdadero. El error relativo multiplicado por 100
nos da el error porcentual (en tanto por ciento) en el resultado.
Una interpretación interesante del error relativo en una medida o cálculo es el siguiente: si un
valor aproximado tiene un error relativo de 1.0 × 10−𝑛 , entonces su representación decimal tiene
𝑛 dígitos.
Los dígitos o cifras significativas del valor numérico de una magnitud (o medida) son aquellos
dígitos que tienen significado, la cantidad de cifras significativas de dicha magnitud se define del
siguiente modo:
1. El primer dígito diferente de cero por la izquierda es el más significativo.
2. Si no hay punto decimal, el dígito más a la derecha distinto de cero es el menos significativo.
3. Si hay un punto decimal, el dígito más a la derecha es el menos significativo aunque sea un
0.
4. Todos los dígitos entre el más y el menos significativo son contados como cifras significati-
vas.
Por ejemplo: 780 y 0.0078 tienen el mismo número de cifras significativas (dos); en cambio 780.0
y 0.7800 tienen cuatro. Otro ejemplo: 430, 430., 430.0 tienen diferentes cifras significativas (dos,
tres y cuatro respectivamente) ya que fueron obtenidas con precisión de una decena, una unidad y
una décima, respectivamente.
Usar la notación científica tiene la ventaja que usa siempre todas las cifras significativas de un
número, y sólo ellas, con un argumento en notación decimal y acompañado con la potencia de 10
apropiada para representarlo. Por ejemplo, 320 lo escribiríamos 3.2 × 102 . Otro ejemplo: 86420 y
0.00008642 (ambos con 4 cifras significativas) los escribimos como 8.642×104 y 8.642×10−5 . En
python y, en general cualquier lenguaje de ordenador, estos numeros se escribirán así: 8.642e04 y
8.642e-05 .
En el caso de resultados experimentales productos de la observación, el número de cifras significa-
tivas que se escriben suele ser una más que la que da la precisión experimental para evitar errores
de redondeo en sucesivos cálculos. Por ejemplo, si medimos una longitud como 1.348 m con una
incertidumbre de 0.011 m, el resultado podríamos expresarlo como: 1.348 ± 0.011; no obstante, si
la incertidumbre es 0.073 (el dígito más significativo es mayor que 5) podríamos expresarlo como:
1.35 ± 0.07. Fijénse que hemos redondeado el dígito menos significativo.
Esto nos lleva a los conceptos de precisión y exactitud. La diferencia entre precisión o exactitud
en una medida o cálculo la podemos establecer aquí como:
PRECISIÓN. Se refiere al número de dígitos o cifras significativas con el que se expresa una
medida. Es una valoración de lo ‘cuidadoso’ que ha sido el cálculo o la medida. Cuantos más
sean mayor es la precisión.
EXACTITUD. Se relaciona mejor con un error absoluto pequeño; cuanto más pequeño más
exacto. Es una valoración de cuán cercano al valor real o verdadero es una medida o cálculo.
Por ejemplo: tengamos el número 3.141345654320976810345 con todas las cifras significativas
como resultado de una medida; se trata de un número muy preciso (22 cifras significativas) pero
muy poco exacto como valor de 𝜋 (su error absoluto es de 0.0003). En cambio, 3.1416 (5 cifras
significativas) es un valor menos preciso pero más aproximado (exacto) a 𝜋 (número irracional que
vale 3.14159265...) que el anterior.
En cualquier ordenador o calculadora los números se almacenan en un número finito de bits. Los
números enteros no tienen problema y se almacenan exactamente, si disponemos del número su-
ficiente de bits para ello. Sin embargo, en el caso de los números reales esto no ocurre así ya que
muchos de ellos no tienen representación exacta y se procede al redondeo para poder representar-
los; de hecho, se habla de números en coma flotante (floating point). Su representación se toma de
la notación científica y consta de: signo, mantisa y exponente; por ejemplo: −3.9267 × 10−8 , el
signo es negativo, la mantisa es 3.9267 y el exponente es -8. La notación habitual que nos ofrecen
los ordenadores es -3.9267e-8 o también -3.9267E-8.
El almacenamiento de estos números en un ordenador depende de las características de éste y, a
veces, del lenguaje de programación que utilicemos. Python sigue la norma “IEEE standard 754”
que consiste en representar los números en coma flotante en una notación binaria normalizada
(tiene una base 2 y una mantisa menor que 2). Utiliza para ello 64 bits de los cuales reserva uno
para el signo, 52 para la mantisa y 11 para el exponente. Esto determina el intervalo de números
que puede almacenar; alrededor de 2.2 × 10−308 y 1.7 × 10308 , es decir, lo más aproximado a 0 e
infinito que tenemos.
Todo ello hace que tengamos errores, normalmente pequeños, al hacer cálculos con números en
coma flotante. Algunas de las características peculiares que todo esto conlleva son las siguientes:
En la pantalla, Python nos ofrece sólo los primeros 17 decimales, el último redondeado.
El redondeo consiste en buscar el número en coma flotante más próximo al que queremos
representar.
No todos los números reales pueden representarse así; por ejemplo, prueben con 0.1. Se
redondea el último.
La precisión depende del propio número. Cuanto más próximos a 0 sean los números me-
jor precisión podremos obtener en las operaciones con ellos; para números grandes vamos
perdiendo precisión; pueden comprobarlo probando a sumar a 1e8 la cantidad de 1e-9.
Volviendo a los números enteros, normalmente en Python se almacenan en 32 bits (el mayor sería
231 ) sin redondeos. No obstante, Python utiliza también los llamados enteros largos que utilizan
el número de bits necesario para almacenar el entero de que se trate. Es muy rápido operar con
enteros pero si el resultado de una operación es un entero que no le cabe en 32 bits, Python se
encarga de alargarlo convenientemente, con el consiguiente mayor gasto en memoria y en rapidez
de cálculo; esto no sucede así en otros lenguajes de programación pero es muy cómodo no tener
que preocuparse de estos problemas.
Nota: Les será muy útil que repasen el Sistema Internacional de Unidades, que pueden encontrar
en cualquiera de estas direcciones en la red, entre otras:
http://es.wikipedia.org/wiki/Sistema_Internacional_de_Unidades
http://en.wikipedia.org/wiki/International_System_of_Units
http://physics.nist.gov/cuu/Units/
En la siguiente dirección podemos encontrar además tablas con múltiplos y submúltiplos decimales
(potencias de 10): http://recursos.citcea.upc.edu/unidades/sicas.html
1.6 Ejercicios
1. Calcular los errores absolutos y relativos si se aproxima el valor de 𝜋 por las siguientes
cantidades: 3 ; 3.14 ; 22/7 .
2. Si a, con error relativo R, es el valor aproximado para una cantidad cuyo valor verdadero es
v, probar a partir de las definiciones que a = v (1 + R).
3. Tenemos que 𝑥 = 0.012, con dos cifras significativas. Si 𝑦 = exp(𝑥), encontrar con cuántas
cifras significativas podemos evaluar 𝑦.
Para crear un algoritmo para resolver un problema matemático, necesitamos conocer un lenguaje de
programación e implementarlo en un ordenador. Un lenguaje de programación, como los lenguajes
naturales, son una serie de órdenes que nos permiten “hablar” con un ordenador pero a diferencia
de los naturales, los lenguajes de programación son estrictos y no permiten ambigüedades. Existen
cientos de lenguajes de programación, cada uno con sus peculiaridades, pero solo con unos pocos
se han programado la mayoría de los programas que usamos diario como navegadores, editores de
texto, aplicaciones en smartphones o muchas páginas web como Wikipedia o Facebook. Entre los
lenguajes más usados están C/C++, Java o Python, aunque en ciencia también son muy populares
FORTRAN y Matlab, entre otros.
En cualquier caso, el objetivo de un lenguaje de programación es ofrecer un idioma relativamente
sencillo con el que dar órdenes a un ordenador; éste, no comprende directamente ningún lenguaje
de programación, únicamente el llamado “lenguaje de máquina”, que es propio de cada tipo de
ordenador y por tanto el programa, hecho en cualquier lenguaje, debe traducirse a este lenguaje de
máquina.
Según cómo se convierte un código o programa a lenguaje de máquina, los lenguajes se clasifican
en dos tipos, los compilados, como C/C++ o FORTRAN, en los cuales mediante una orden se
traduce (compila) completamente el programa una única vez generándose un programa ejecutable
que se puede usar cuantas veces queramos. Por otro lado están los lenguajes interpretados como
Python, Perl o Ruby, que se van traduciendo y ejecutando línea a línea. Por regla general los
programas compilados son más rápidos, pero los interpretados tienen la ventaja de que son mucho
más interactivos y flexibles y es mucho más facil y rápido programar y corregir errores con ellos y
por eso se usan mucho en el análisis de datos.
Python es lo que se denomina un lenguaje interpretado (no compilado), que puede utilizarse como
un programa ejecutable desde una terminal de comandos o también, de manera interactiva mediante
una consola de comandos, donde se dan instrucciones línea a línea. Python incorpora una consola
por defecto, pero también existen otras alternativas con características muy útiles para el análisis
científico de datos. Un ejemplo es ipython 1 que se trata de una consola de Python mejorada,
incluyendo completado de funciones y variables, funcionalidad de los comandos básicos de la
1
Mucha más información en la web oficial: http://ipython.org/
9
Curso de Computación Científica, Publicación 2016-09-15
consola del sistema (cd, ls, pwd, etc.), comandos adicionales (llamados comandos mágicos) y
un largo etcétera. Esta es la consola de Python que usaremos preferentemente durante este curso,
aunque se puede trabajar sin problema con la consola por defecto.
Se puede iniciar Python de manera interactiva desde una terminal de comandos estándar de Linux
o Mac sin más que escribir python. Los usuarios de Windows deben lanzar la consola de Python
(o IPython) desde el menú de programas y se abrirá una ventana nueva. Aparecerá el prompt >>>
(en la consola estándar de Python) y entonces estará listo para empezar. Este símbolo de prompt,
que puede ser distinto en otras consolas Python, indica que el sistema está preparado para recibir
instrucciones del usuario. Veamos un comando sencillo:
>>>
Aquí hemos usado la función print() para imprimir una frase en la pantalla. Nota que la frase
está entre comillas, que pueden ser dobles (”) o simples (‘). El resultado se muestra inmediatamente
por pantalla y vuelve a aparecer de nuevo el prompt esperando por nuevos comandos. En el caso de
querer iniciar la consola ipython hay que escribir ipython y tendremos un prompt algo diferente:
In [2]:
En este caso cada línea de entrada y salida está numerada y además tenemos otras funcionalidades
muy útiles para el trabajo interactivo que iremos conociendo.
Nota: Cuando iniciamos una sesión de trabajo con ipython, suele ser muy útil guardar todo lo
que vamos haciendo para repasar o estudiar lo que se hizo. Para ello se empieza la sesión con el
comando mágico de ipython 2 %logstart con el objeto de grabarla en un fichero, por ejemplo:
te, detener temporalmente y continuar el registro de sesión que hayamos iniciado previamente
con %logstart.
Si queremos guardar todo lo que hemos hecho en un fichero ejecutable o continuar a partir de dónde
lo dejamos, tendríamos que guardarlo con la extensión .py en vez de .txt. Si posteriormente,
queremos recuperar las sesiones hechas y guardadas en un fichero de registro, podemos hacerlo
con el comando run dentro de ipython:
In [1]: run clase_25_septiembre_2015.py
al hacerlo, se ejecuta todo el código de entrada que está en el fichero, repitiendo una a una todas
las operaciones.
En cualquier lenguaje de programación existen distintos tipos de datos, que podemos almacenar y
operar de forma diferente con ellos y que poseen distintas propiedades. Los más comunes son las
llamadas cadenas de texto o string (indicadas siempre entre comillas) y los números ( int o float,
enteros o de “coma flotante” respectivamente):
>>> print("Esta es una linea de texto")
Esta es una linea de texto
>>> print(28) # este es un entero
28
Las variables son componentes fundamentales de un lenguaje de programación y no son más que
un nombre que se refiere a un registro en el computador (una serie de bits de memoria) que contiene
uno o varios datos, que pueden ser de distinto tipo. Veamos unos ejemplos:
>>> frase = "Esta es una linea de texto"
>>> num = 22
>>> num*2
44
>>> frase*2
Esta es una linea de textoEsta es una linea de texto
Aquí, frase es una variable tipo string mientras que num es otra variable numérica entera. Nótese
que mientras se multiplicó num por 2 de forma conocida, la cadena de texto frase fué duplicada
al multiplicarla por 2. En este caso hemos operado con dos tipos de datos distintos, un string y
un int, algo que muchos lenguajes de programación produce un error por no tener sentido, sin
embargo, Python interpreta el producto de un string y un int como la unión o concatenación de la
misma cadena de texto varias veces, y es por eso que vemos frase duplicada.
A lo largo del trabajo podemos acabar definiendo muchas variables de distinto tipo. Con el coman-
do type() podemos saber el tipo de dato que se asigna a una variable si en cualquier momento
>>> type(frase)
<type 'str'>
>>> type(num)
<type 'int'>
Los nombres de las variables pueden ser cualquier combinación de letras y números (siempre
que no empiece por número) pero no están permitidos carácteres especiales como tildes, puntos,
espacios en blanco, etc. Algunas palabras están además reservadas ya por el propio Python para su
uso como: def, class, return, etc. por lo que tampoco las podremos usar como nombres de
variables.
Algo muy importante es que, como casi todos los lenguajes de programación, hay una distinción
clara y de funcionalidad entre enteros y decimales, es decir, entre int y float; aunque ambos sean
valores numéricos, tienen propiedades distintas que afectan al cálculo. Así, la variable num la
hemos definido de tipo entero estricto y el cálculo entre números enteros siempre da como
resultado otro número entero, redondeándose al más cercano en caso de no ser entero exacto,
por ejemplo:
>>> 113/27
4 # en lugar de 4.1851851851851851
Si queremos usar números decimales, también llamados de coma flotante, debe emplearse directa-
mente un valor decimal (float); en estos casos:
>>> 113.0/27.0
4.1851851851851851
>>> type(113.0/27.0)
<type 'float'>
Es decir, numero = 3 no es lo mismo que numero = 3.0 aunque ambos tengan el mismo
valor. Como dijimos en el tema anterior, Python emplea números de 64 bits (por defecto) en los
float. En cualquier operación es muy importante usar los enteros y float correctamente y tener
cuidado al mezclarlos, de otro modo se obtendrá un resultado no deseado llegando a hacernos
creer que el computador se ha equivocado.
Algunos tipos de datos pueden ser convertidos de unos a otros, empleando str() para convertir
a cadena de texto, int() a entero y float() a coma flotante:
>>> float(3)
3.0
>>> int(3.1416)
3
>>> str(34)
'34'
Para el caso de los float, se pueden redondear con round(), que redondea al entero más próxi-
mo. La funciones ceil() y floor() del módulo math redondean hacia arriba y hacia abajo
respectivamente:
Más adelante veremos qué son los módulos, que ofrecen otras funciones predefinidas muy intere-
santes y aprenderemos cómo importarlos y a utilizarlos.
Nota: La versión de Python que empleamos siempre en este curso es la 2.7, que la más usada
actualmente, aunque la versión más moderna es la serie 3.0, en concreto la versión Python 3.5.
Entonces ¿por qué no usamos Python 3.0? El motivo es porque hay varias diferencias importantes
entre la series Python 2 y Python 3 y aunque el lenguaje es el mismo, hay aún muchos paquetes
que no están completamente soportados en Python 3, por lo que por ahora Python 2.7 es aún con
diferencia la versión de Python más usada, aunque Python 3 lleva existiendo muchos años. Desde
luego, Python 3 es una versión más moderna y mejor, por lo que con el tiempo poco a poco se va
pasando a Python 3. Una peculiaridad de Python 3 es su forma de operar entre enteros. A diferencia
de muchos lenguajes de programación, incluyendo Python 2, la división entre entero devuelve un
float:
Con python se pueden hacer las operaciones aritméticas habituales usando los símbolos corres-
pondientes:
Operación Símbolo
Suma +
Resta −
Multiplicación *
División /
Exponenciación **
Residuo o resto %
La prioridad en la ejecución (de mayor a menor, separados por ;) es la siguiente: **; *, /, %; +, - .:
>>> x=3.5
>>> x**2
12.25
>>> x+x**2
15.75
>>> x**(2./3.)
2.3052181460292234
>>> x**(2/3)
1.0
>>> 123%8
3
>>> x+x/2+x*x-x**1.25-x%2
11.212761600464169
Los operadores lógicos son aquellos operadores que permiten comparar valores entre sí. En con-
creto se usan:
Operacion Simbolo
Igual a (comparación) ==
Distinto de (comparación) != o <>
Mayor que, Menor que >, <
Mayor o igual, Menor o igual >=, =<
y, o and, or
cierto, falso True, False
Como resultado de una operación lógica, obtenemos como respuesta un elemento, True o False,
según se verifique o no la operación. Estos elemento lógicos los podemos usar a su vez para otras
operaciones. Veamos algunos ejemplos:
Ahora que conocemos los operadores lógicos, podemos consultar si una variable es de un tipo
concreto:
En este caso ìnt y float no son cadenas de texto, sino un indicador del tipo de dato. Esto
se puede usar para cualquier tipo de dato más complejos, no únicamente números o cadenas, los
cuales veremos más adelante.
Las cadenas de texto, como hemos visto, no son mas que texto formado por letras y números de
cualquier longitud y son fácilmente manipulables. Para poder hacerlo, cada carácter de una cadena
de texto tiene asociado un índice que indica su posición en la cadena, siendo 0 el de la izquierda
del todo (primero), 1 el siguiente hacia la derecha y así sucesivamente hasta el último. Veamos
ejemplos:
hombros de gigantes"
>>> print frase[0] # Primera letra de la cadena
S
más lejos,
También se pueden referir con índices contando desde la derecha, usando índices negativos, siendo
-1 el primero por la derecha:
s
>>> print( frase[-1] == frase[len(frase)-1] ) # Compruebo si son
˓→iguales
True
Desde luego es irrelevante qué sistema de índices se use, ya que sólo son dos formas distintas de
referirse a un mismo caracter, pero lo más habitual es usar índices positivos de izquierda a derecha.
Figura 2.1: Ejemplo de indexado de cadenas de texto con Python. Nótese que el último índice del
rango no está incluído en la selección.
Los caracteres en Python son de tipo ascii (7 bits), si queremos que no haya problemas con los
acentos o con la letra ñ tienen que ser definidos como de tipo unicode de 8 bits. Para no te-
ner problemas con estos símbolos en la variable frase, la podemos definir como un string de
caracteres unicode.
>>> frase = u"Si he logrado ver más lejos, ha sido porque he subido a
hombros de gigantes"
>>> type(frase)
<type 'unicode'>
Existen varios métodos o funciones específicas para tratar y manipular cadenas de texto, éstos nos
permiten cambiar de varias maneras las cadenas. Veamos algunos:
Probar qué ocurriría si no hubiésemos definido la variable frase como un string de caracteres de
tipo unicode:
# la variable frase_minusculas
>>> print(frase_minusculas)
si he logrado ver más lejos, ha sido porque he subido a hombros de
˓→gigantes
Advertencia: Recordar que el índice en las cadenas de texto y en general cualquier lista de
números, empieza siempre con el 0, por lo que el primer elemento de una cadena de texto o
de una lista es frase[0] y no frase[1]. Al escribir frase[10] estamos tomando el
elemento ordinal 11, no el 10.
Sin embargo, la concatenación sólo es posible para texto (string), por lo que no se pueden conca-
tenar letras y números. Una posibilidad es convertir los números a string:
Una manera más práctica y correcta de hacer esto es imprimiendo los números con el formato que
queramos; veamos como hacerlo:
Aqui se reemplaza cada símbolo %s (para cadenas de texto), %d (para enteros) o %f (para floats)
sucesivamente con los valores después de % que están entre paréntesis. En caso de los floats se
puede utilizar el formato %10.5f, que significa imprimir 10 carácteres en total, incluído el pun-
to, usando 5 decimales. Se puede escribir también floats en formato científico utilizando %e, por
ejemplo:
Los formatos son muy útiles a la hora de expresar el resultado de un cálculo con los dígitos signi-
ficativos solamente o con la indicación del error en el resultado. Así por ejemplo, si el resultado de
un cálculo o de una medida es 3.14158 ± 0.00013 podemos expresarlo como:
Los datos se pueden almacenar en variables univaluadas como ya hemos visto. No obstante, en
variables estructuradas también pueden almacenarse uno o más datos. Los tipos de variables
estructuradas que ofrece Python son las llamadas listas, tuplas y diccionarios que se definen de la
siguiente forma:
2.6.1 Listas
Se trata de un conjunto de números, cadenas de texto u otras listas (aquí tendríamos listas de listas),
ordenadas de alguna manera:
En el último ejemplo se puede comprobar que es posible mezclar varios tipos de datos, como
enteros, strings y hasta otras listas. Se puede utilizar la función len() para ver el número de
elementos de una lista:
>>> len(alumnos)
6
Es posible utilizar el método split() para separar por medio de los espacios una cadena de texto
cualquiera y colocar los elementos resultantes en una lista:
>>> print(palabras)
['Dios', 'no', 'juega', 'a', 'los', 'dados'] # el resultado es una
˓→lista
Como se ve en el ejemplo anterior, el método split() separa (por defecto, por espacios en
blanco) los elementos, palabras en este caso, de la cadena de texto y da como resultado una lista
con los elementos. Se puede usar otro separador, como la coma en este ejemplo la cadena ‘no’,
usándolo como parámetro. Nótese que en este caso ‘no’ ya no está en ningún elemento de la lista,
ya que ahora se usa de separador y por eso desaparece.
En este caso hemos llamado método a una función que se aplica a un tipo de dato en concreto, en
este caso un string, usando la variable y el método unidos por un punto, en lugar de un función
normal como es len(alumnos), por ejemplo.
Existen varias formas de añadir nuevos elementos a una lista existente, estas son la formas más
comunes:
>>> alumnos.index("Jairo")
>>> 3
En la última orden del ejemplo anterior hemos usado index() para encontrar el índice o posi-
ción del primer elemento de la lista que es “Jairo”.
Es posible ordenar alfabéticamente una lista con el método sort():
>>> alumnos.sort()
>>> print(alumnos)
['Fran', 'Iballa', 'Jairo', 'Luisa', 'Luisma', 'Maria', 'Miguel',
˓→'Ruyman']
Para extraer un elemento de la lista podemos usar los métodos pop() y remove():
'Jairo'
>>> print(alumnos)
['Fran', 'Iballa', 'Luisa', 'Luisma', 'Maria', 'Miguel', 'Ruyman']
>>> print(alumnos)
['Fran', 'Iballa', 'Luisa', 'Luisma', 'Miguel', 'Ruyman']
Las listas se manipulan de manera similar a las cadenas de texto, utilizando índices que indican la
posición de cada elemento siendo 0 el primer elemento de la lista y -1 el último (si se empieza a
numerar por el final):
>>> alumnos[2:6]
['Luisma', 'Fran', 'Luisa', 'Ruyman']
Y aquí va un truco. Si queremos invertir el orden de la lista, podemos hacerlo de esta manera:
Una función muy útil es la función range(), que permite crear una lista de números enteros. Por
ejemplo, para crear un lista de 10 elementos, de 0 a 9, e imprimirlos podemos hacer esto:
Con esta función se puede crear también una lista de números indicando el inicio, final y el inter-
valo entre dos consecutivos. Por ejemplo, para crear una lista con números enteros de 100 a 200 a
intervalos de 20 podemos escribir:
Nótese que el último número, 200, no se incluye la lista. La función range() se emplea para
generar listas de números enteros solamente. Más adelante veremos cómo crear listas similares de
floats.
Con las variables listas podemos operar de forma parecida a las cadenas de texto, por ejemplo:
Como se ve en este ejemplo, se pueden unir dos listas usando + o incluso multiplicarla por un
número entero, que equivale a sumar varias veces la misma lista.
La forma de indexar las listas y los strings de Python, de manera que el segundo índice en un rango
no se incluye en la sublista resultante, puede parecer confuso, pero está bien pensado. Recordemos
esto con un ejemplo
>>> numeros = range(1, 11) # lista de números de 1 a 10 (11 no
˓→incluido)
como vemos, el mismo índice (5) con que termina la primera la lista, empieza la segunda. De esta
manera, si queremos recuperar la lista original sólo tenemos que unir las dos partes:
>>> print primeros5 + ultimos5
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y así las cuentas y manipulaciones con strings y listas se hacen más fácilmente.
Las tuplas son listas que no se pueden modificar o alterar y se definen enumerando sus elementos
entre paréntesis en lugar de corchetes, por ejemplo:
>>> lista_alumnos = ('Miguel', 'Maria', 'Luisma', 'Fran', 'Luisa',
˓→'Ruyman')
También podemos definirlas como una variable con varios valores separados por comas; python
interpreta esto como una tupla aunque no esté entre paréntesis:
>>> print(c)
>>> (1, 3)
>>> type(c)
<type 'tuple'>
>>> print(c[0]) # imprimimos el primer elemento de la tupla
1
>>> print(c[1]) # imprimimoa el segundo elemento de la tupla
3
Si se intenta insertar, quitar o reordenar la tupla con los comandos que ya conocemos para las
listas, o en definitiva modificar la tupla, aparecerá un error indicando que no es posible.
2.6.3 Diccionarios
Los diccionarios son listas en las que cada elemento se identifica no con un supuesto índice (nú-
mero de orden), sino con un nombre o clave, por lo que siempre se usan en parejas clave-valor
separadas por ”:”. La clave va primero y siempre entre comillas y luego su valor, que puede ser
en principio cualquier tipo de dato de Python; cada pareja clave-valor se separa por comas y todo
se encierra entre llaves. Por ejemplo, podemos crear un diccionario con los datos básicos de una
persona:
>>> type(datos)
<type 'dict'>
En este caso hemos creado una clave “Nombre” con valor “Juan”, otra clave “Apellido” con valor
“Martínez”, etc. Al crear los datos con esta estructura, podemos acceder a los valores de las claves
fácilmente:
Fíjense que ya no podemos acceder a los datos por su índice y obtenemos error:
>>> print(datos[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 0
También podemos conocer todas las claves y los valores de un diccionario usando los métodos
keys() y values() respectivamente:
>>> datos.keys()
['Apellidos', 'Nombre', 'Altura']
>>> datos.values()
['Martinez', 'Juan', 1.6699999999999999]
Como vemos, la respuesta de estos dos métodos es una lista con las claves y valores del diccionario,
que podemos manipular como tales.
Python viene con muchos módulos (un conjunto de funciones) que ofrecen funcionalidades adi-
cionales muy interesantes. Uno de ellos es el módulo de funciones matemáticas básicas math, que
ya hemos visto brevemente, otro el paquete de utilidades del sistema sys y muchos más. Se puede
importar un módulo cualquiera haciéndolo implícitamente, o sea importando el módulo completo
o bien nombrando una, varias o todas sus funciones; veamos cómo hacerlo:
>>> from math import sin, cos, pi # importa funciones sin, cos y pi
˓→de math
Podemos ver un listado de las funciones que ofrece un módulo usando la función dir():
Para conocer otros módulo de la librería estándar pueden consultar el “tutorial” oficial de Python
o la guía oficial de la Librería de Python. En ocasiones nos encontraremos con módulos más gran-
des y complejos, que en realidad son módulos que contienen otros módulos y se les suele llamar
paquetes, aunque se usan de manera similar y a veces se mezclan una y otra definición.
Por nuestra parte iremos introduciendo más adelante otros módulos y paquetes numéricos de Pyt-
hon más avanzados que nos aportan éstas y muchas otras funciones matemáticas, muy útiles para
la computación científica.
Prácticamente todas las funciones de Python tienen un texto de ayuda que nos explican qué hacen
y cómo funcionan. Para consultar esta ayuda usamos la función help() y entre paréntesis el
nombre de la función que queremos consultar:
>>> help(round)
round(...)
round(number[, ndigits]) -> floating point number
Si lo que queremos consultar es un método (es decir, una función que se aplica a un tipo de dato
o variable en concreto) y no una función general, podemos ver la ayuda empleando la variable o
elemento con el que queremos usarlo:
de esta manera vemos la ayuda del método replace() de los string. Deberemos teclear q para
salir de la ayuda. Como acabamos de ver, si queremos ver las funciones disponibles en un módulo
podemos hacerlo con dir(modulo). Si usamos ipython, también podemos obtener la ayuda
añadiendo ? a la función, método o módulo que nos interese:
Si escribimos help() sin ningún parámetro, entramos en un entorno de ayuda general de Python,
donde además de la ayuda de funciones y módulos también podemos saber los módulos instalados
en nuestro ordenador entre otras cosas.
Recuerda que la fuente de información más completa y actualizada es la documentación oficial de
Python, donde además de una guía de uso (tutorial), hay una referencia completa de las funciones
y módulos que Python trae por defecto.
2.9 Ejercicios
1. Calcular a mano cada una de las expresiones siguientes y comprobar, con la ayuda de Python
en el ordenador, si el resultado es correcto:
16**(1/2)+1/2
16**(1./2)+1/2
3e3/10
15/5e-3+1
2. Evaluar en punto flotante las siguientes funciones para los valores de x=-0.5, 1.1, 2, 3.1:
𝑥4 + 𝑥3 + 2𝑥2 − 𝑥 + 3
√︀
4𝑥 3 | sin 𝑥 + tan 𝑥|
𝑥3 −5𝑥
(𝑥2 + 12 )2
3. Calcular a mano cada una de las expresiones siguientes y comprobar, con la ayuda de Python
en el ordenador, si el resultado es correcto:
int(exp(2*log(3)))
round(4*sin(3*pi/2))
abs(log10(0.01)*sqrt(25))
round(3.21123*log10(1000),3)
4. Calculen a mano cada una de las expresiones siguientes y comprueben, con la ayuda de
Python en el ordenador, si ambas soluciones son iguales:
str(2.1) + str(1.2)
int(str(5) + str(7))
int('5' + '7')
str(5 + 7)
str(int(83.6) + float(7))
5. Dada la frase de S. Hawking: “Dios no solo juega a los dados, a veces los tira donde no se
pueden ver”, ¿qué funciones o comandos de Python utilizarían para responder o ejecutar lo
siguiente?:
¿Cuántos carácteres tiene (incluyendo letras, espacios y signos de puntuación)? ¿y pa-
labras?
Pasa a una variable los 15 primeros carácteres. Pasa a una lista las cinco primeras
palabras.
2.9. Ejercicios 27
Curso de Computación Científica, Publicación 2016-09-15
11. Crear una variable que valga 174 (entero) y calcular su raíz cuadrada que almacenan en otra
variable. Imprimir ahora una frase que diga “La raiz cuadrada de 174 es 13.1909.” usando
las variables creadas junto con el formato de impresión adecuado.
12. Consideremos una lista de python llamada “cosas” que posee distintos tipos de datos:
Análisis de errores
Aunque a veces algunos errores en un cálculo se pueden atribuir a los datos de entrada, en otras
ocasiones se atribuyen al propio cómputo o cálculo. Tal distinción no siempre es clara. veámoslo
con un ejemplo: en el caso del problema típico de evaluar una función 𝑓 (𝑥), donde tenemos que:
𝑥, es valor verdadero de la variable de entrada,
𝑦 = 𝑓 (𝑥), es el valor verdadero deseado de salida,
𝑥𝑖 , es valor inexacto o aproximado de entrada,
𝑓𝑎 , es la aproximación mejor (algoritmo) a la función 𝑓 ,
podemos calcular que el valor del error absoluto total acumulado en el cálculo será:
que podemos asignar como: [error del cómputo] + [error propagado de los datos].
Fíjense que el algoritmo escogido para realizar el cálculo no influye en el segundo sumando y que,
por lo tanto, sólo es el resultado de la propagación de los errores de los datos. El valor del primer
sumando depende del algoritmo escogido y el segundo depende de cuánto se aproxime el valor
aproximado usado al valor verdadero.
Además, en el error del cómputo, podemos diferenciar los siguientes tipos de error:
1. Error de truncamiento. Diferencia entre el resultado verdadero (dada la entrada real) y el
resultado que se produciría usando un algoritmo determinado utilizando aritmética exacta.
Depende básicamente de la máquina de cómputo empleada. El truncamiento es el número
de dígitos a la derecha del separador decimal con el que cuenta nuestro sistema de cálculo.
Algunas veces proporcionará el mismo resultado que el redondeo, pero el truncamiento su-
pone poner un límite al número de dígitos decimales por lo que, en definitiva, produce un
29
Curso de Computación Científica, Publicación 2016-09-15
redondeo hacia abajo. El error de truncamiento puede ser hasta el doble del error máximo de
redondeo.
2. Error de redondeo. Es la diferencia entre el resultado obtenido con un algoritmo con aritméti-
ca exacta y el resultado producido por el mismo algoritmo utilizando aritmética de precisión
finita.
Por medio de la resolución de un ejemplo práctico podemos entender mejor estos conceptos:
Ejemplo: Calculen una aproximación al 𝑠𝑒𝑛(𝜋/10) y los errores (computacional, de los datos y
el total) que cometen tomando como dato de entrada (𝑥𝑖 ) el valor de 𝜋 ≈ 3 y el del algoritmo
aproximado (𝑓𝑎 ) como 𝑠𝑒𝑛(𝑥) ≈ 𝑥.
Solución: error computacional = 0.00448 , error datos = -0.013497. Pero continuen un poco más
para responder a estas preguntas:
¿Qué pasaría si repetimos los cálculos para 𝑠𝑒𝑛(𝜋/50) o 𝑠𝑒𝑛(𝜋/5)?
¿Cuál es el resultado si utilizamos una calculadora simple en vez del computador?, ¿a todos
les sale igual?, ¿Cómo explican las diferencias si las hay?.
RECUERDEN que: la calidad en el resultado de un cálculo depende primordialmente de la cali-
dad de los datos de entrada, entre otras cosas. Por ejemplo, si nuestros datos de entrada tienen hasta
5 cifras significativas en el resultado del cálculo, independientemente de lo bien que lo hagamos,
no podemos esperar tener dicho resultado con más de 5 cifras significativas.
Debemos enfatizar una vez más que si los errores con que conocemos los datos de entrada son
grandes, por mucho que utilicemos un algoritmo perfecto no vamos a obtener un resultado con una
precisión mejor. Por ello, es importante estudiar como se propagan los errores y cuáles son sus
posibles fuentes durante la computación, con el objeto de minimizarlos o al menos acotarlos.
Supongamos que queremos evaluar una función 𝑦 = 𝑓 (𝑥) para un cierto valor de 𝑥 dado, y sólo
podemos obtener un valor aproximado 𝑦𝑎 , utilizando un algoritmo 𝑓𝑎 ; entonces podemos estimar
el error absoluto que cometemos definiendo ERROR ADELANTE como ∆𝑦 = 𝑦𝑎 − 𝑦.
No obstante, no siempre es posible calcularlo y, a veces, debemos resolver el problema de otra for-
ma: obtener la solución (aproximada) al problema original podría hacerse obteniendo la solución
exacta de un problema ‘’modificado’‘, pero no demasiado. Por lo tanto, podríamos preguntarnos
cuán grande puede ser la modificación al problema propuesto para obtener la solución real. En
otras palabras, definimos la cantidad ∆𝑥 = 𝑥𝑖 − 𝑥, donde 𝑓 (𝑥𝑖 ) = 𝑦𝑎 , como el ERROR ATRÁS;
es decir, cuánto error en los datos de entrada podemos ‘’admitir” para explicar todo el error obteni-
do al final. Obviamente, éste lo deberemos estimar en un análisis hacia atrás del problema. Desde
este punto de vista una solución aproximada para un problema dado es buena si es la solución
exacta a un ‘’problema parecido” al original.
Unas veces será más fácil utilizar uno u otro de los procedimientos y siempre que podamos eligi-
remos el algoritmo o procedimiento que nos de menor error.
√
Ejemplo: Queremos calcular la raíz cuadrada de 2, 𝑦 = 2, con la aproximación 𝑦𝑎 = 1.41.
Calculen los errores hacia adelante y hacia atrás.
Solución: hacia adelante: |∆𝑦|√= |𝑦𝑎 −𝑦| = |1.41−1.4142...| = 0.0042. Para calcular el error hacia
atrás tenemos en cuenta que 1.9881 = 1.41 = 𝑥𝑖 , entonces |∆𝑥| = |𝑥𝑖 − 𝑥| = |1.9881 − 2| =
0.0119.
Como ya hemos visto, a lo largo de la computación de un problema, los errores iniciales en los
datos de entrada, o en cualquier paso del cómputo, se irán transmitiendo a lo largo del proceso
(error computacional) y tendrán influencia en el resultado final. Por lo tanto es importante tratar de
mantener este error que se va propagando lo más pequeño posible y para ello es interesante saber
como se propaga el error a lo largo de un cálculo. Veámoslo con un ejemplo.
Si tenemos dos magnitudes 𝑟 y 𝑠 (cuyos errores relativos son 𝑅𝑟 y 𝑅𝑠 ) y las medimos con errores
absolutos de ∆𝑟 y ∆𝑠 , con ∆𝑟 ≪ 𝑟 y ∆𝑠 ≪ 𝑠, si efectuamos su producto 𝑄 = 𝑟·𝑠, lo obtendremos
con un error:
𝑄 = 𝑟 · 𝑠 = 𝑟𝑣 (1 + 𝑅𝑟 ) · 𝑠𝑣 (1 + 𝑅𝑠 ) = 𝑟𝑣 𝑠𝑣 (1 + 𝑅𝑟 + 𝑅𝑠 + 𝑅𝑟 𝑅𝑠 ) ≈ 𝑟𝑣 𝑠𝑣 (1 + 𝑅𝑟 + 𝑅𝑠 ) = 𝑄𝑣 (1 + 𝑅𝑄 ) ,
Una solución inexacta a un problema también puede ser debida al propio problema que queremos
resolver puesto que éste puede ser altamente sensible a pequeñas perturbaciones en los datos de
entrada. Esta cualidad es la sensibilidad, que se define como lo sensible que es la solución de un
problema a pequeñas perturbaciones o a los errores en los datos de entrada. Como vemos, tanto la
sensibilidad como su medida, el acondicionamiento, están relacionados con la propagación de los
errores en los datos de entrada. Así se habla de:
Problema bien acondicionado o insensible, si el cambio relativo en la solución del problema
puede ser menor o igual que en los datos de entrada.
Problema mal acondicionado o sensible, cuando no sucede lo anterior.
Definimos el acondicionamiento de un problema como el cociente entre el Cambio Relativo en la
Solución y el Cambio Relativo en la Entrada, es decir:
| 𝑓 (𝑥𝑖𝑓)−𝑓
(𝑥)
(𝑥)
| | Δ𝑦
𝑦
|
= ,
| (𝑥𝑖𝑥−𝑥) | | Δ𝑥
𝑥
|
o también: Error Relativo Adelante / Error Relativo Atrás , que siempre resulta difícil de obtener.
Así que a menudo se define que:
Es interesante darse cuenta que 𝐶 se interpreta como un factor de amplificación del cambio en
los datos de entrada. Si es mucho mayor que uno tendremos un problema mal acondicionado o
altamente sensible a los errores en los datos de entrada.
De la definición anteriormente efectuada, y teniendo en cuenta la aproximación 𝑓 (𝑥𝑖 ) = 𝑓 (𝑥) +
𝑓 ′ (𝑥)∆𝑥 + ... podemos calcular 𝐶 de forma aproximada como:
𝑓 ′ (𝑥)Δ𝑥
𝑓 (𝑥) 𝑥𝑓 ′ (𝑥)
𝐶= | Δ𝑥 |=| |
𝑥
𝑓 (𝑥)
estricto. Podemos ser más permisivos, estable en sentido laxo, si el algoritmo produce la solución
‘casi exacta’ al problema aproximado.
La exactitud, como ya sabemos, se refiere a lo cerca que está la solución computada de la so-
lución verdadera del problema matemático. La estabilidad de un algoritmo no garantiza que la
solución computada sea exacta. La exactitud depende tanto de la sensibilidad (acondicionamiento)
del problema matemático como de la estabilidad del algoritmo utilizado para resolverlo.
3.5 Ejercicios
3.5. Ejercicios 33
Curso de Computación Científica, Publicación 2016-09-15
Programas ejecutables
Además de usar python de manera interactiva desde una consola, es posible crear programas o
scripts (guiones) ejecutables; de hecho es la forma más habitual de trabajar. Un programa o script
es un fichero de texto con los comandos o sentencias de python escritos de manera consecutiva y
que se interpretarán línea a línea al ejecutar el script desde una consola de comandos del sistema
operativo. Bastará utilizar un editor de textos cualquiera (kate, gedit, notepad, etc.), crear un fichero
con el nombre que queramos y con extensión .py para que éste lo identifique como un programa
en Python. En él escribiremos las sentencias o comandos uno detrás de otro, en el orden en que
queramos que sean ejecutados.
El siguiente ejemplo es un pequeño programa que llamaremos cubo.py que calcula el cubo de un
número cualquiera dado por el usuario:
#!/usr/bin/python
#-*- coding: utf-8 -*-
# Mensaje de bienvenida
print("Programa de calculo del cubo de un numero.\n")
# La \n antes de las comillas introduce una línea en blanco
# Numero de entrada
x = 23.0
# Imprimo el resultado
print("El cubo de %.2f es %.2f" % (x, y))
El programa se puede ejecutar ahora desde una consola desde el directorio en donde tenemos el
archivo con el programa, escribiendo en ella
35
Curso de Computación Científica, Publicación 2016-09-15
$ python cubo.py
En la primera línea del programa hemos escrito #!/usr/bin/python (válido para Linux o Mac OS,
no Windows)para indicar la ruta donde tenemos instalado python. La segunda línea, #-*- coding:
utf-8 -*- hemos indicado el tipo de codificación UTF-8, para poder poner caracteres especiales
como tildes y eñes.
Ya que la primera línea indica en qué directorio está el ejecutable de python, el programa tam-
bién se puede ejecutar como un comando o programa del sistema escribiendo, fuera de Python,
únicamente:
$ ./cubo.py
Pero para que funcione esto tenemos que asegurarnos de que el programa tiene permisos de ejecu-
ción. Si no lo tiene se lo damos (ver Apéndice B), que para Linux o Mac se hace como sigue:
La primera línea del programa depende del sistema operativo que está instalado en el ordenador,
que en el ejemplo se supone para Linux (o Mac). En el caso de Windows debemos escribir en la
primera línea de programa algo similar a esto:
pero puede variar según la instalación de python del ordenador y además, en Windows es más
común ejecutar un programa desde el Explorador de Ficheros (File manager) donde funcionará
siempre que esté configurado para que los ficheros .py se ejecuten con Python, por lo que no
tiene tanta utilidad en el el caso de Windows. De cualquier manera, la ejecución de un programa
desde fuera de la consola de python siempre funcionará usando:
$ python cubo.py
Si quieres ejecutar el programa desde dentro de la consola de python, puedes usar lo siguente:
>>> execfile('cubo.py')
de igual manera, si estamos usando ipython podemos usar el comando mágico run:
La ventaja en este último caso con ipython es que una vez ejecutado, las variables definidas en el
programa lo estarán ahora en la sesión de ipython a nuestra disposición, lo que es muy útil para
probar y corregir nuestro programa interactivamente.
In [2]: print x
23.0
En capítulos anteriores ya hemos aprendido a usar funciones; algunas de ellas están predefinidas
(abs(), round(), etc.) mientras que otras deben importarse de módulos antes de poder ser uti-
lizadas (sin(), sqrt() se importan del módulo math). No obstante, además de las funciones
incluidas en python, el usuario puede crear las suyas propias, que permiten reutilizar partes del
código de un programa sin necesidad de repetirlo varias veces. Las funciones para poder ser utiliza-
das o llamadas o invocadas, antes hay que definirlas. Veamos por ejemplo, como podemos escribir
un programa con una sencilla función para calcular el cubo de un número y otra para imprimir un
mensaje:
#!/usr/bin/python
#-*- coding: utf-8 -*-
def cubo(x):
y = x**3
return y
# imprimo el resultado
print(resultado)
Definimos la función cubo() que calculará el cubo de un número cualquiera. Nótese que la
definición de la función se hace en líneas identadas (con sangría) y acaba con la sentencia return
para devolver el valor calculado de la función, sin imprimirlo necesariamente. Esto es lo más
habitual cuando se crea un función: hacer todas las operaciones necesarias y luego utilizar return
para devolver un valor. Después de utilizar la función, el resultado puede imprimirse, volcarse a
una variable, o lo que el programador necesite. También podemos definir funciones que dependan
de varios parámetros (separados por comas); lo único que debemos hacer a la hora de utilizarla
después será invocarla con el mismo número de parámetros y los valores que queramos en el mismo
orden que en la definición. Alternativamente, podemos definir una función en la que hacemos
varios cálculos que queremos que nos devuelva; en este caso, los podemos devolver por medio del
return en una lista que contenga dichos resultados. Más adelante veremos un ejemplo.
Vamos a definir a continuación una función con dos variables:
#!/usr/bin/python
#-*- coding: utf-8 -*-
y 3 de altura
resultado = area_triangulo(2.3,3.0)
# imprimo el resultado
print(resultado)
No obstante, no todas las funciones devuelven un valor y en este caso los llamamos procedimien-
tos. Veamos un ejemplo interesante, la función que llamamos saludo() en el que no calculamos
nada y por tanto no devolvemos un valor:
#!/usr/bin/python
#-*- coding: utf-8 -*-
def saludo(nombre):
print("Buenas tardes, %s " % nombre)
En este caso no calculamos nada y por lo tanto, no necesitamos acabar con un return la defi-
nición del procedimiento. Lo que hacemos es ordenar que se imprima una variable y por lo tanto
siempre se imprime una línea cada vez que se llama a la función.
En la definición de las funciones es posible definir y utilizar variables cuya acción y definición
queda circunscrita a la propia función. Son las llamadas variables locales que fuera de la función
no tienen validez. Veamos un ejemplo. Calculemos el volumen, area superficial y la longitud de
cualquier circunferencia máxima en una esfera de radio R definiendo una función que llamamos
esfera():
#!/usr/bin/python
#-*- coding: utf-8 -*-
# imprimimos el resultado
print('La longitud del circulo maximo es: %.3f' % l)
print('La superficie de la esfera es: %.3f' % s)
print('El volumen de la esfera es: %.3f' % v)
Nótese que en la definición de la función usamos la variable pir que es interna o local, es decir, sólo
se usa dentro de la función ya que no la devolvemos al hacer el return. Por lo tanto, si la quisiéramos
usar (o saber el valor que tiene) fuera del cuerpo de la función, en el programa principal, no
podríamos (por ejemplo, no podemos hacer print). Los parámetros de entrada en una función
también son variables locales; en este caso r. Las demás variables definidas son variables llamadas
globales ya que las podemos usar fuera del cuerpo de la función. Es conveniente no mezclarlas,
aunque podemos hacerlo si tenemos cuidado; es decir, una misma variable podemos usarla fuera
y dentro del cuerpo de la función y a pesar de que se llamen igual dentro del cuerpo de la función
tendrá el valor que sea (como local) y fuera de él también lo tendrá, como global.
Generalmente el valor de una variable se asigna haciendo por ejemplo pi=3.1416, pero se puede
hacer que pida una entrada por teclado usando la función raw_input() de la forma siguiente
(pueden probarlo en la consola de Python):
>>> entrada_numero = raw_input('Dame un numero: ' )
Dame un numero: # Escribimos 22 con el teclado y pulsamos Enter/
˓→Intro
y esperará hasta que se le de un número o cualquier otro carácter (por ejemplo, le damos 22). Es
muy importante saber que el valor que se asigna a la variable, en este caso entrada_numero, es
siempre un string, aunque se introduzca un número. Por ejemplo, pueden comprobar que en el
ejemplo anterior el valor de la variable entrada_numero es “22”, con comillas, o sea un string. Si
queremos operar aritméticamente con el resultado debemos pasarlo a int o a float, según cómo lo
vayamos a usar, por ejemplo:
entrada_numero = float(entrada_numero)
Ahora, la variable entrada_numero es del tipo float y vale 22.0. Con la ayuda de esta función
podemos crear programas que pidan uno o varios números (u otro tipo de dato) al usuario y hacer
cálculos tan complejos como queramos con ellos, sin tener que cambiar los valores de entrada en
la correspondiente línea del programa cada vez que se utilice. Por ejemplo, podríamos modificar
el programa anterior de manera que pida al usuario el radio de la esfera cuando se ejecute el
programa:
#!/usr/bin/python
#-*- coding: utf-8 -*-
# este programa calcula el volumen, la superficie y la longitud
# del círculo máximo de una esfera de radio r que solicita por pantalla
# teo
# marzo 2012
# imprimimos el resultado
print('La longitud del circulo maximo es: %.3f' % longitud)
print('La superficie de la esfera es: %.3f' % superficie)
print('El volumen de la esfera es: %.3f' % volumen)
De esta forma, si queremos reutilizar el programa para el cálculo en el caso de otra esfera de radio
diferente no tenemos que cambiar nada del mismo y podemos volver a ejecutarlo tal cual está sin
problemas, introduciendo el radio que queramos cada vez que la usemos.
También podemos usar a función eval() para introducir valores de una variable. Esta función
evalúa el tipo de dato que introducimos:
Ya hemos visto como las funciones ayudan a hacer los programas más legibles y cortos al evitar que
tengamos que escribir una y otra vez los mismos algoritmos en un mismo programa. Sin embargo
a medida que vayamos aprendiendo a programar iremos haciendo más y más programas. Es muy
posible que en varios de estos programas usemos las mismas funciones con lo que acabaremos
teniendo que escribir la misma función en cada programa. Para evitar esta cuestión lo mejor es
definir nuestro propio módulo que incluya las funciones que vamos definiendo.
Un módulo no es más que una colección de funciones que podemos utilizar desde cualquier pro-
grama. Ya hemos visto que la distribución estándar de python ofrece un buen número de módulos
predefinidos que se agrupan normalmente por ámbitos de aplicación: funciones matemáticas en
math; las de numeros aleatorios en random; las que tratan con cadenas en string, etc... Así pues,
¿para qué vamos a re-escribir continuamente en nuestros programas funciones que nosotros ya
hemos escrito antes? Pues para evitarlo, creamos un módulo.
Para ello, creamos un fichero de texto que llamamos por ejemplo: funciones.py y vamos a incluir
en él todas las funciones que vayamos creando. El sufijo o extensión py significa que lo que habrá
dentro del fichero va a ser código python. Incluyamos en él las funciones que hayamos definido
en este capítulo:
def cubo(x):
y = x**3
return y
def saludo(nombre):
print("Buenas tardes, %s" % nombre)
def esfera(r):
pir=pi*r
longitud = 2.*pir
superficie = 4.*pir*r
volumen = superficie*r/3.
return [longitud, superficie, volumen]
Listo. Ya podemos utilizar o importar el módulo en cualquier programa sin más que hacer:
Cuando se importa o ejecuta por vez primera funciones.py verás que python crea automáticamen-
te un fichero llamado funciones.pyc; este fichero contiene una versión del módulo más fácil de
cargar en memoria que el original, pero ininteligible para nosotros ya que está codificado en “có-
digo binario”. De esta forma python acelera la carga del módulo en sucesivas ocasiones sin que
tengamos que preocuparnos nosotros de nada. Si se borra el .pyc no afectará al programa original
y simplemente se volverá a crear cuando se importe el módulo otra vez. Igualmente, si se modi-
fica de alguna manera el programa el funciones.py, se vuelve a crear una versión actualizada de
funciones.pyc. Ahora podemos probar nuestro módulo de funciones personales:
>>> saludo("Teo")
>>> Buenas tardes, Teo
Se trata de una manera muy práctica de tener a mano las funciones más usadas en nuestros cálculos
sin tener que copiar el código de un programa a otro.
Al hacer un programa es siempre conveniente guardar una cierta estructura que ayudará a enten-
derlo y corregirlo. No hay reglas definidas para ello pero una buena práctica podría ser la siguiente:
1. Primero poner los comentarios con el tipo de codificación, objeto del programa, nombre del
programador y fecha de la última modificación.
2. Después incluir las sentencias que importan los módulos que nos van a hacer falta para el
desarrollo del algoritmo solución del problema.
3. Después incluimos la definición de las funciones y procedimientos que vayamos a utilizar.
4. A continuación vendría el grueso del algoritmo o programa principal.
5. Finalmente, incluimos las órdenes de salida o de escritura de la solución del problema plan-
teado.
Además de una correcta y ordenada estructura general del programa, es conveniente mantener cier-
tas buenas prácticas de codificación. Estas normas no son obligatorias, como la propia sintaxis
del lenguaje, pero conviene seguir las recomendaciones de los desarrolladores de Python. Un ejem-
plo básico para entender a lo que nos referimos es el sangrado, que como hemos visto en Python en
obligatorio, pero mientras la estructura de bloques sea correcta, a Python no le importa el número
de espacios que se usen. Pues bien, aunque a Python le da igual, la recomendación es usar cuatro
espacios (no tabuladores) para la sangrar bloques. Hay otras normas similares muy sencillas que
debemos intentar seguir, como estas:
Cuando sea posible, define variables con nombres que tengan algún sentido o que puedas identificar
fácilmente, no importa que sean más largas. Por ejemplo, en un programa podríamos escribir:
a = 10. # altura
b = 3.5 # base
print("El volumen es %.1f" % (a*b))
pero, ¿qué significan a y b? lo sabemos por el comentario (bien hecho), pero si más adelante nos
encontramos con esas variables, tendremos que recordar cual es cual. Es mejor usar nombres con
significado:
altura = 10.
base = 3.5
print("El volumen es %.1f" % (altura*base))
De hecho podemos usar el nombre para dar más información sobre la variable:
velocidad_metros_segundo = 12.5
angulo_radianes = 1.3
Las líneas de código no deben ser muy largas, como mucho 72 caracteres. Si se tiene una línea
larga, se puede cortar con una barra invertida (\) y continuar en la siguiente línea:
Justo después de coma, punto y coma y punto, separar con un espacio, para mayor claridad, pero
no antes:
Sí: print x, y; x, y = y, x
No: print x , y ; x , y = y , x
4.6 Ejercicios
1. Escribir una función que calcule la distancia cartesiana entre dos puntos cualesquiera de
coordenadas (𝑥1 , 𝑦1 ) y (𝑥2 , 𝑦2 ).
2. Escribir un programa que calcule la densidad media de cualquier planeta, pidiendo como
entrada su masa y su radio medio.
3. La variación de temperatura de un cuerpo a temperatura inicial 𝑇0 en un ambiente a 𝑇𝑠
cambia de la siguiente manera:
𝑇 = 𝑇𝑠 + (𝑇0 − 𝑇𝑠 ) 𝑒−𝑘𝑡
con 𝑡 en horas y siendo k un parámetro que depende del cuerpo (usemos k=0.45). Una lata
de refresco a 5ºC queda en la guantera del coche a 40ºC. ¿Qué temperatura tendrá 1, 5,
12 y 14 horas? Encuentren las horas que tendríamos que esperar para que el cuerpo esté a
0.5ºC menos que la temperatura ambiente. Definir funciones adecuadas para realizar ambos
cálculos para cualquier tiempo y cualquier diferencia de temperatura respecto al ambiente
respectivamente.
4. Crear una función dentro de un programa que calcule el resto (o residuo) de una división.
Probarlo haciendo un programa que pida dos números al usuario para dividir y devuelva el
resto usando esta función.
5. Para el cálculo de la letra del DNI se calcula el residuo 23 del número, es decir, el resto que
se obtiene de la división entera del número del DNI entre 23. El resultado será siempre un
valor entre 0 y 22 y cada uno de ellos tiene asignado una letra según la siguiente tabla:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
T R W A G M Y F P D X B N J Z S Q V H L C K E
Escribir un programa que solicite el número de DNI al usuario y calcule la letra que le
corresponde.
6. Escribir su propio módulo o librería que contenga todas las funciones que haya hecho para
resolver los problemas anteriores. Escribir un programa principal en el que las utilice y de
esta forma comprueba si funciona correctamente.
4.6. Ejercicios 45
Curso de Computación Científica, Publicación 2016-09-15
Control de flujo
Hemos visto que un programa o script consiste en una serie de sentencias que se ejecutan una
detrás de otra siguiendo así el flujo natural de ejecución. No obstante, puede convenirnos cambiar
esta secuencia de ejecución, de manera que nuestro programa haga una acción u otra según ciertas
condiciones. Para ello se utilizan las sentencias de control de flujo, que nos permiten interactuar
con el programa, tomar decisiones, saltar hacia adelante o hacia atrás y ejecutar sentencias en un
orden diferente al habitual. Son un elemento básico presente en cualquier lenguaje programación
y en este capítulo veremos las más importantes.
Realiza una misma acción o serie de acciones o sentencias varias veces. Una manera común de
usar el for es para recorrer con una variable cada uno de los elementos de una lista. Esta sería
una manera básica de usarla en un programa de Python:
"""Resultado:
La isla de La Gomera
La isla de Maui
La isla de Cuba
La isla de Sri Lanka
"""
En este caso isla es una variable muda (no definida previamente) que va tomando el valor de
cada uno de los elementos de la lista que queramos de forma sucesiva; el bucle finaliza cuando se
terminen los elementos de la lista. Hay que darse cuenta de la importancia del sangrado del bucle
ya que Python reconoce que el bucle termina cuando el sangrado cambia.
Naturalmente, también es posible hacerlo definiendo antes la lista en una variable:
47
Curso de Computación Científica, Publicación 2016-09-15
Aquí es especialmente útil la función range() que, como ya vimos, crea automáticamente una
lista de números enteros; con ella, podemos usar un bucle for para hacer cálculos con los elementos
de una lista de números; por ejemplo:
for i in range(2,12,2):
print("El cubo de %d es %d" % (i, i**3) )
"""Resultado
El cubo de 2 es 8
El cubo de 4 es 64
El cubo de 6 es 216
El cubo de 8 es 512
El cubo de 10 es 1000
"""
El mismo resultado al que llegamos al principio se puede llegar utilizando el bucle for de otra
forma. Consiste en definir una variable índice para escoger o recorrer los elementos de la
lista; en este caso, la variable índice recorrerá otra lista (con idéntico número de elementos de la
lista que se quiere recorrer) que contiene el valor de los índices; la podemos crear usando len()
dentro de range(). Veamos un ejemplo:
for j in range(len(islas)):
print("%d- La isla de %s" % (j+1, islas[j]))
"""Resultado
1- La isla de La Gomera
2- La isla de Maui
3- La isla de Cuba
4- La isla de Sri Lanka
"""
Aquí, con la función range(len(islas)) hemos creado una lista nueva de tantos elemen-
tos (números enteros en este caso) como elementos tiene la lista islas, es decir, desde 0 hasta
len(islas)-1 (la longitud de la lista); después hacemos recorrer al índice j esta lista e iden-
tificamos los elementos de la otra lista con el índice apropiado islas[j]. Esta forma de utilizar
el bucle for nos permite manejar elementos de otras listas que tengan la misma longitud.
Como trabajar de esta manera con elementos de una lista y sus índices al mismo tiempo es muy
común, Python tiene la función enumerate(), que devuelve un elemento y su índice en un
bucle, sin tener que usar range(). Se usa de la siguiente manera:
como se ve el resultado es el mismo sin tener que preocuparnos por el índice. Puesto que
enumerate() devuelve en cada ciclo dos elementos, índice y valor, hemos tenido que usar dos
variables, j e isla en lugar de una.
Nota: Además de la función range(), python también tiene un función llamada xrange() que
es prácticamente igual, pero usada en un bucle for, va creando los elementos a medida que los va
necesitando, en lugar de crearlos todos de una vez y guardarlos en memoria ( es decir, no poduce
una lista). Por eso, xrange() es mucho más rápido y eficiente que range(), especialmente
cuando trabajamos con listas de muchos números.
Los bucles for nos permiten crear nuevas listas con resultados de cálculos fácilmente, por ejem-
plo:
c = [log10(x) for x in b]
print(c)
# Resultado:
# [0.36172783601759284, 0.66275783168157409, 0.87506126339170009, 1.0]
Al hacer esto hemos creado una nueva lista que contiene el logaritmo en base 10 de cada uno de
números en la lista b. Con respecto a la función logaritmo, generalmente se denota el de base 10
como acabamos de ver, mientras que la función log() calcula el logaritmo natural o de base
e. Con la función log() también se puede calcular el logaritmo de cualquier base, indicándola
como un segundo parámetro; por ejemplo, para calcular el logaritmo en base 3 de 5 haríamos
log(5,3).
Otra aplicación muy interesante del bucle ∑︀
for es la suma de series de números. Supongamos que
queremos calcular el sumatorio siguiente 10 1
𝑛=1 𝑛2 . Para ello debemos definir una variable en la
que vamos acumulando los términos del sumatorio. Abrimos un fichero para escribir el programa
que realiza este cálculo:
for n in range(1,11):
term = 1/n**2.0 # calculamos el término del sumatorio
suma = term + suma # acumulamos el término en la variable suma
print("%3d %10.6f %10.6f" % (n, term, suma)) # imprimimos el
˓→resultado
En este ejemplo definimos una variable suma con valor inicial cero para ir acumulando en ella cada
uno de los términos de la suma, que metemos en la variable term y sumándola en cada ciclo del
bucle hasta que termina. El valor final de la suma será el que tenga la variable suma al terminar el
bucle.
Si no conocemos el número de veces que hay que ejecutar las operaciones (es decir, el número de
términos de la serie a sumar) existe otra forma de definir los bucles utilizando la sentencia while.
En este bucle se ejecutan una o varias operaciones mientras cierta condición que definimos sea
cierta. Por ejemplo para imprimir los números naturales del 0 al 5 podemos hacer (los números
naturales pueden definirse considerando el cero o no según el área de la ciencia):
cuentas = 0
0
1
2
3
4
5
En este ejemplo se define inicialmente un valor 0 para la variable cuentas y su valor se va re-
definiendo, aumentado en 1 e imprimiéndolo. Mientras cuentas sea menor que 6 las sentencias
dentro del bucle while seguirán ejecutándose y se detendrá cuando la condición deje de cumplirse,
es decir cuando cuentas valga 6, algo que podemos comprobar después del bucle sin más que
escribir:
print(cuentas)
# Imprime 6
Nótese que, a diferencia del bucle for, en este caso debemos incrementar explícitamente el valor
de la variable que interviene en la condición, de no haber aumentado el valor de cuentas en cada
ciclo del bucle, su valor nunca cambiaría y tendríamos bucle infinito. Si esto nos ocurre podemos
parar el programa con las teclas Ctrl+C (pueda tardar un poco en responder).
Veamos otro ejemplo. Supongamos que tenemos unos ahorros en el banco (unos míseros 100
euros) y queremos saber el tiempo que nos llevará tener cierta cantidad (p.e. 500 euros) gracias
a los intereses que producen. Lo que hacemos es crear un bucle while en el que añadiremos
anualmente los intereses y vamos contando los años. Cuando lleguemos a la cantidad deseada el
bucle se detendrá y tendremos los años que nos llevaría; el código del programa podría ser este:
Podemos utilizar el bucle while con una condición negativa, es decir de que no se cumpla,
usando while not de la forma siguiente:
x = 0
while not x == 5:
x = x + 1
print("x = %d" % x)
Sin embargo, hay que tener cuidado cuando se comparan, mediante una igualdad exacta, números
decimales o de coma flotante floats entre sí. Debido a la precisión finita de los ordenadores, es
posible que una determinada igualdad nunca se cumpla exactamente y por lo tanto la ejecución
del bucle nunca se detendrá. Comprobemos este aspecto con un ejemplo en el que imprimimos los
números que van de 0.0 a 1.0 a intervalos de 0.1,:
x = 0.0
# Mientras x no sea exactamente 1.0, suma 0.1 a la variable *x*
while not x == 1.0:
x = x + 0.1
print("x = %19.17f" % x)
x = 1.09999999999999987
x = 1.19999999999999996
x = 1.30000000000000004
.
.
. <-- Presionar Ctrl+C para detener el programa
"""
y así sucesivamente; el bucle no se para nunca. El código anterior produce un bucle que no se pa-
ra nunca porque la condición x == 1.0 nunca se da exactamente debido a la precisión limitada
de los ordenadores (el valor más cercano es 0.99999999999999989 pero no 1.0). La conclusión que
podemos extraer de aquí es que es preferible no comparar nunca variables o números de tipo
float exactamente. Una opción para resolver el problema anterior es usar intervalos de precisión,
por ejemplo:
x = 0.0
while abs(x - 1.0) > 1e-8:
x = x + 0.1
print("x = %19.17f" % x)
de esta forma cuando x se acerque a 0.1 con una precisión igual o inferior a 1 × 10−8 se detendrá
el bucle.
Este conjunto de sentencias realizan una o varias operaciones si una determinada condición que
imponemos es cierta. De no cumplirse tal condición, se pueden realizar opcionalmente otras ope-
raciones o detener el proceso de cálculo. Un ejemplo de su utilización es el siguiente:
c = 12
En el ejemplo anterior, primero se define una variable c con valor entero 12 y luego se emplea la
sentencia if para comprobar si c es positiva, luego se usa elif para que en caso de no cumplirse
el if anterior, compruebe si c es negativa. Si no se cumple ninguna de las condiciones anteriores
y sólo en ese caso, se ejecutan las sentencias que vienen después de else. Hay que notar que en
caso de cumplirse la primera condición if, el bucle se interrumpe y el intérprete ya no continúa
comprobando las posibles condiciones elif (pueden haber varias) o else final.
Advertencia: Es muy importante darse cuenta nuevamente los bloques de sangrado o espa-
cios que separan los condicionales if-else en el ejemplo anterior. Al ejecutarse el código
y en contrarse el primer if, el intérprete de Python sabe que todo lo que viene después de
los “:” y sangrado con espacios es lo que debe ejecutarse en caso de cumplirse la condición
y esto termina cuando se encuentra con un bloque de sangrado inferior, que en este caso es la
sentencia elif.
Veamos lo anterior con un ejemplo usando sólo la sentencia if. Tenemos dos números diferentes y
sólo queremos calcular y escribir su diferencia si el primero es mayor que el segundo; el programa
podría ser:
a, b = 9.3, 12.0
if a > b:
c = a - b
print ("La variable a es mayor que b")
print ("El valor de c es %f" % c)
En este ejemplo se comprueba si a es mayor que b y en ese caso se calcula su diferencia e imprime
un mensaje. Luego se imprime el valor de c en otra sentencia, pero como esa línea está ya fuera
del bloque de identación del if, el condicional termina justo antes y esa sentencia se intentará
imprimir aunque la condición del if no se cumpla, ya que no está contenido en ella. El código
correcto sería simplemente:
a, b = 9.3, 12.0
if a > b:
c = a - b
print ("La variable a es mayor que b")
print ("El valor de c es %f" % c)
Evidentemente, los elementos de control de flujo pueden contener a su vez otros elementos de
flujo; por ejemplo, después de un if puede venir otra vez otro if con otras condiciones. También
es posible reproducir el comportamiento de un while combinando if y for, etc., pero como
siempre, hay que ser muy cuidadoso con los bloques de sangrado.
La sentencia break se puede usar para interrumpir el bloque más cercano en un bucle while
o for. De manera similar, continue continua con la siguiente iteración dentro del bucle más
próximo. La sentencia else en bucles permite ejecutar una acción cuando el bucle termina una
lista (en bucles for) o cuando la condición es falsa (con while) pero no si el bucle se interrumpe
usando break. Veamos el ejemplo de un programa que calcula los números primos entre 2 y 10:
""" Imprime:
2 es numero primo.
3 es numero primo.
4 es igual a 2*2.
5 es numero primo.
6 es igual a 2*3.
7 es numero primo.
8 es igual a 2*4.
9 es igual a 3*3.
"""
En este ejemplo usamos break para cortar el bucle for más interno si el if se cumple (es True)
y así evitar que muestre multiplicaciones equivalentes (e.g.: 3*4 = 4*3); podemos comprobar
lo que ocurre si no pusiésemos break. Es útil para por ejemplo, evitar que un bucle siga corriendo
si ya se consiguió la condición.
Algo muy interesante en este ejemplo es que usamos la sentencia else con for, en lugar del
usarla con if como es habitual. Un else en for se ejecuta cuando la lista en el for llega al
final, sin cortarse. De esta manera imprimimos un aviso si el bucle termina (el for agota la lista)
sin llegar a usar break, lo que indica que ningún número de la lista es múltiplo suyo y por tanto
es primo.
La declaración continue indica continuar con el siguiente elemento del bucle más interior, inte-
rrumpiendo el ciclo actual. Vámoslo con un ejemplo:
for k in range(8):
if k> 4:
print("%d es mayor que 4." % k)
continue
print("%d es menor o igual que 4." % k)
Es este caso, con continue evitamos que se ejecute el último print() si k>4, continuando
el bucle con el siguiente elemento de la lista. Fíjate que pudimos haber hecho un código similar
usando una sentencia if-else.
Hemos visto que cuando existe algún error en el código, Python detiene la ejecución y nos devuelve
una excepción o mensaje de error indicándonos qué fue lo que ocurrió. Por ejemplo, supongamos
que por algún motivo hacemos una división por cero:
In [4]: a, b = 23, 0
In [5]: a/b
-----------------------------------------------------------------------
˓→----
al hacer esto, nos avisa del error indicando el tipo en la última línea, ZeroDivisionError,
terminando la ejecución. Que Python nos dé tanta información al ocurrir una excepción es muy
útil pero muy a menudo sabemos que estos errores pueden ocurrir y lo ideal es estar preparado
capturando la excepción y actuar en consecuencia en lugar de interrumpir el programa. Para hacer
esto podemos usar la sentencia try-except, que nos permite “probar” (Try) una sentencia y
capturar un eventual error y hacer algo al respecto (except) en lugar de detener el programa. En el
caso anterior podríamos hacer lo siguiente:
a, b = 23, 0
try:
a/b
except:
print("Hay un error en los valores de entrada")
Ahora el código intenta ejecutar a/b y de haber algún tipo de error imprime el mensaje indicado
y sigue adelante en lugar abortar la ejecución del programa. Al hacer esto hemos “capturado” la
excepción o error evitando que el programa se detenga, suponiendo que éste puede continuar a
pesar del error. Nótese que de esta manera no sabemos qué tipo de error ha ocurrido, que antes se
indicaba con la clave ZeroDivisionError, que es uno de los muchos tipos de errores que Pyt-
hon reconoce. Si quisiésemos saber exactamente qué tipo de error ocurrió, debemos especificarlo
en except, por ejemplo:
a, b, c = 23, 0, "A"
try:
a/b
except ZeroDivisionError:
print("Error, division por cero.")
except TypeError:
print("Error en el tipo de dato.")
# Resultado:
# Error, division por cero.
try:
a/c
except ZeroDivisionError:
print("Error, division por cero.")
except TypeError:
print("Error en el tipo de dato.")
# Resultado:
# Error en el tipo de dato.
De esta manera, sabemos exactamente qué tipo de error se cometió en cada caso, una división por
cero o un error en el tipo de dato (que es lo que indica TypeError). Naturalmente, si no ocurriese
ninguno de estos errores específicamente, Python daría un error y terminaría el programa de manera
habitual. Pueden consultar la documentación oficial de Python para tener más información sobre
la captura de excepciones y los tipos de errores reconocidos.
A menudo en ciencia nos encontramos con que los modelos con los que intentamos describir la
realidad de un fenómeno natural nos lleva a tener que resolver ecuaciones polinómicas o tras-
cendentes (que no tienen solución algebraica) u otras en las que, por su complejidad, la solución
es complicada. La búsqueda de las raices de tales ecuaciones en un intervalo prefijado podemos
hacerlo con el cálculo numérico de forma sencilla utilizando lo que sabemos hasta ahora.
El método más sencillo es el llamado método del bisector que nos permite encontrar una raíz de
una función real cualquiera 𝑓 (𝑥), continua en el intervalo [𝑎, 𝑏] donde 𝑓 (𝑎) y 𝑓 (𝑏) tienen diferente
signo. Recuerden que el Teorema de Bolzano nos dice que esto es posible y lo que hay que hacer
es implementar un programa en Python para resolverlo en el caso de funciones de una sola variable
real.
El método consiste en dividir el intervalo dado [𝑎, 𝑏] por la mitad y llamemos 𝑚 al punto medio. Si
el signo de 𝑓 (𝑚) es diferente al de 𝑓 (𝑎) aplicamos otra vez el método al intervalo [𝑎, 𝑚]; si, por el
contrario, el signo de f(m) es diferente al de f(b) aplicamos otra vez el método al intervalo [𝑚, 𝑏].
El nuevo intervalo en cualquier caso es menor que el anterior y contiene la raiz buscada, por lo
tanto lo único que tenemos que hacer es repetir el método anterior tantas veces como sea necesario
hasta que 𝑓 (𝑚) = 0 o, como ya sabemos, es mejor utilizar la condición 𝑓 (𝑚) < 𝜖, donde 𝜖 es
un número tan pequeño como queramos. Este es un método iterativo, es decir, que se repite tantas
veces como sea necesario hasta que se cumpla la condición que impongamos a la solución.
5.7 Ejercicios
2. Crear una lista con 10 números enteros con valores distintos y arbitrarios de 0 a 100. Progra-
mar una función que encuentre el mayor de ellos y que dé su posición en la lista.
3. Utilizando la lista anterior, crear una lista nueva que incluya los números que son primos y
otra que incluya sus índices en la lista original.
4. Generar una lista con 100 números enteros aleatorios de -100 a 100, utilizando la fun-
ción randint() (escribir from numpy.random import randint y luego nums
= randint(-100,100,100). Separar en tres listas distintas los números negativos, los
positivos y los mayores de 50, pero de manera que la suma de los números de cada lista no
sea mayor que 200; es decir, vayan rellenando las listas mientras se cumpla la condición.
6. Modificar el programa anterior para obtener el valor de 𝜋 con una precisión determinada (por
ejemplo 10−6 ) comparado con el valor más aproximado tomado del módulo math.
7. Diseñar un programa que calcule volumen de una esfera, cilindro o un cono. El programa
debe preguntar primero qué es lo que se desea calcular y luego pedir los datos necesarios
según lo que se elija.
8. Dada la lista de notas de los alumnos de una clase, decir quien ha obtenido aprobado (entre
5 y 6.9), notable (entre 7 y 8.9), sobresaliente (más de 9) o ha suspendido.
Alumnado Nota
Carolina 7.1
Enar 4.4
Patricia 9.6
Matias 5.0
Ruyman 6.7
Maria 8.3
Nayra 5.6
Suponiendo que todos los nombres de mujer terminan con “a” (lo que casualmente en este
ejemplo es cierto), decir del alumnado quien es varón o mujer.
9. Escribir un programa que calcule la suma de los elementos necesarios de la serie siguiente:
𝑛
∑︁ 1 3
= ,
𝑖=0
(𝑖 + 1)(𝑖 + 3) 4
para obtenerla con 5 cifras significativas. Como resultado dar el valor de la suma y el número
n de sumandos sumados.
10. La nota final de la asignatura de Computación Científica (p) se calcula añadiendo a la nota
del examen final (z) una ponderación de la evaluación contínua (c) a lo largo del curso de la
forma:
(10 − 0.6𝑐)
𝑝 = 0.6𝑐 + 𝑧
10
El alumno estará aprobado cuando la nota final p sea mayor o igual a cinco, siempre que
z supere un tercio de la nota máxima (z>10/3); en caso contrario, se queda con p=z. Un
grupo de alumnos ha obtenido las siguientes calificaciones en la evaluación contínua y en el
examen final:
c 8.2 0.0 9.0 5.0 8.4 7.2 5.0 9.2 4.9 7.9
z 7.1 5.1 8.8 3.1 4.6 2.0 4.1 7.4 4.4 8.8
Hacer un programa que calcule sus notas finales indicando además quién ha aprobado y
quién ha suspendido. Calcular también la nota media de la evaluación continua, del examen
final y de la nota final. En todos los resultados debe mostrarse una única cifra decimal.
11. Dada la serie geométrica cuya suma exacta es:
∞
∑︁ 𝑎
𝑎𝑥𝑛 =
𝑛=0
1−𝑥
válida siempre que 0 < x < 1 y siendo a un número real cualquiera, escribir un programa
que calcule esta suma con diez cifras significativas solamente para cualquier valor de x y a,
comprobando que se cumple la condición necesaria para x. Dar como resultado el valor de
la suma y el número de sumandos sumados para obtenerla.
12. Se llama sucesión de Fibonacci a la colección de n números para la que el primer elemento
es cero, el segundo 1 y el resto es la suma de los dos anteriores. Por ejemplo, la sucesión
para n=5 es (0, 1, 1, 2, 3). Crear un programa que calcule la lista de números para cualquier
n.
13. Escribir un programa que proporcione el desglose en el número mínimo de billetes y mone-
das de una cantidad entera cualquiera de euros dada. Recuerden que los billetes y monedas
de uso legal disponibles hasta 1 euro son de: 500, 200, 100, 50, 20, 10, 5, 2 y 1 euros. Para
ello deben solicitar al usuario un número entero, debiendo comprobar que así se lo ofrece y
desglosar tal cantidad en el número mínimo de billetes y monedas que el programa escribirá
finalmente en pantalla.
14. Escribir un programa que pida el valor de dos números enteros n y m cualesquiera y calcule
el sumatorio de todos los números pares comprendidos entre ellos (incluyéndolos en el caso
de que sean pares). Comprobar que efectivamente los números proporcionados son enteros.
15. Crear un programa que resuelva la ecuación de segundo grado 𝑎𝑥2 +𝑏𝑥+𝑐 = 0 para cualquier
valor de a, b y c comprobando el valor del discriminante ∆ = 𝑏2 − 4𝑎𝑐.
16. Una de las formas de medir distancias en el cosmos es utilizar el llamado módulo de dis-
tancias, en el que la diferencia entre las magnitudes aparente 𝑚𝑣 (proporcional al logaritmo
del flujo recibido) y absoluta 𝑀𝑣 (proporcional al logaritmo del flujo(︀ recibido si el astro se
𝑑
)︀
encontrara a 10 parsec de distancia) de un astro es 𝑚𝑣 − 𝑀𝑣 = 5 log 10 + 𝑎𝑑, donde, 𝑑 es
la distancia en parsec y 𝑎 es el coeficiente de absorción interestelar. Lo aplicamos a un caso
concreto. En la Gran Nube de Magallanes, en 1987 se descubrió una supernova la SN1987A
que en su máximo de luminosidad alcanzó una magnitud de 𝑚𝑣 = 3. Sabiendo que este tipo
de supernovas tienen una magnitud absoluta de 𝑀𝑣 = -19.3 y que el coeficiente de absorción
interestelar es de 𝑎 = 0.8 × 10−4 , ¿a qué distancia se encuentra la galaxia?.
5.7. Ejercicios 61
Curso de Computación Científica, Publicación 2016-09-15
Juguemos con el azar. Si lanzamos una moneda, el resultado puede ser cara o cruz; decimos que
la probabilidad de que salga cara es de 1/2 al igual que lo es de que salga cruz. La Probabilidad
es una forma de asignar a cada resultado del experimento un valor entre cero y uno, teniendo en
cuenta que el suceso formado por todos los posibles resultados (en nuestro caso cara o cruz) tiene
una probabilidad de 1. Decimos que el resultado del lanzamiento de una moneda es una variable
aleatoria (discreta), puesto que a pesar de que sabemos que el resultado es cara o cruz, no sabemos
cuál va a ser el resultado del próximo lanzamiento. La teoría de la Probabilidad es una rama de la
matemática que trata del análisis de fenómenos aleatorios, sucesos no deterministas, resultados de
experimentos u observaciones de fenómenos naturales sujetos a “ruido” que parecen evolucionar
con el tiempo en forma aleatoria.
Desde un punto de vista matemático una “probabilidad” es una función ℘ real que actúa sobre los
elementos de un conjunto 𝐶 de tal forma que ℘(𝐶) = 1 y que para cualquier subconjunto 𝑆 ⊂ 𝐶
se tiene que 0 ≤ ℘(𝑆) ≤ 1. Además para cualquier familia 𝑆𝑖 de subconjuntos de 𝐶, disjuntos dos
a dos, se verifica que: ℘(𝑆1 ∪ 𝑆2 ∪ ... ∪ 𝑆𝑛 ) = ℘(𝑆1 ) + ℘(𝑆2 ) + ... + ℘(𝑆𝑛 ). Estas propiedades,
desde un punto de vista físico, son propiedades deseables para una función que debe describir
la probabilidad de obtener un resultado u otro, producto de un experimento u observación. Esta
función ℘ descrita hasta aquí se suele llamar también distribución de probabilidad por muchos
autores.
Sigamos jugando con el azar. Si lanzamos un dado de 6 caras, el resultado puede ser cualquiera de
ellas; decimos que la probabilidad de que salga cualquier cara es de 1/6. Por lo tanto, el resultado
del lanzamiento de un dado es un suceso aleatorio cuya probabilidad de que salga cualquier cara
es la misma. Si realizamos el experimento un número muy grande de veces (𝑛) y vemos cuantas
veces ha salido cada una de las caras obtendremos como distribución subyacente (el número de
veces que ha salido cada una de las caras) del experimento una muy parecida a la llamada dis-
tribución uniforme, en la que todas las caras (de la 1 a la 6) han salido igual número de veces
(𝑛/6). Tal distribución la definimos como un número finito de resultados conocidos con la misma
probabilidad de que suceda uno cualquiera de ellos. Por otro lado, decimos que el resultado del
lanzamiento de un dado es también una variable aleatoria.
Con Python podemos jugar a estos juegos, o más bien podemos simular la realización de este juego
utilizando un módulo que nos proporciona los llamados números aleatorios. Es decir, números
63
Curso de Computación Científica, Publicación 2016-09-15
o secuencias de números que tienen la misma probabilidad de que sucedan como resultado de
cualquier experimento o, dicho de otro modo, secuencias de resultados de un experimento o de
valores de una variable aleatoria.
Aclaremos que una secuencia de números se dice que es aleatoria cuando no se puede recono-
cer cuadros de regularidad en ella; como por ejemplo, los resultados del lanzamiento de un dado
ideal o también los dígitos del número pi. La aleatoriedad estadística no implica necesariamente
aleatoriedad “verdadera”, es decir “impredicibilidad objetiva”. La llamada pseudo-aleatoriedad es
suficiente para muchos usos y consiste en secuencias de números aleatorios que pasan suficientes
tests o pruebas de nula regularidad.
Para ello usamos el módulo random que implementa funciones generadoras de números aleatorios
para varios tipos de distribuciones de probabilidad. En nuestro caso, para una introducción al tema,
nos quedaremos con las más básicas de las que hemos expuesto algunos ejemplos antes.
Casi todas las funciones del módulo se basan en la función básica random(), que se fundamenta
en un algoritmo generador de números aleatorios (del tipo Mersenne-Twister) en coma flotante
en el intervalo [0.0, 1.0). La secuencia presenta regularidad despues de (2**19937-1) numeros
generados lo que le concede una pseudo-aleatoriedad muy cercana a la aleatoriedad objetiva; en
todo caso suficiente para nuestros usos.Para usar el módulo hay que importarlo primero:
>>> import random # importamos el módulo
>>> help(random) # obtenemos ayuda informativa sobre él
En concreto, para obtener un número aleatorio entero, entre 1 y 20 por ejemplo, utilizamos:
>>> random.randint(1,20) # obtenemos un entero aleatorio en el
˓→intervalo [1,20]
Si repetimos el comando obtendremos otro número. No obstante poco podemos hacer con un sólo
número aleatorio. En general, nos interesará simular secuencias más o menos largas de números
aleatorios. Podemos utilizar bucles (for o while) para obtener secuencias de ellos pero podemos
utilizar otras funciones más interesantes. Por ejemplo, para obtener una muestra de 10 números
aleatorios, de una población de 20 (de 0 a 19 obtenida con el conocido range), podemos hacerlo
de la forma siguiente:
>>> random.sample(range(20),10)
[1, 2, 13, 11, 12, 14, 4, 7, 16, 3]
Alternativamente podemos simular una lista de números aleatorios sin más que crear una lista y
escogerlos de ella o mezclarlos de forma aleatoria; veamos cómo:
>>> x=range(15) # obtenemos una lista ordenada con numeros del 0 al 14
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> x
[5, 7, 9, 12, 11, 6, 10, 2, 13, 14, 3, 8, 4, 1]
Obviamente si ustedes prueban estos comandos les saldrán resultados diferentes cada vez que lo
hagan ya que de esto se trata: obtener muestras de números aleatorios en los que todos ellos tienen
la misma probabilidad. Podemos comprobar que se trata de números aleatorios con una distribu-
ción uniforme sin más que hacer un histograma de la secuencia de números aleatorios creada. Un
histograma nos da la frecuencia con que hemos obtenido cada resultado de un experimento dado
y aunque aprenderemos a dibujarlo más adelante podemos entender el resultado con un ejemplo
práctico. Volvamos al juego de tirar un dado ideal; los siguientes comandos de python nos ayudarán
a ello:
Si 𝑛 es pequeño (𝑛 = 60) es muy posible que el número de veces que sale cada cara sea diferente
de 10 (distribución uniforme); podemos calcular cual es la desviación respecto a este valor para
cada cara calculando su respectivo error relativo:
No obstante, si repetimos la operación un gran número de veces (digamos 𝑛 = 6000, por ejemplo)
el resultado para cada cara se irá aproximando a 𝑛/6. Pueden comprobarlo repitiendo la serie de
comandos anterior para un número creciente de veces (𝑛) la desviación decrece al aumentar el
número de tiradas.
Los datos obtenidos por la observación y experimentación en muchos campos tales como la cien-
cia, la ingeniería, la sociología, las finanzas y varios juegos de azar presentan muy diferentes dis-
tribuciones subyacentes. No obstante, la mayoría de ellas son muy similares o se aproximan bien
a alguna de las tres, especialmente importantes, tipos de distribuciones de probabilidad que son:
Nota: Ejemplo. En una carrera de campo a través en la que participan 143 atletas, ¿de cuántas
maneras se puede configurar el podio al final de la carrera?. Está claro que aquí podemos aplicar
las variaciones de 143 atletas tomados de 3 en 3, puesto que son 3 los que caben en el podio
y además el orden en el que están importa mucho. Por lo tanto la respuesta sería: 𝑉 (143, 3) =
143!/140! = 143 × 142 × 141 = 2863146.
Variaciones con Repetición. Nótese que si está permitido repetir los elementos del conjunto a
partir del cual generamos los grupos de 𝑚 elementos y también importa el orden en que estén,
entonces estamos en un caso en que el uso de un elemento en un grupo no lo elimina para estar
en otro y por tanto hablamos de Variaciones con repetición de n elementos tomados m en m, y lo
calcularemos así:
𝑉 𝑅(𝑛, 𝑚) = 𝑛𝑚
Nota: Ejemplo. ¿Cuántas columnas diferentes debemos rellenar en una quiniela de fútbol para
asegurarnos un pleno de 14 aciertos?. Como pueden repetirse los resultados en diferentes partidos
se trata de un caso claro de variaciones con repetición de 3 elementos (1,X,2) tomados de 14 en
14, por lo que la solución será: 𝑉 𝑅(𝑛, 𝑚) = 𝑛𝑚 = 314 = 4782969.
𝑃 (𝑛) = 𝑉 (𝑛, 𝑛) = 𝑛!
Nota: Ejemplo. ¿De cuántas maneras se pueden sentar 6 amigos en sus seis asientos de una sala
de conciertos? Este es un caso claro de permutaciones de 6 elementos. Su solución es: 𝑃 (6) =
6! = 720.
esta fórmula puede interpretarse como el número de permutaciones de los 𝑛 elementos (𝑛!) divi-
dido por el número de permutaciones de los 𝑚 elementos seleccionados (𝑚!) multiplicadas por el
número de permutaciones de los restantes 𝑛 − 𝑚, ((𝑛 − 𝑚)!).
Nota: Ejemplo. ¿De cuántas formas podemos lanzar una moneda 12 veces y obtener cua-
tro cruces?. Imaginen que tenemos 12 monedas de las cuales cuatro son cruces. Además tene-
mos 12 posiciones donde colocar nuestras monedas. De cuántas formas podemos colocarlas, sa-
biendo que no distinguimos unas monedas de otras más que por el hecho de ser o no ser ca-
ras?. Tenemos 12! formas de colocarlas, pero podemos intercambiar las caras entre ellas de 4!
formas y las cruces de 8! maneras. Así que como nos dice la fórmula de las combinaciones,
12!
𝐶(12, 4) = 4!8! = 12·11·10·9
4·3·2·1
= 495.
Veámoslo con un ejemplo práctico. En un experimento lanzamos 12 veces una moneda, las pro-
babilidades de obtener 0, 1, 2, 3, 4,... 12 veces cara nos viene dada por los términos sucesivos del
desarrollo de binomio ( 12 + 21 )12 , es decir:
𝑛!
℘(𝑚, 𝑛) = 𝑝𝑚 𝑞 𝑛−𝑚 𝑚 = 0, ..., 𝑛
𝑚!(𝑛 − 𝑚)!
6.2 Ejercicios
1. Verifiquen que los datos de la tabla adjunta, en la que 𝑓 denota la frecuencia con la que un su-
ceso ocurre 𝑛 veces, forma una distribución binomial y encuentren su media y su desviación
estándar.
n 0 1 2 3 4 5
f 1 10 40 80 80 32
2. Una fábrica que produce chips de memoria para portátiles, al final de su cadena de produc-
ción se examina su calidad tomando una muestra de 5 unidades cada hora. Comprobadas
muchas muestras se encuentra que el número de ellas que contienen 0, 1, 2, 3 , 4, 5 me-
morias defectuosas son 58, 32, 7, 2, 1, 0 respectivamente. Mostrar que esta distribución es
aproximadamente binomial y deducir el porcentaje de memorias que salen defectuosas.
Ya hemos visto el potencial de cálculo que tiene un lenguaje de programación como Python cuan-
do se usan las operaciones y sentencias de programación disponibles con funciones matemáticas
estándar como las que ofrece el módulo math. Si tenemos muchos números con los que calcular,
podemos usar listas y operar entre ellas con bucles. Sin embargo, esto puede ser un trabajo labo-
rioso porque las listas de Python están pensadas para uso general y no precisamente para cálculo
numérico y por eso tienen muchas limitaciones que pronto veremos.
Afortunadamente existen módulos adicionales de Python (es decir, que no vienen con la instalación
de estándar de Python) para el cálculo numérico, que facilitan enormemente el cálculo, ofreciendo
nuevos elementos y funciones. En concreto, el módulo numpy nos ofrece un nuevo tipo de dato
llamado array (traducción del inglés: colección, formación), similar a las listas pero con propie-
dades especiales muy útiles. En este capítulo veremos cómo se trabaja con arrays y las nuevas
funcionalidades que nos ofece numpy.
Los arrays son un tipo nuevo de dato, similar a las listas, pero orientados especialmente al cálculo
numérico. En cierto modo se pueden considerar como vectores o matrices (parecidos a como se
los conoce en álgebra) y son un tipo de dato fundamental para el cálculo científico de cierto nivel
utilizando tipos de datos estructurados (conjuntos de datos).
El inconveniente principal de las listas, que es el tipo básico de dato estructurado en Python, es que
no está pensado para el cálculo matemático o numérico; veámolo con un ejemplo:
In [1]: lista = range(5) # Lista de numeros de 0 a 4
In [2]: print(lista*2)
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
In [3]: print(lista*2.5)
-----------------------------------------------------------------------
˓→----
69
Curso de Computación Científica, Publicación 2016-09-15
En el ejemplo anterior vemos cómo al multiplicar una lista por un número entero, el resultado es
concatenar la lista original tantas veces como indica el número, en lugar de multiplicar cada uno
de sus elementos por este número. Peor aún, al multiplicarlo por un número no entero da un error,
al no poder crear una fracción de una lista. Si quisiéramos hacer esto, se podría resolver iterando
cada uno de los elementos de la lista con un bucle for, por ejemplo:
aunque esta técnica es ineficiente y lenta, sobre todo cuando queremos evaluar funciones, polino-
mios o cualquier otra operación matemática que aparece en cualquier problema científico.
Cuando realmente queremos hacer cálculos con listas de números, debemos usar los arrays. El
módulo numpy nos da acceso a los arrays y a una enorme cantidad de métodos y funciones
aplicables a los mismos. Naturalmente, numpy incluye funciones matemáticas básicas similares
al módulo math, las completa con otras más elaboradas y además incluye algunas utilidades de
números aleatorios, ajuste lineal de funciones y otras muchas que iremos viendo más adelante y
que pueden comprobar usando la ayuda como ya sabemos.
In [13]: print(numeros)
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
In [15]: print(lista_ceros)
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
In [17]: print(lista_unos)
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
In [19]: print(otra_lista)
[ 0. 4.28571429 8.57142857 12.85714286 17.14285714
21.42857143 25.71428571 30. ]
Los arrays se indexan prácticamente igual que las listas y las cadenas de texto; veamos algunos
ejemplos:
[ 3. 4. 5. 6. 7.]
[ 0. 1. 2. 3.]
[ 5. 6. 7. 8. 9.]
7.
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
[ 2. 4. 6.]
Al igual que las listas, podemos ver el tamaño de un array unidimensional con len(), aunque la
manera correcta de conocer la forma de un array es usando el método shape():
In [28]: print(len(numeros))
10
In [29]: print(numeros.shape)
(10,)
Nótese que el resultado del método shape() es una tupla, en este caso con un solo elemento ya
que el array numeros es unidimensional.
Si creamos un array con arange() usando un número entero, el array que se creará será de
enteros. Es posible cambiar todo el array a otro tipo de dato (como a float) usando el método
astype():
In [31]: enteros = arange(6)
In [32]: print(enteros)
[0 1 2 3 4 5]
In [33]: type(enteros)
Out[33]: <type 'numpy.ndarray'>
In [34]: type(enteros[0])
Out[34]: <type 'numpy.int32'>
In [36]: type(decimales)
In [37]: type(decimales[0])
Out[37]: <type 'numpy.float64'> # En algunos ordenadores puede dar
˓→float32
In [38]: print(decimales)
[ 0. 1. 2. 3. 4. 5.]
Los arrays permiten hacer operaciones aritméticas básicas entre ellos en la forma que uno esperaría
que se hicieran, es decir, haciéndolo elemento a elemento; para ello siempre ambos arrays deben
tener la misma longitud; por ejemplo:
In [39]: x = array([5.6, 7.3, 7.7, 2.3, 4.2, 9.2])
In [40]: print(x+decimales)
[ 5.6 8.3 9.7 5.3 8.2 14.2]
In [41]: print(x*decimales)
[ 0. 7.3 15.4 6.9 16.8 46. ]
In [42]: print(x/decimales)
[ Inf 7.3 3.85 0.76666667 1.05 1.84]
Como podemos apreciar las operaciones se hacen elemento a elemento, por lo que ambas deben
tener la misma forma (shape()). Fíjense que en la división el resultado del primer elemento es
indefinido/infinito (Inf) debido a la división por cero.
Varios arrays se pueden unir con el método concatenate(), que también se puede usar para
añadir elementos nuevos:
In [44]: z = concatenate((x, decimales))
In [45]: print(z)
[ 5.6 7.3 7.7 2.3 4.2 9.2 0. 1. 2. 3. 4. 5. ]
In [46]: z = concatenate((x,[7]))
In [47]: print(z)
[ 5.6 7.3 7.7 2.3 4.2 9.2 7. ]
Es muy importante fijarse que los arrays o listas a unir deben darse como una tupla y de ahí los
elementos entre paréntesis como (x,[7]) o (x,[2,4,7]) o (x,array([2,4,7])).
Para añadir elementos, numpy tiene las funciones insert() y append(), que funcionan de
manera similar a sus equivalentes en listas, pero en este caso son funciones y no métodos que se
aplican a un array, si no que el array en cuestión hay que darlo como parámetro:
Como se ve, a diferencia de las listas, el primer parámetro es el array y luego el elemento que se
quiere añadir, en el caso de append() y el array, la posición y luego elemento a añadir en el caso
de append(). Si en lugar de un elemento a insertar se da una lista y otro array, añade todos los
elementos de la lista (a append() habría que dar también una lista de posiciones, como segundo
parámetro). También hay que notar que estas dos funciones no cambian el array original y por eso
en este ejemplo redefinimos z con el array ampliado.
Además de las operaciones aritméticas básicas, los arrays de numpy tienen métodos o funciones
específicas para ellas más avanzadas. Algunas de ellas son las siguientes:
Los métodos, que se operan de la forma z.sum() también pueden usarse como funciones de tipo
sum(z), etc. Consulten el manual de numpy para conocer otras propiedades y métodos de los
arrays o simplemente acudan y consulten la “ayuda” de las funciones que quieran utilizar.
Una gran utilidad de los arrays es la posibilidad de usarlos con datos booleanos (True o False) y
operar entre ellos o incluso mezclados con arrays con números. Veamos algunos ejemplos:
In [19]: A = array([True, False, True])
In [20]: B = array([False, False, True])
In [22]: A*B
Out[22]: array([False, False, True], dtype=bool)
In [30]: A*C
Out[30]: array([1, 0, 3])
In [31]: B*C
Out[31]: array([0, 0, 3])
En este ejemplo vemos cómo al multiplicar dos arrays booleanos es resultado es otro array boo-
leano con el resultado que corresponda, pero al multiplicar los arrays booleanos con arrays nu-
méricos, el resultado es un array numérico con los mismos elementos, pero con los elementos que
fueron multiplicados por False iguales a cero.
Tambíén es posible usar los arrays como índices de otro array y como índices se pueden usar
arrays numéricos o booleanos. El resultado será en este caso un array con los elementos que se
indique en el array de índices numérico o los elementos correspondientes a True en caso de usar
un array de índices booleano. Veámoslo con un ejemplo:
# Array con enteros de 0 a 9
In [37]: mi_array = arange(0,100,10)
In [40]: print(mi_array)
[ 0 10 20 30 40 50 60 70 80 90]
In [43]: print(mi_array[indices1])
[ 0 20 40 60 80]
In [44]: print(mi_array[indices2])
[10 20 50 80 90]
También es muy sencillo crear arrays booleanos usando operadores lógicos y luego usalos como
índices, por ejemplo:
# Creamos un array usando un operador booleano
In [50]: mayores50 = mi_array > 50
In [51]: print(mayores50)
[False False False False False False True True True True]
In [52]: print(mi_array[mayores50])
[60 70 80 90]
Hasta ahora sólo hemos trabajado con arrays con una sola dimensión, pero numpy permite tra-
bajar con arrays de más dimensiones. Un array de dos dimensiones podría ser por ejemplo un
array que tuviera como elementos un sistema de ecuaciones o una imagen. Para crearlos podemos
hacerlo declarándolos directamente o mediante funciones como zero() o ones() dando como
parámetro una tupla con la forma del array final que queramos; o también usando arange() y
crear un array unidimensional y luego cambiar su forma. Veamos algunos ejemplos:
# Array de 3 filas y tres columnas, creado implícitamente
In [56]: arr0 = array([[10,20,30],[9, 99, 999],[0, 2, 3]])
In [57]: print(arr0)
[[ 10 20 30]
[ 9 99 999]
[ 0 2 3]]
In [69]: arr2.shape
Out[69]: (4, 1)
Como vemos en la última línea, la forma o shape() de los arrays se sigue dando como una tupla,
con la dimensión de cada eje separado por comas; en ese caso la primera dimensión son las cuatro
filas y la segunda dimensión o eje es una columna. Es por eso que al usar las funciones zero(),
ones(), reshape(), etc. hay que asegurarse que el parámetro de entrada es una tupla con la
longitud de cada eje. Cuando usamos la función len() en un array bidimensional, el resultado es
la longitud del primer eje o dimensión, es decir, len(arr2) es 4.
El acceso a los elementos es el habitual, pero ahora hay que tener en cuenta el eje al que nos
referimos; además podemos utilizar ”:” como comodín para referirnos a todo el eje. Por ejemplo:
In [94]: print(arr0)
array([[ 88, 20, 30],
[ 50, 60, 999],
[ 0, 20, 30]])
7.7 Ejercicios
1. Crea un array de 100 números aleatorios con valores de -100 a +100 como hicimos en el
ejercicio 5 del Capítulo 5. Usando arrays de booleanos crea otros arrays que contengan los
positivos, los negativos y los mayores de 50 de ese array.
2. Resuelve el ejercicio 11 del capítulo 5 sobre el cálculo de la nota de la asignatura usando
arrays de numpy e ignorando la condición de z>10/3. ¿Podrías hacerlo también de manera
que sí lo tenga en cuenta? Para ello podrías usar arrays de booleanos.
3. Crea un array bidimensional 5x5 con todos los valores cero. Usando el indexado de arrays,
asigna 1 a todos los elementos de la última fila y 5 a todos los elementos de la primera
columna. Finalmente, asigna el valor 100 a todos los elementos del subarray central 3x3 de
la matriz de 5x5.
4. Para el ejercicio 4 del Apéndice C sobre los diámetros de las esporas del lycopodium, calcu-
lar, usando funciones de numpy, el diámetro medio de las esporas y la desviación estándar
de la muestra. Separar, en arrays independientes, las medidas de los diámetros:
Que tengan valores inferiores a la media menos la desviación estándar, es decir 𝑑 <
𝑑 − 𝜎𝑑
Que tengan valores superiores a la media más la desviación estándar, es decir 𝑑 > 𝑑+𝜎𝑑
Que tengan valores entre 𝑑 − 𝜎𝑑 < 𝑑 < 𝑑 + 𝜎𝑑
La Estadística es la rama de las Matemáticas que trata del estudio de fenómenos de los que no se
tiene toda la información completa y se diferencia del resto porque usa el concepto de Probabili-
dad, que es una medida de la posibilidad de que algo ocurra o de que algo sea correcto como ya
hemos visto.
En el estudio de la naturaleza, en su observación y medida, nos encontramos con estas situaciones
continuamente. Por ejemplo, ¿cómo describiríamos el comportamiento del Sol?; si nos fijamos
que en el Sol hay alrededor de 𝑁 ≈ 𝑀⊙ /𝑚𝐻 ≈ 1057 partículas, nos damos cuenta de que no
podemos describir el estado de movimiento de cada una de ellas en cada instante de tiempo; serán
conceptos y procedimientos de la Física Estadística los que nos ayudarán a describirlo. Por otro
lado, cualquier observación y/o medida de cualquier magnitud en un experimento de laboratorio
nunca coincide exactamente con otra realizada un instante antes o después debido al “ruido” entre
otros factores; el conjunto de medidas obtenidas forman un ejemplo de aplicación del análisis
estadístico de datos.
Para el análisis de un conjunto de datos desde el punto de vista estadístico, hay que poder compri-
mir o representar la información contenida en ellos en una serie de parámetros o indicadores que
nos den una idea de como se comporta el conjunto. Estos números se llaman parámetros esta-
dísticos y entre los más conocidos están: un parámetro de posición, la media, y otro de “amonto-
namiento”, la dispersión. Por supuesto, estos parámetros estadísticos serán tanto mejores cuanto
mejor representen a todo el conjunto de datos. Hay dos condiciones básicas para que representen
bien al conjunto: la ausencia de “sesgos” por un lado y la “robustez” por otro; al final del capítulo
nos referiremos a ellos.
Cada vez que realizamos un experimento o una observación de un fenómeno natural, estamos de
algún modo “preguntando” cual es el valor exacto o verdadero de una cierta cantidad. Esta actitud
79
Curso de Computación Científica, Publicación 2016-09-15
presupone la idea que tenemos de que tal valor existe y que además puede ser medido; no es
trivial darse cuenta de que tal cosa no es cierta. No obstante, vamos a suponer que la cantidad que
deseamos medir tiene un valor y que además podemos obtenerlo, aunque sea de modo aproximado,
a través de nuestras medidas. Cuán buena será nuestra aproximación y cuánto trabajo nos costará
obtenerlo y mejorarlo es, de hecho, el objetivo principal de la estadística aplicada a la Física.
Supongamos que hemos realizado un conjunto de 𝑁 medidas de una determinada cantidad 𝑥 y
que las representamos por {𝑥𝑖 } (𝑖 = 1, 2, ..., 𝑁 ). Esperamos que las diferentes medidas realizadas
𝑥𝑖 , se irán acumulando alrededor de un valor que esperamos que coincida con el valor real de la
cantidad medida. Tal distribución de valores que podemos representar mediante un histograma 1
que según añadamos más medidas será cada vez más suave de forma que, en el límite (un número
infinito de medidas), reproducirá una distribución de probabilidad, que llamaremos distribución
subyacente. Nuestro objetivo será aprender lo máximo posible acerca de ella a partir de nuestro
limitado conjunto de datos.
El primer parámetro que nos gustaría tener es un valor (aunque sea aproximado) al valor verda-
dero de la cantidad medida, lo que llamamos el valor medio. Comenzaremos por definir la media
aritmética (o simplemente media) como:
∑︀
𝑥𝑖
𝑥= 𝑖
𝑁
Esta definición coincide con la que habitualmente se usa en el lenguaje común para mencionar
el valor medio o promedio de una serie de cantidades. En numpy de Python podemos utilizar
mean() para calcularla.
De modo similar, se define la media geométrica utilizando la multiplicación en vez de la suma, es
decir:
𝑥 = (Π𝑖 𝑥𝑖 )(1/𝑁 ) ,
esta definición resulta útil cuando la incertidumbre en nuestro problema actúa de forma multipli-
cativa en vez de aditiva. Se usa, por ejemplo, cuando queremos promediar porcentajes o casos en
que la variable muestra valores acumulativos. De hecho, son muy pocas las veces en las que esta
medida resulta útil en la práctica. Para calcularla en python hay que usar la función gmean() del
módulo stats de scipy.
Otra medida de posición interesante es la llamada mediana. Se define como el valor de la variable
en la distribución tal que la mitad de los valores presentes en el conjunto son mayores que ella
y la otra mitad restante son menores. La mediana 𝑥𝑚𝑒𝑑 , cumple que 𝑛(𝑥 < 𝑥𝑚𝑒𝑑 ) = 𝑁/2 =
𝑛(𝑥 > 𝑥𝑚𝑒𝑑 ). Su utilidad principal, como podemos comprobar, es que es un indicador más fiable
que la media en el caso de tener algunos valores en la distribución muy alejados de la media o
1
El histograma es una representación gráfica del conjunto de medidas. Se dibuja el número de medidas (eje OY)
que caen en un cierto intervalo de variación dado frente a estos intervalos (eje OX).
que realmente son erróneos o que no pertenecen a la distribución que nos gustaría encontrar. En
numpy de Python podemos utilizar median() para calcularla.
Finalmente, otra medida de posición es la moda de la distribución que es el valor que más veces se
observa en la misma; su utilidad en ciencia es realmente escasa.
La segunda cuestión que nos interesa conocer es cómo se distribuyen las medidas del conjunto
alrededor de la media, por dos buenas razones:
1. por cuestiones de predicción; por ejemplo, en el caso de que queramos montar otro experi-
mento sabremos el rango de variación de las medidas, o
2. porque nos da una idea de la incertidumbre o del error más probable de nuestras medidas.
Aunque se pueden definir varios indicadores de distancia, o medidas de dispersión, entre cada uno
de los elementos del conjunto de medidas y el valor medio, utilizaremos el más apropiado tanto
matemáticamente como desde el punto de vista físico. Se define la varianza, el cuadrado de la
desviación estándar, como:
(𝑥𝑖 − 𝑥)2
∑︀
2
𝜎 = 𝑖
𝑁
donde tomamos como valor real de la cantidad medida su valor medio. Por cierto, pueden demos-
trar, sin demasiado problema, que también puede calcularse como 𝜎 2 = 𝑥2 − 𝑥2 ; de hecho, esta es
la definición más utilizada para su cálculo. En numpy de Python podemos utilizar las funciones
var() y std() para calcularlas.
El hecho de utilizar como valor real el valor medio de la distribución subyacente, hace que al
utilizar la 𝜎 como medida de dispersión haga que ésta sea mínima. Se puede demostrar (aunque
esta vez no es fácil quedando más allá de este curso hacerlo) que el denominador en la ecuación
anterior debe cambiar de 𝑁 a (𝑁 − 1). Nótese que a medida que 𝑁 aumenta, el error relativo al
utilizar una u otra definición decrece.
En las medidas de fenómenos naturales es muy común encontrarse con que tales medidas siguen
una distribución subyacente muy parecida a la conocida como distribución gaussiana, de tal suer-
te que suele llamársela también distribución normal (ver Apéndice C). Aunque fue obtenida pri-
mero por Demoivre en 1733 cuando estudiaba un problema de lanzamiento de monedas, fue más
tarde obtenida independientemente por Laplace y por Gauss; de ahí lo de distribución gaussiana,
y fue ampliamente aplicada inicialmente a los errores accidentales (o aleatorios) en medidas astro-
nómicas y de otros experimentos científicos. La forma de una distribución gaussiana, es decir su
histograma, es la famosa “Campana de Gauss”.
Por otro lado, es bastante usual ver a un conjunto de observaciones o resultados de cualquier ex-
perimento como una muestra finita del conjunto infinito, o mucho más grande que podríamos
obtener si repirtiéramos el experimento indefinidamente, que se conoce como población. Como
esto no es posible, lo más que obtenemos son diferentes conjuntos de medidas de un mismo expe-
rimento que serán muestras finitas. Cada una de ellas las podremos representar por su media y su
desviación estándar que, en general no coincidirán.
No obstante, puede demostrarse matemáticamente que la distribución de medias y la de desviacio-
nes estándar también es una distribución gaussiana. Su media
√ es igual a la media verdadera de toda
la población mientras que su desviación estándar es 𝜎/ 𝑛 donde 𝑛 es el número de datos de cada
muestra y 𝜎 la desviación estándar de toda la población.
Por lo tanto, cuando trabajamos con un conjunto de datos cuya distribución subyacente es una
distribución normal de media 𝑥 y desviación estándar 𝜎,√
es apropiado escribir el resultado final del
experimento u observación como: 𝑥 ± 𝜖, donde 𝜖 = 𝜎/ 𝑛, que es el llamado error estándar de
la media.
Cuando se realizan medidas repetidas de una misma cantidad es común encontrar que algunas de
ellas, por una razón u otra, son más fiables que otras. Por ejemplo, en Astronomía es corriente
utilizar observaciones realizadas por diferentes observadores, en diferentes instantes de tiempo,
en diferentes observatorios y a menudo con diferentes instrumentos; si queremos utilizarlas todas
es interesante disponer de un método que permita promediarlas, dándoles a cada una de ellas
una influencia proporcional a la medida de su fiabilidad. Ello se consigue introduciendo pesos
asociados a cada medida; es decir, que tenemos un conjunto de medidas {𝑥𝑖 } cada una de ellas con
un peso estadístico asociado, con lo que tendremos finalmente un conjunto de pesos {𝑤𝑖 }. En este
caso, para calcular la media, que se suele llamar media ponderada, hacemos:
∑︀
𝑤𝑖 𝑥𝑖
𝑥 = ∑︀𝑖 (8.1)
𝑖 𝑤𝑖
Con numpy podemos utilizar average() para calcularla y análogamente, siguiendo el apartado
anterior, podemos calcular su varianza usando:
𝑤𝑖 (𝑥𝑖 − 𝑥)2
∑︀
2
𝜎 = 𝑖 ∑︀ (8.2)
𝑖 𝑤𝑖
En el caso de medidas experimentales, una elección habitual para los pesos es utilizar valores in-
versamente proporcionales al cuadrado de los errores de cada medida, cuando éstas son conocidas
o pueden ser estimadas, es decir, 𝑤𝑖 = 1/𝜖2𝑖 . La varianza asociada a este promedio la calculamos
sustituyendo apropiadamente en la relación anterior.
En palabras llanas nos gustaría que el conjunto de nuestras medidas tuvieran las cualidades si-
guientes: que den el resultado que “queremos” y que no tuviera valores erróneos.
La primera cualidad exigiría que nuestro conjunto de medidas nos dé un valor medio que se aproxi-
me de forma asintótica al valor de la media de la distribución subyacente al hacer muchas medidas.
Un parámetro que cumple esta condición se dice que no tiene sesgos, es decir, que no contiene erro-
res sistemáticos. Ejemplos de ellos serían: límites en la calibración de instrumentos, condiciones
ambientales en el que se desarrollan los experimentos, errores humanos entre otros.
En cuanto a la segunda cualidad, se exigiría que el parámetro de posición utilizado (media, por
ejemplo) no varíe sensiblemente si quitamos una o unas pocas medidas cualesquiera del conjunto.
Si esto sucede, se dice que la media o el parámetro que sea es robusta. Pueden comprobar por
ejemplo, a partir de las definiciones, que la mediana es un parámetro de posición más robusto que
la media.
En general, obtener medidas robustas y sin sesgos es lo que estamos buscando siempre y es impo-
sible de obtener. No obstante, siempre podemos mejorar las observaciones, los instrumentos y los
experimentos de tal forma que minimicemos los sesgos posibles y dotemos de mayor robustez a la
medida final. La mejora en estos aspectos es el objetivo de cualquier científico y es lo que permite
avanzar a la ciencia.
8.4 Ejercicios
1. Comprobar que para un conjunto de datos de media 𝑥 y varianza 𝜎 2 , ésta última también a
podemos calcular como 𝜎 2 = 𝑥2 − 𝑥2 .
2. El valor medio de un conjunto de 100 observaciones es 2.96 y la desviación estándar es 0.12
. Hacemos otras 50 observaciones cuya media es de 2.93 y su desviación estándar es 0.16.
Encuentren la media y la varianza del conjunto de las 150 observaciones.
3. Comprobar de qué forma podemos calcular también la media y la varianza de una distribu-
ción de datos con sus correspondientes pesos (𝑤𝑖 = 1/𝜎𝑖2 ) en los casos en que: (a) Todos los
errores son iguales, y (b) una sola de las medidas tiene una precisión mucho mejor que las
demás.
4. Escribir un programa que calcule las medias aritmética, aritmética ponderada y geométrica
para la lista de números 34.4, 30.1, 29.8, 33.5, 30.9, 31.1; ponderada con los pesos 0.9, 0.79,
0.84, 0.6, 0.88, 0.78.
5. Crear un programa que calcule la varianza y la desviación estándar de los elementos (núme-
ros) de la misma lista del problema anterior.
6. En observaciones de dispersión de electrones, en el detector se mide la intensidad relativa
del haz a diferentes radios de curvatura obteniendo los datos de la tabla adjunta. Encontrar
Muy a menudo tenemos datos iniciales para un cálculo o medidas de un experimento en un fichero
(de texto). Para poder manipular estos datos y calcular con ellos debemos aprender a leerlos como
números o arrays. Igualmente, el resultado de un cálculo o un análisis es necesario volcarlo a un
fichero de texto en lugar de mostrarlo por pantalla para conservar el resultado. Esto es especial-
mente necesario cuando los resultados son arrays largos o cuando tenemos que procesar un gran
número de ficheros. Vamos a ver cómo leer y escribir ficheros de texto, es decir letras y números y
signos de puntuación, con Python.
El primer paso para manipular un fichero, ya sea para crearlo, leerlo o escribir en él es crear lo que
en Python se llama una instancia a ese fichero; una instancia es una llamada o referencia, en este
caso a un fichero, a la que se le asigna un nombre. Esto consiste simplemente en “abrir” el fichero
en modo de lectura, escritura, para añadir o una combinación de estos, según lo que necesitemos:
En los ejemplos anteriores se ha abierto un fichero de varias maneras posibles, donde hemos indi-
cado en el primer parámetro el nombre del fichero y el segundo el modo de apertura:
85
Curso de Computación Científica, Publicación 2016-09-15
Vamos a crear un fichero y escribir algo en él. Lo primero es abrir el fichero en modo escritura:
aquí la variable fs es una instancia o llamada al fichero. A partir de ahora cualquier operación
que se haga en el fichero se hace utilizando esta instancia fs y no el nombre del fichero en sí.
Escribamos ahora algo de contenido, para esto se emplea el método write() a la instancia del
fichero:
Al teminar de trabajar con el fichero debemos cerrarlo con el método close(), es aquí cuando
realmente se escribe el fichero y no hay que olvidar cerrarlo siempre al terminar de trabajar con él.
Una vez cerrado, se puede abrir con un editor de textos cualquiera como Kate o gEdit. Veremos
que cada orden de escritura se ha hecho consecutivamente y no línea a línea. Si queremos añadir
líneas nuevas debemos ponerlas explícitamente con \n que es el código ASCII para el “intro” o el
“return”, es decir para generar una nueva línea:
De igual manera podemos usar un bucle for para escribir una lista de datos:
De esta forma creamos un fichero de nombre datos.txt en el que hay escrito, a dos columnas, los
cien primeros números enteros positivos y su exponencial (con 10 caracteres en total y cuatro
decimales). Podemos ver el contenido de este fichero con cualquier editor de texto o desde una
consola de linux usando cat como ya sabemos.
Ahora podemos leer este fichero de datos u otro similar que ya exista. Una vez abierto el fichero
para lectura, podemos crear una lista vacía para cada columna de datos y luego con un bucle
leerlo línea por línea separando cada una en columnas con el método split(). Veámoslo con
un ejemplo: queremos leer el fichero datos.txt que acabamos de crear antes. Contiene 100 filas
con dos columnas, la primera un número y la segunda su exponencial. Lo podríamos hacer de esta
forma:
....:
....:
In [75]: fdatos.close()
En este ejemplos usamos el método readlines() al fichero para que lea línea a linea y devuel-
ve una lista donde cada elemento es una cadena de texto con una línea, por lo que si hacemos
len(lineas) para ver la longitud de la lista nos da el número de líneas del fichero. En realidad
este paso nos lo podemos ahorrar ya que python es capaz de iterar línea a línea una instancia de
fichero, es decir, podemos poner:
en lugar de:
In [77]: type(x_datos)
Out[77]: <type 'list'>
In [78]: len(x_datos)
Out[78]: 100
In [80]: type(x_datos)
Out[80]: <type 'numpy.ndarray'>
In [81]: x_datos.shape
Out[81]: (100,)
Ahora que tenemos todos los datos en arrays los podemos manipular como tales. Recuerda que
split() separa, por defecto, datos en el texto separados por espacios; si queremos separar por
comas u otro carácter debemos incluirlo como parámetro: split(',').
Una manera alternativa y muchísimo más sencilla de escribir y leer arrays es usando los métodos
savetxt() y loadtxt() de numpy, que escriben (guardan) y leen ficheros de texto pero con
un formato prefijado por defecto.:
Hay que notar que si guardamos varios arrays en un fichero de esta manera, savetxt() guarda
cada uno en una fila, de manera que x_datos ocupa la primera fila del fichero y y_datos
la segunda, en lugar de en columnas y al leerlo con loadtxt(), la variable x contiene toda
la primera fila e y la segunda. No obstante, en el caso de que sólo queramos guardar un array
unidimensional, se escribe en una única columna.
Por otro lado, si leemos el fichero y cargamos los datos en un único array veamos la forma que
tiene:
d = loadtxt("datos2.txt")
d.shape()
(2, 100)
es decir, un array bidimensional de 2 x 100, que corresponde primero a las dos filas y luego 100
columnas. Esto quiere decir que si tenemos un fichero de texto con varias columnas (como es
habitual), como datos.txt que contiene:
# tiempo x1 x2
1.0 1.2 1.4
2.0 2.1 2.3
3.0 3.3 3.0
4.0 4.2 4.2
5.0 5.1 5.1
d = loadtxt("datos.txt") # d es un array de 6 x 3
t = d[0,:] # columna 1 (tiempo)
x1 = d[1,:] # columna 2 (x1)
x2 = d[2,:] # columna 1 (x2)
Lo anterior sólo es necesario si el fichero tiene varias columnas, si sólo tiene una no hace falta usar
unpack=True.
Supongamos ahora que estamos interesados en leer los datos de un fichero a partir de la línea 50 y
no desde el principio y que además las columnas están separadas por comas en lugar de espacios;
podemos indicarlo con parámetros adicionales:
Consulten la ayuda de la función loadtxt() de numpy para conocer otras opciones de lectura,
como seleccionar columnas determinadas, definir otro símbolo de comentario, entre otras opciones.
9.4 Ejercicios
1. En el fichero datos_2col.txt hay dos columnas de datos. Calcular la raíz cuadrada de los
valores de la primera columna y el cubo de la segunda y escribirlos a un nuevo fichero de
dos columnas, pero solo para las entradas cuyos valores de la segunda columna de datos
original sean mayor o igual a 0.5.
2. El fichero datos_4col.txt contiene cuatro columnas de datos. Escribir un fichero de datos con
cuatro columnas que incluya el número de entrada (empezando por 1), el promedio entre
las dos primeras columnas, el promedio entre las dos últimas y la diferencia entre ambas
medias.
9.4. Ejercicios 89
Curso de Computación Científica, Publicación 2016-09-15
La representación de funciones y/o datos científicos mediante gráficos resulta ser fundamental
para expresar una gran variedad de resultados. Cualquier informe, artículo o resultado a menudo
se expresa de manera mucho más clara mediante gráficos. Python posee varios paquetes gráficos;
nosotros usaremos matplotlib, una potente librería gráfica de alta calidad también para gráficos
bidimensionales y sencilla de manejar. Matplotlib posee el módulo pylab, que es la interfaz para
hacer gráficos bidimensionales. Veamos un ejemplo sencillo:
Hemos creado un gráfico que representa diez puntos en un array y luego lo hemos mostrado con
show(); esto es así porque normalmente solemos hacer varios cambios en la gráfica, mostrán-
dolos todos juntos. Sin embargo, cuando trabajamos interactivamente, por ejemplo con la consola
ipython podemos activar el modo interactivo para que cada cambio que se haga en la gráfica
91
Curso de Computación Científica, Publicación 2016-09-15
se muestre en el momento, mediante la función ion(), de esta manera no hace falta poner show()
para mostrar la gráfica cada vez que se haga plot():
Recuerden que este modo interactivo sólo está disponible en la consola avanzada ipython pero
no lo está en la consola estándar de Python. Otra posibilidad es iniciar ipython en modo pylab,
haciendo ipython -pylab, de esta manera se carga automáticamente pylab, se activa el mo-
do interactivo, y además se importa el módulo numpy y todas sus funciones.
Fíjense cómo el comando plot() que hemos usado hasta ahora devuelve una lista de instancias
de cada dibujo. Una instancia es una referencia a un elemento que creamos, en este caso la línea en
gráfica. En este caso es una lista con un sólo elemento, una instancia Line2D. Podemos guardar
esta instancia para referirnos a este dibujo (a la línea en concreto) más adelante haciendo:
Ahora la variable mi_dibujo es una instancia o “referencia” a la línea del dibujo, que podremos
manipular posteriormente con métodos que se aplican a esa instancia dibujo. Nótese que después
de mi_dibujo hay una coma; esto es para indicar que mi_dibujo debe tomar el valor del
primer (y en este caso el único) elemento de la lista y no la lista en sí, que es lo que habría ocurrido
de haber hecho mi_dibujo = plot(x) (erróneamente). Esto es habitual al trabajar con listas,
veámoslo con un ejemplo:
a = [3, 5]
# Así ``a`` es una lista, que contiene dos valores
a, b = [3, 5]
# Así *desempaquetamos* los elementos de la lista y a=3 y b=5
# Esto funciona porque pusimos tantas variables como elementos en la
˓→lista
Pero si la lista sólo tiene un elemento ¿cómo desempaquetamos ese elemento?. Veamos:
a, = [3] # Si añadimos una coma indicamos que queremos meter ese único
# elemento en una variable, en lugar de usar la lista
Y esto es justo lo que hicimos con mi_dibujo,= plot(x), para hacer que mi_dibujo con-
tenga una instancia y no una lista de instancias, que es lo que devuelve plot().
La sintaxis básica de plot() es simplemente plot(x,y), pero si no se incluye la lista x, ésta
se reemplaza por el número de elementos o índice de la lista y, por lo que es equivalente a hacer
plot(range(len(y)),y). En la gráfica del ejemplo anterior no se ven diez puntos, sino una
línea contínua uniendo esos puntos, que es como se dibuja por defecto. Si queremos pintar los
puntos debemos hacerlo con un parámetro adicional, por ejemplo:
In [5]: plot(x,'o-') # igual que antes pero ahora los une con una
˓→linea continua
En este caso el ‘o’ se usa para dibujar puntos gruesos y si se añade ‘-‘ también dibuja la línea
contínua. En realidad, lo que ha sucedido es que se dibujaron dos gráficos uno encima del otro;
si queremos que se cree un nuevo gráfico cada vez que hacemos plot(), debemos añadir el
parámetro hold=False a plot():
93
Curso de Computación Científica, Publicación 2016-09-15
Símbolo Color
‘b’ Azul
‘g’ Verde
‘r’ Rojo
‘c’ Cian
‘m’ Magenta
‘y’ Amarillo
‘k’ Negro
‘w’ Blanco
Marcas y líneas
Símbolo Descripción
‘-‘ Línea continua
‘–‘ Línea a trazos
‘-.’ Línea a puntos y rayas
‘:’ Línea punteada
‘.’ Símbolo punto
‘,’ Símbolo pixel
‘o’ Símbolo círculo relleno
‘v’ Símbolo triángulo hacia abajo
‘^’ Símbolo triángulo hacia arriba
‘<’ Símbolo triángulo hacia la izquierda
‘>’ Símbolo triángulo hacia la derecha
‘s’ Símbolo cuadrado
‘p’ Símbolo pentágono
‘*’ Símbolo estrella
‘+’ Símbolo cruz
‘x’ Símbolo X
‘D’ Símbolo diamante
‘d’ Símbolo diamante delgado
Para borrar toda la figura se puede usar la función clf(), mientras que cla() sólo borra lo que
hay dibujado dentro de los ejes y no los ejes en si.
Se pueden representar varias parejas de datos con sus respectivos símbolos en una misma figura,
aunque para ello siempre es obligatorio incluir el valor del eje x:
In [8]: clf() # Limpiamos toda la figura
Esta lista de salida de plot() contiene 3 instancias que se refieren a 3 elementos diferentes de la
gráfica.
Además del marcador y el color indicado de la manera anterior, se pueden cambiar muchas otras
propiedades de la gráfica como parámetros de plot() independientes como los de la tabla adjun-
ta:
95
Curso de Computación Científica, Publicación 2016-09-15
También es posible cambiar las propiedades de la gráfica una vez creada, para ello debemos captu-
rar las instancias de cada dibujo en una variable y cambiar sus parámetros. En este caso a menudo
hay que usar draw() para actualizar el gráfico,
usando instancias similares poder cambiar prácticamente todas las propiedades de nuestro gráfico
sin tener que rehacerlo. Por tanto es buena costumbre guardar las instancias en variables cuando
trabajemos interactivamente.
Exiten funciones para añadir texto (etiquetas) a los ejes de la gráfica y a la gráfica en sí; éstos son
los más importantes:
In [10]: p, = plot(x,log10(x)*sin(x**2))
En este ejemplo, se usó la función text() para añadir un texto arbitrario en la gráfica, cuya
posición se debe dar en las unidades de la gráfica. Cuando se utilizan textos también es posible
usar fórmulas con formato LaTeX. Veamos un ejemplo,:
In [27]: clf() #limpio la figura
In [30]: y1 = sin(x)/x
In [31]: y2 = sin(x)*exp(-x)
Aquí hemos usado código LaTeX para escribir fórmulas matemáticas, para lo que siempre hay que
escribir entre r'$ formula $' y he usado un tamaño de letra mayor con el parámetro fontsize.
En la última línea hemos añadido una malla con la función grid().
Visto el ejemplo anterior, vemos que en Python es muy fácil representar gráficamente una fun-
ción matemática. Para ello, debemos definir la función y luego generar un array con el intervalo
de valores de la variable independiente que se quiere representar. Definamos algunas funciones
trigonométricas y luego representémoslas gráficamente:
.....:
Nótese que hemos añadido una leyenda con la función legend() que admite como entrada una
tupla con strings que corresponden consecutivamente a cada una de las curvas del gráfico.
10.3 Histogramas
Cuando tenemos un conjunto de datos numéricos, por ejemplo como consecuencia de la medida
de una cierta magnitud y queremos representarlos gráficamente para ver la distribución subyacente
de los mismos se suelen usar los gráficos llamados histogramas. Los histogramas representan el
número de veces que los valores del conjunto caen dentro de un intervalo dado, frente a los di-
ferentes intervalos en los que queramos dividir el conjunto de valores. En Python podemos hacer
histogramas muy fácilmente con la función hist() indicando como parámetro un array con los
números del conjunto. Si no se indica nada más, se generará un histograma con 10 intervalos (lla-
mados bins, en inglés) en los que se divide la diferencia entre el máximo y el mínimo valor del
conjunto. Veamos un ejemplo:
2.71408319]),
<a list of 10 Patch objects>)
Se pueden hacer cuantas figuras independientes (en ventanas distintas) queramos con la función
figure(n) donde n es el número de la figura. Cuando se crea una figura al hacer plot() se
hace automáticamente figure(1), como aparece en el título de la ventana. Podríamos crear
una nueva figura independiente escribiendo figure(2), en ese momento todos los comandos de
aplican a figura activa, la figura 2. Podemos regresar a la primera escribiendo figure(1) para
trabajar nuevamente en ella, por ejemplo:
En ocasiones nos interesa mostrar varios gráficos diferentes en una misma figura o ventana. Para
ello podemos usar la función subplot(), indicando entre paréntesis un número con tres dígitos.
El primer dígito indica el número de filas en los que se dividirá la figura, el segundo el número
de columnas y el tercero se refiere al gráfico con el que estamos trabajando en ese momento. Por
ejemplo, si quisiéramos representar las tres funciones anteriores usando tres gráficas en la misma
figura, una al lado de la otra y por lo tanto con una fila y tres columnas, haríamos lo siguiente:
>>> # Figura con una fila y tres columnas, activo primer subgráfico
>>> subplot(131)
>>> p1, = plot(x,f1(x),'r-')
>>> # Etiqueta del eje Y, que es común para todas
>>> ylabel('Amplitud / cm')
>>> title('Funcion 1')
>>> # Figura con una fila y tres columnas, activo segundo subgráfico
>>> subplot(132)
>>> p2, = plot(x,f2(x),'b-')
>>> # Etiqueta del eje X, que es común para todas
>>> xlabel('Tiempo / s')
>>> title('Funcion 2')
>>> # Figura con una fila y tres columnas, activo tercer subgráfico
>>> subplot(133)
>>> p3, = plot(x, f3(x),'g-')
>>> title('Funcion 3')
Al igual que con varias figuras, para dibujar en un gráfico hay que activarlo. De esta forma, si
acabamos de dibujar el segundo gráfico escribiendo antes subplot(132) y queremos cambiar
algo del primero, debemos activarlo con subplot(131) y en ese momento todas funciones de
gráficas que hagamos se aplicarán a él.
>>> # Trazo una línea horizontal en la coordenada y=4 color verde (g)
>>> axhline(4, color='g')
>>> <matplotlib.lines.Line2D object at 0x169c6d8c>
>>> # Trazo una línea vertical en la coordenada x=20 color verde (g)
>>> axvline(20, color='g')
En este caso hemos usado además algunas funciones para crear líneas y bandas horizontales y
verticales.
Cuando se trabaja con datos de laboratorio es muy habitual tener errores asociados a los datos que
se van tomando; al representarlos gráficamente después es muy conveniente dibujar también las
barras de error de cada dato tomado. En Python esto se puede hacer fácilmente usando la función
errorbar() en lugar de plot() o junto con ella. A la hora de usarla hay que incluir en su
sintaxis los errores como parámetros usando floats si son errores iguales para todos los puntos o
bien un array representando el error de cada punto. Veamos un ejemplo:
>>> # Datos de x e y
>>> x = array([ 0.1, 0.6, 1.1, 1.6, 2.1, 2.6, 3.1, 3.6, 4.1,
˓→4.6])
Hay veces que para algunos tipos de datos, conviene representar alguno de los ejes o ambos en
escala logarítmica para apreciar mejor la evolución de la gráfica. Podemos usar las funciones
semilogx(), semilogy() o loglog() para hacer un gráfico en escala logarítmica en el
eje x, en el eje y o en ambos, respectivamente. Por ejemplo, para representar el gráfico anterior con
el eje y en escala logarítmica, podemos hacer lo siguiente:
>>> # Eje y en escala logarítmica
>>> p1, = semilogy(x, y,'rs-')
>>> grid()
>>> xlabel('Eje X')
>>> ylabel('Eje Y')
>>> title('Representacion en escala logaritmica del eje Y')
Los datos bidimensionales como su nombre indica son valores de una magnitud física representada
por una función que tiene dos variables independientes, normalmente x e y; se trata pues de repre-
sentar funciones del tipo z=z(x,y), donde z puede representar flujo luminoso, presión atmosférica,
altura del terreno, etc... Hay varias formas de representar estos datos bidimensionales (como podría
ser una imagen), una de ellas es usar escalas de colores para representar los diferentes valores de z
para pares de valores (x,y). En Python lo hacemos usando la función imshow():
>>> # Creo un array 1D 100x100 de valores de 0.0 a 99999.0
>>> datos1D = arange(10000.) # array unidimensional con 10000
˓→elementos
Vamos a representar una imagen en color con formato png, nebulosa.png, que está en nuestro
directorio de trabajo.:
Las imágenes en color tienen 3 canales RGB, podemos limitarnos a tomar uno haciendo:
Podemos ahora cambiar la paleta o la escala de colores con la que queremos ver la imagen, para lo
que variamos el argumento cmap. Si queremos mostrarla en blanco y negro usamos gray():
Después de crear una figura con cualquiera de los procedimientos descritos hasta ahora podemos
guardarla con la función savefig() poniendo como parámetro el nombre del fichero con su ex-
tensión. El formato de grabado se toma automáticamente de la extensión del nombre. Los formatos
disponibles en Python son los más usuales: png, pdf, ps, eps y svg. Por ejemplo:
Si el gráfico se va usar para imprimir, por ejemplo en una publicación científica o en un informe, es
recomendable usar un formato vectorial como Postscript (ps) o Postscript encapsulado (eps), pero
si es para mostrar por pantalla o en una web, el más adecuado es un formato de mapa de bits como
png o jpg.
Consulta la web de matplotlib (http://matplotlib.sourceforge.net/) para ver muchas más pro-
piedades y ejemplos de esta librería.
10.10 Ejercicios
1. La curva plana llamada trocoide, una generalización de la cicloide, es la curva descrita por un
punto P situado a una distancia b del centro de una circunferencia de radio a, a medida que
rueda (sin deslizar) por una superficie horizontal. Tiene por coordenadas (x,y) las siguientes:
𝑥 = 𝑎 𝜑 − 𝑏 𝑠𝑒𝑛𝜑 , 𝑦 = 𝑎 − 𝑏 𝑐𝑜𝑠𝜑
Escribir un programa que dibuje tres curvas (contínuas y sin símbolos), en el mismo gráfico
cartesiano (OX,OY), para un intervalo 𝜑 = [0.0, 18.0] (en radianes) y para los valores de
a=5.0 y b=2.0, 5.0 y 8.0 . Rotular apropiadamente los ejes e incluir una leyenda con los tres
valores de que distinguen las tres curvas.
2. Dibujar las diferentes trayectorias de los proyectiles disparados por un cañón situado en un
terreno horizontal para diferentes ángulos de elevación (inclinación respecto de la horizontal)
en un intervalo de tiempo de 0 a 60 s. El cañón proporciona una velocidad inicial de 300 m/s.
Dibujarlas para los ángulos de elevación siguientes: 20, 30, 40, 50, 60 y 70 grados y suponer
que el cañón está situado en el origen de coordenadas. Rotular apropiadamente los ejes e
insertar una leyenda que identifique las diferentes trayectorias. Recordar que el proyectil no
puede penetrar en el suelo de forma que hay que establecer los límites apropiados para el
dibujo.
3. Cuando una fuente de luz coherente atraviesa una rendija delgada, se produce difracción de
la luz, cuyo patrón de intensidad en la aproximación de Fraunhofer está dado por:
𝑠𝑒𝑛𝛽 2 𝜋𝑎𝑠𝑒𝑛𝜃
𝐼(𝜃) = 𝐼0 ( ) 𝛽=
𝛽 𝜆
𝑇 = 𝑇𝑠 + (𝑇0 − 𝑇𝑠 )𝑒−𝑘𝑡
𝑛
∑︁ (−1)𝑘+1
4
𝑘=1
2𝑘 − 1
el valor obtenido de 𝜋 se acerca lentamenta al verdadero con cada término que se añada.
Calculen todos los valores que va tomando 𝜋 con cada término añadido y representen en una
figura con dos gráficas (usando subplot()) los primeros 300 valores que toma 𝜋 frente al
número de términos usados en una de ellas y el valor absoluto de la diferencia entre el valor
calculado y el real frente al número de elementos en la otra.
5. El fichero medidas_I131.txt (directorio de datos en el aula virtual) contiene medidas de
masa de yodo 131 radioactivo hechas diariamente para medir su coeficiente de desinte-
gración. La primera columna es la masa residual en gramos y la segunda es el error de
la medida. Representen gráficamente las medidas de masa frente al tiempo incluyendo ba-
rras de error usando puntos sin unir con líneas y etiquetando los ejes apropiadamente. Al
leer el fichero saltar las primeras cuatro líneas que no contienen datos. Esto se hace con
input.readlines()[4:].
6. El movimiento de un oscilador amortiguado se puede expresar de la siguiente manera:
𝑎 𝑎
𝑥 = (𝑎 + 𝑏) cos 𝜑 − 𝑏 cos( + 1)𝜑 , 𝑦 = (𝑎 + 𝑏) sin 𝜑 − 𝑏 sin( + 1)𝜑
𝑏 𝑏
Escribir un programa en el que se dibuje la curva (contínua y sin símbolo) para un intervalo
𝜑 = [0, 6𝜋] y para los valores de a= 1/3 y b= 1/8. Rotular apropiadamente los ejes e incluir
en el título los valores de a, b y el intervalo de valores de 𝜑 utilizado.
8. En un experimento de laboratorio se tiene un circuito eléctrico al que vamos variando el
voltaje de la fuente de alimentación desde 0.05 V hasta 15.00 V, a intervalos de 0.01 V y se
mide la intensidad de corriente que circula por él con un amperímetro. Se repite el experi-
mento cinco veces, de forma que para cada voltaje V aplicado se obtienen cinco medidas de
I (en amperios) que se almacenan en una tabla que contiene el fichero datos_intensidad.txt.
Dibujar un histograma y calcular el valor medio y la desviación estándar de las cinco series
de medidas de la intensidad para cada voltaje aplicado al circuito.
El principio de mínimos cuadrados fue formulado por primera vez por Legendre de la siguiente
forma: “El valor más probable de una cantidad observada es tal que la suma de los cuadrados
de las desviaciones de las observaciones respecto de este valor es mínimo”; y se utiliza extensiva-
mente en el problema de la optimización de funciones o en el ajuste de funciones a datos obtenidos
de la observación de fenómenos naturales o experimento de laboratorio.
Puede formularse sucintamente así: si tenemos un conjunto de observaciones de una cantidad
cualquiera
∑︀𝑖=𝑛 𝑥𝑖 con 𝑖 = 1, 2, ..., 𝑛, el valor más probable de esta cantidad es 𝑋 si verifica que la
2
𝑖=1 (𝑥𝑖 − 𝑋) es mínima.
Es útil comprobar que en este caso el valor más probable coincide con el valor medio del conjun-
to de observaciones. En efecto, para comprobarlo no tenemos mas que calcular el mínimo de la
función anterior, es decir:
(︃ 𝑖=𝑛 )︃ 𝑖=𝑛 ∑︀
𝑑 ∑︁
2
∑︁ 𝑥𝑖
(𝑥𝑖 − 𝑋) = 0 ⇒ −2 (𝑥𝑖 − 𝑋) = 0 ⇒ 𝑋 = =𝑥
𝑑𝑋 𝑖=1 𝑖=1
𝑛
Nótese que cuando esta función es mínima lo es también la varianza, 𝜎 2 del conjunto de observa-
ciones 𝑥𝑖 .
113
Curso de Computación Científica, Publicación 2016-09-15
Supongamos que la función que relaciona 𝑥 con 𝑦 es: 𝑦 = 𝑓 (𝑥) = 𝑎𝑥 + 𝑏, por lo tanto la recta
más probable que relaciona el conjunto de observaciones 𝑥𝑖 con el de 𝑦𝑖 será la que minimice la
función:
(︃ 𝑖=𝑛 )︃
∑︁
𝑀= (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )2
𝑖=1
Para calcular los valores de a y b que definen la recta en cuestión, planteamos el problema de
optimización de funciones y obtenemos las ecuaciones que verifican
(︃ 𝑖=𝑛 )︃
𝜕𝑀 ∑︁
=0=2 𝑥𝑖 (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )
𝜕𝑎 𝑖=1
(︃ 𝑖=𝑛 )︃
𝜕𝑀 ∑︁
=0=2 (𝑎𝑥𝑖 + 𝑏 − 𝑦𝑖 )
𝜕𝑏 𝑖=1
es decir,
∑︁ ∑︁ ∑︁
𝑎 𝑥2𝑖 + 𝑏 𝑥𝑖 = 𝑥𝑖 𝑦 𝑖
∑︁ ∑︁
𝑎 𝑥𝑖 + 𝑏𝑛 = 𝑦𝑖
y resolviendo este sistema lineal de dos ecuaciones con dos incógnitas (a y b) nos queda:
∑︀ ∑︀ ∑︀
𝑛 𝑥𝑖 𝑦𝑖 − 𝑥𝑖 𝑦𝑖
𝑎=
𝑛 𝑥2𝑖 − ( 𝑥𝑖 )2
∑︀ ∑︀
𝑦𝑖 𝑥2𝑖 − 𝑥𝑖 𝑥𝑖 𝑦𝑖
∑︀ ∑︀ ∑︀ ∑︀
𝑏=
𝑛 𝑥2𝑖 − ( 𝑥𝑖 )2
∑︀ ∑︀
𝑋𝑎 = 𝑌
en la que X es una matriz MxN para M medidas experimentales y N grados de libertad y el objetivo
es minimizar |𝐴𝑥−𝑦|. En el caso del ajuste a un polinomio de grado N, el modelo sería de la forma
𝑦(𝑥) = 𝑎𝑁 𝑥𝑁 + · · · + 𝑎2 𝑥2 + 𝑎1 𝑥 + 𝑎0
para el que habría que escojer la combinación de parámetros 𝑎𝑖 que mejor se ajusten a los datos.
Así, la matriz A, llamada matriz de Vandermonde tiene esta forma:
⎡ ⎤
𝑥𝑁
1 · · · 𝑥21 𝑥1 1
⎢ 𝑥𝑁 · · · 𝑥2 𝑥2 1 ⎥
⎢ 2 2 ⎥
⎢ .. ⎥
⎣ . ⎦
𝑁 2
𝑥𝑀 · · · 𝑥𝑀 𝑥𝑀 1
en ella, cada fila corresponde a una medida experimental 𝐴𝑥 = 𝑦𝑖 .
en donde x e y son los datos experimentales y n grado del polinomio a ajustar. El resultado es una
lista con los parámetros del polinomio. Si a polyfit() se le incluye la opción full=True, además
de los parámetros devuelve los residuos y otros datos (ver ayuda de polyfit() para detalles).
Consideremos el siguiente ajuste a una recta de una serie de datos x e y:
# Importo todas las funciones de numpy, si no lo he hecho
from numpy import *
# Datos experimentales
x = array([ 0., 1., 2., 3., 4.])
y = array([ 10.2 , 12.1, 15.5 , 18.3, 20.6 ])
print(p)
# imprime [ 2.7 9.94]
en este ejemplo polyfit() devuelve la lista de parámetros p de la recta, por lo que el modelo
lineal 𝑓 (𝑥) = 𝑎𝑥 + 𝑏 de nuestros datos será:
𝑦(𝑥) = 𝑝0 𝑥 + 𝑝1 = 2.7𝑥 + 9.94
Ahora podemos dibujar los datos experimentales y la recta ajustada:
xlabel('Eje x')
ylabel('Eje y')
Como se ve en este ejemplo, la salida por defecto de polyfit() es un array con los parámetros
del ajuste. Sin embargo, si se pide una salida detalla con el parámetro full=True (por defecto
full=False), el resultado es una tupla con el array de parámetros, el residuo, el rango, los valores
singulares y la condición relativa.
∑︀Nos interesa especialmente el residuo del ajuste, que es la suma
cuadrática de todos los resíduos 𝑛𝑖=1 |𝑦𝑖 −𝑓 (𝑥𝑖 )|2 . Para el ejemplo anterior tendríamos lo siguiente:
# Ajuste a una recta, con salida completa
resultado = polyfit(x, y, 1, full=True)
print(resultado)
""" Imprime tupla
(array([ 2.7 , 9.94]), # Parámetros del ajuste
array([ 0.472]), # Suma de residuos
2, # Rango de la matriz del
˓→sistema
Si estamos trabajando con polinomios, puede que nos interese usar las funciones polyval() o
poly1d() de numpy. Se utilizan para evaluar y generar funciones polinómicas respectivamente,
a partir de una lista o array o lista de parámetros. Por ejemplo, si del ejemplo anterior tenemos un
array p con los parámetros del ajuste lineal:
Como es de esperar, polyfit() sólo puede ajustar polinomios a datos experimentales, pero a
veces nos gustaría ajustar otras funciones. En muchos casos, sin embargo, es posible linealizar la
función con un cambio de variable adecuado y ajustar esta última. Por ejemplo la función
𝑏
𝑦=
𝑥+𝑎
se puede linealizar a 𝑦 ′ (𝑥) = 𝑎′ 𝑥 + 𝑏′ haciendo el cambio
1 𝑎′ 1
𝑦′ = 𝑎= 𝑏= .
𝑦 𝑏′ 𝑎′
ahora basta con ajustar la recta 𝑦 ′ (𝑥) = 𝑎′ 𝑥+𝑏′ y recuperar los parámetros a y b de la recta original.
En otros casos en los que no es posible linealizar la función o modelo, se tendrá que emplear un
método de optimización por mínimos cuadrados más general.
# Datos de laboratorio
datos_y = array([ 2.9, 6.1, 10.9, 12.8, 19.2])
datos_x = array([ 1.0, 2.0, 3.0, 4.0, 5.0])
p0 = [2.0, 0.0]
Naturalmente, para un simple ajuste lineal bien podemos usar polyfit(), de hecho podemos
comprobar que da lo mismo.
Pero leastq() se hace indispensable con una función no lineal ni linealizable. Veamos otro
ejemplo con una función seno.
from pylab import *
from scipy.optimize import leastsq
from scipy import random
# Parámetros iniciales
# y = p[0]*sin(2*pi*p[1]*x + p[2])
# Si estos se alejan mucho del valor real
# la solución no convergerá
p0 = [8.0, 40.0, pi/3]
Aunque es algo más laborioso, leastsq() nos permite en principio ajustar a cualquier función
ajustable. El módulo optimize posee otros métodos de optimización/minimización y desde la
versión 0.8 de scipy posee la función curve_fit(), que básicamente es una manera simplifi-
cada y más práctica de usar leastsq() para ajuste de curvas.
11.5 Ejercicios
1. Representar gráficamente los siguientes datos y hacer un ajuste por mínimos cuadrados a un
polinomio de grado tres de los datos representando la curva resultante.
x = 3.1 6.3 9.9 12.6 21.4
y = 50.1 190.2 499.0 720.8 1130.0
Superponer los dibujos de las funciones que resultarían de ajustes a polinomios de orden 1 y
de orden 2.
2. El fichero de texto medidas_PT_H.txt (archivos Tema 7) posee medidas de presión y tempe-
ratura para 10 mol de hidrógeno, que se someten a distintas temperaturas a volumen constan-
te. Este experimento se realiza en tres envases con volúmenes distintos. Suponiendo que el
gas se comporta idealmente y por tanto que se verifica que PV=nRT, representar los datos y
realizar un ajuste lineal P(T) para cada volumen. ¿Cuánto vale la constante de gases ideales
según cada realización del experimento?
3. El fichero de texto medidas_PV_He.txt (archivos Tema 7) posee medidas de presión y vo-
lumen para 0.1 mol de helio, que se comprime sistemáticamente a temperatura constante.
Este experimento se realiza a tres temperaturas distintas. Suponiendo que el gas se compor-
ta idealmente y por tanto que se verifica que PV=nRT, representar los datos y realizar un
ajuste lineal P(V) para cada temperatura. ¿Cuánto vale la constante de gases ideales según el
experimento?
4. Se mide el movimiento unidimensional de una partícula sometida una fuerza a lo lar-
go de 50 segundos; las medidas de posición y tiempo se encuentran en el fichero medi-
das_movimiento_acelerado.txt. Hacer un ajuste a los datos de un movimiento uniforme-
mente acelerado y deducir la aceleración y la velocidad y posición iniciales de la partícula.
Representar gráficamente los datos con puntos y la curva de ajuste, con el etiquetado de ejes
y leyenda.
5. Un isótopo radioactivo sigue una ley de desintegración exponencial de la forma 𝑁 (𝑡) =
𝑁0 𝑒−𝑘𝑡 , donde 𝑁 (𝑡) es la cantidad de material que queda en un tiempo t, 𝑁0 la cantidad origi-
nal (en t=0) y k es la tasa de decaimiento del isótopo. La semivida de un isótopo es el tiempo
que tarda una muestra de ese elemento en disminuir hasta la mitad, es decir 𝑁 (𝑇 ) = 𝑁0 /2.
En un laboratorio se mide cada 12 minutos la masa en gramos de cierto elemento radioactivo.
Estos datos se encuentran en el fichero medidas_sustancia_radioactiva.txt (archivos Tema 7).
Representar gráficamente las medidas con puntos y hacer un ajuste por mínimos cuadrados
del modelo teórico de desintegración radioactiva para conocer la tasa de decaimiento del
isótopo.
6. Para una muestra que contiene 10g de yodo 131 (semivida de 8 días), se hacen diariamente
cinco medidas independientes a lo largo de 60 días. Esas medidas están en el fichero medi-
das_decaimiento_yodo131b.txt (archivos Tema 7), donde cada fila corresponde a cada una
de las 5 medidas realizadas diariamente en gramos de yodo que queda. Representar en un
gráfico con puntos las cinco medidas con colores distintos para cada una y ajustar a cada
una la curva teórica de decaimiento. Imprimir por pantalla los parámetros de cada uno de los
cinco ajustes.
7. Cualquier metal de longitud 𝐿0 a temperatura inicial 𝑇0 , que es sometido posteriomente a una
temperatura T sufre una dilatación o contracción dada aproximadamente por 𝛿𝐿 = 𝛼∆𝑇 )
donde ∆𝑇 es la diferencia de temperaturas y 𝛼 el coeficiente de dilatación característico del
metal. En un laboratorio se mide la dilatación que experimentan cuatro varillas de metal de
distinto material de longitud inicial 𝐿0 = 10𝑐𝑚 al ir aumentando progresivamente su tempe-
ratura en un grado; estos datos se encuentran en el fichero medidas_dilatacion_metales.txt.
Representar gráficamente las medidas en una única figura con un color distinto para cada
metal y calcular el factor de dilatación a para cada uno ajustando el modelo teórico a los
datos experimentales.
125
Curso de Computación Científica, Publicación 2016-09-15
En geometría elemental se aprende a calcular el área de figuras simples como rectángulos, triángu-
los, círculos y demás, así como los volúmenes de esferas, cubos, cilindros, conos y otros. Una de
las motivaciones principales para el invento del cálculo integral fue el cálculo de áreas y volúmenes
de regiones del espacio de forma irregular que además constituye un problema central en mecánica
clásica; por ejemplo, para muchos problemas de dinámica de un cuerpo rígido puede considerarse
como una masa puntual localizada en su centroide (centro de gravedad).
Supongamos una función 𝑓 (𝑥) definida en un intervalo [a,b] , la definición moderna de integral:
∫︁ 𝑏
𝐼(𝑓 ) = 𝑓 (𝑥)𝑑𝑥
𝑎
donde, 𝑎 = 𝑥1 < 𝑥2 < ... < 𝑥𝑛 < 𝑥𝑛+1 = 𝑏 y 𝜉 ∈ [𝑥𝑖 , 𝑥𝑖+1 ], 𝑖 = 1, ..., 𝑛.
Supongamos que ℎ𝑛 = máx (𝑥𝑖+1 − 𝑥𝑖 ) : 𝑖 = 1, ..., 𝑛 − 1. Si para cualquier 𝑥𝑖 tal que ℎ𝑛 → 0 y
cualquiera que sea 𝜉𝑖 tenemos que el lı́m𝑛→∞ 𝑅𝑛 = 𝑅 finito, entonces se dice que 𝑓 es integrable
en el sentido de Riemann en el intervalo [𝑎, 𝑏] y que el valor de la integral es 𝑅.
Como pueden intuir esta definición enseguida sugiere una forma sencilla de calcular aproximada-
mente la integral. Sólo tenemos que usar una suma de Riemann con 𝑛 suficientemente grande para
alcanzar la precisión requerida en el valor de la integral. Esta idea funciona, pero debemos elegir
los valores de 𝑥𝑖 y 𝜉𝑖 apropiadamente para evaluar el integrando el número de veces que haga falta
sin que sean demasiadas (aumenta el tiempo de cálculo y la necesidad de memoria) o insuficientes
(aumenta la imprecisión en el resultado).
Desgraciadamente, algunas funciones que queremos integrar no tienen primitivas de forma analí-
tica y, por lo tanto, no nos quedará más remedio que utilizar métodos numéricos para calcular las
integrales de forma aproximada. Por otro lado, muchas veces tendremos la función a integrar en
forma de serie numérica y no en forma analítica por lo que deberemos utilizar métodos numéricos
para calcular la integral. A este proceso se le llama cuadratura numérica.
Existen muchas más aplicaciones de la llamada cuadratura numérica, tanto en matemáticas como
en diferentes partes de la Física, de otras ciencias e ingeniería. Aquí solamente introduciremos los
conceptos elementales para el cálculo práctico de las integrales determinadas de funciones de una
variable en un intervalo dado.
Para el cálculo aproximado de las integrales tomaremos la idea de la suma de Riemann. De hecho,
aproximaremos la integral por una suma ponderada de los valores del integrando en un número
finito de puntos en el intervalo de integración. De forma más precisa, aproximaremos el valor de
𝐼(𝑓 ) por una regla de cuadratura de 𝑛 puntos que tiene la forma de:
𝑛
∑︁
𝑄𝑛 (𝑓 ) = 𝑤𝑖 𝑓 (𝑥𝑖 ) , 𝑎 ≤ 𝑥1 < 𝑥2 < ... < 𝑥𝑛 ≤ 𝑏.
𝑖=1
A los puntos 𝑥𝑖 se les llama nodos de abcisas o nodos simplemente y a los factores 𝑤𝑖 se les
llama pesos o coeficientes. Una cuadratura se llama abierta si 𝑥 ∈ (𝑎, 𝑏) y cerrada si 𝑥 ∈ [𝑎, 𝑏]. El
principal objetivo consiste en elegir los nodos y los pesos de forma apropiada para que podamos
obtener el nivel deseado de precisión con un coste computacional aceptable, en memoria y/o en
rapidez.
Algunas reglas de cuadratura pueden obtenerse utilizando lo que se conoce como interpolación
polinómica. Se trata de una técnica de interpolación de un conjunto de datos o de una función
por un polinomio. Es decir, dado cierto número de puntos (obtenidos por muestreo o a partir de un
experimento) se pretende encontrar un polinomio que pase por todos los puntos. Los más conocidos
y sencillos los detallamos a continuación.
La elección más simple de los nodos en una regla de cuadratura es elegirlos igualmente separa-
dos en el intervalo de integración [a,b] lo que constituye la propiedad que define las llamadas
cuadraturas de Newton-Cotes (NC). Una cuadratura NC de n puntos tendrá por nodos:
𝑏−𝑎
𝑥𝑖 = 𝑎 + 𝑖 , 𝑖 = 1, ..., 𝑛.
𝑛+1
Algunas de las reglas más simples de cuadraturas de NC son las siguientes:
Regla del punto medio. Consiste en evaluar la función en el punto medio del intervalo y
sustituir los valores de la función en él por un polinomio de grado 0, es decir una constante;
lo que nos da la regla de NC de un punto:
𝑛−1
∑︁ 𝑥𝑖 + 𝑥𝑖+1
𝑀 (𝑓 ) = ℎ 𝑓( ) ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
𝑖=1
2
Regla del trapecio: Consiste en evaluar la función en ambos extremos del intervalo y sus-
tituir los valores de la función en él por un polinomio de grado 1, es decir una recta; lo que
nos da la regla de NC de 2 puntos:
𝑛−1
∑︁ 𝑓 (𝑥𝑖 ) + 𝑓 (𝑥𝑖+1 )
𝑇 (𝑓 ) = ℎ ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
𝑖=1
2
Regla de Simpson: Se evalúa la función en los extremos del intervalo y en el punto medio
del mismo sustituyendo los valores de la función en él por un polinomio de grado 2, una
parábola; lo que nos da la regla de NC de 3 puntos:
𝑛−1 (︂ )︂
ℎ ∑︁ 𝑥𝑖 + 𝑥𝑖+1
𝑆(𝑓 ) = 𝑓 (𝑥𝑖 ) + 4 · 𝑓 ( ) + 𝑓 (𝑥𝑖+1 ) ℎ = 𝑥𝑖+1 − 𝑥𝑖 ∀𝑖.
6 𝑖=1 2
El subpaquete integrate del paquete scipy de Python ofrece varias herramientas de integra-
ción numérica con distintos métodos. Podemos ver todos los disponibles consultando su ayuda:
Integration routines
====================
Existen por ejemplo implementaciones del método del trapecio o el método de Simpson, am-
bos métodos simples basados en muestras fijas como ya hemos visto. Las funciones trapz()
y simps(), que emplean dichos métodos para al cálculo numérico de integrales, admiten como
entrada un array y de valores a integrar y otro array con la variable independiente x; si no se incluye
un segundo parámetro, el espaciado entre los elementos de y es de 1 por defecto, aunque este valor
se puede asignar con el parámetro opcional dx=1.
Supongamos que queremos integrar numéricamente la función sin(𝑥) de 0 a 𝜋, cuyo valor exacto
es 2. Veamos cómo se puede calcular numéricamente con distinto número de muestras y el grado
de precisión que se alcanza:
>>> from scipy import integrate
Como se ve, el método de Simpson nos da un valor más cercano al verdadero (error relativo de
1.14e-05), que el del trapecio (error relativo de 2.28e-03), ya que el primero emplea polinomios de
grado 2 para la integración. Obsérvese que estos métodos de cálculo nos permiten también obtener
las integrales de funciones que conozcamos en forma numérica (como sucesión de nímeros).
Por otro lado, si queremos calcular la integral de una función que tenemos en forma analíti-
ca también lo podemos hacer utilizando la función de Python de uso general más eficiente es
quad(func,a,b), que integra por cuadratura de Clenshaw-Curtis 1 una función de Python
func() entre a y b. Consideremos como ejemplo el cálculo del área una semicircunferencia
de radio unidad calculando la integral bajo la curva. Para ello definimos la función e integramos
entre -1 y 1:
La función quad() devuelve por defecto el valor de la integral, que en este caso vale 𝜋2 y una
estimación del error en el proceso de integración numérica. La función quad ofrece como salida,
además del valor de la integral, una estimación del error que comete en el cálculo de la integral;
este error lo calcula internamente y es debido al proceso empleado para el cálculo de la integral
pero no debe confundirse con el cálculo del error relativo que podemos hacer sólo si conocemos el
valor exacto de la integral.
En Python los límites de integración también pueden ser +∞ ∫︀ 2 o −∞ usando
2
los string +Inf o
-Inf; por ejemplo podemos calcular la integral (ver figura) −2 2𝑒𝑥𝑝− 𝑥5 𝑑𝑥,
# integración entre -2 y +2
>>> int1, err1 = integrate.quad(func1, -2, +2)
>>> print(int1,err1)
(6.294530963693763, 6.9883332051087914e-14)
Si ahora hacemos la misma integral pero desde −∞ hasta +∞ obtendremos toda el área bajo la
curva definida en el integrando:
1
Para detalles, ver por ejemplo http://en.wikipedia.org/wiki/Clenshaw-Curtis_quadrature
Nótese la diferencia entre el resultado de ambas integrales así como la diferencia entre el error
estimado al calcularlas.
Es posible incluir varios parámetros a la función empleando el parámetro opcional args como
∫︀una
𝜋
tupla de parámetros (ver la ayuda de quad()). Por ejemplo, calculemos la integral siguiente:
0
(𝑎 + 𝑏𝑠𝑒𝑛(𝑥))𝑑𝑥 para valores de a=0.2 y de b=1:
Los arrays bidimensionales de numpy pueden interpretarse como matrices, aunque en realidad los
arrays, cuando se opera algebraicamente con ellos, no funcionan como estamos acostumbrados
con matrices. Por ejemplo el producto de dos arrays bidimensionales NxM se realiza elemento a
elemento y no como un producto algebraico de matrices. Veamos unos ejemplos:
>>> print(A)
[[ 3 6 7]
[ 2 6 2]
[10 9 1]]
>>> print(B)
[[ 4 5 5]
[ 8 3 4]
[ 3 11 2]]
que, como vemos da como resultado otro array con elementos que son el producto elemento a
elemento de los arrays factores. Sin embargo numpy permite hacer el producto punto entre estos
arrays que es similar (de hecho idéntico) al producto matricial de dos matrices con la función
func:dot:
>>> # Producto entre los arrays como si fueran matrices
>>> print(dot(A,B))
[[ 81 110 53]
[ 62 50 38]
[115 88 88]]
La inversa de una matriz A es una matriz B tal que AB = I donde I es la llamada matriz
identidad que consiste en una matriz en la que los elementos en la diagonal son unos y son ceros
en el resto. Normalmente B se denota como B = A−1 . En scipy, la inversa de una matriz de un
array numpy se puede calcular haciendo linalg.inv(A), o usando el método A.I si A es una
matriz. Por ejemplo, consideremos
⎡ ⎤
1 3 5
A =⎣ 2 5 1 ⎦
2 3 8
entonces:
⎡ ⎤ ⎡ ⎤
−37 9 22 −1.48 0.36 0.88
1 ⎣
A−1 = 14 2 −9 ⎦ = ⎣ 0.56 0.08 −0.36 ⎦ .
25
4 −3 1 0.16 −0.12 0.04
>>> b = mat('[10;8;3]')
>>> A.I*b # Usando la matriz inversa
matrix([[-9.28],
[ 5.16],
[ 0.76]])
Supongamos que 𝑎𝑖𝑗 son los elementos de la matriz A y 𝑀𝑖𝑗 = |A𝑖𝑗 | será el determinante de la
matriz que se obtiene elimiando la i-esima fila y la j-esima columna de A. Entonces para cualquier
fila i:
∑︁
|A| = (−1)𝑖+𝑗 𝑎𝑖𝑗 𝑀𝑖𝑗 .
𝑗
Con Scipy el determinante se puede calcular con linalg.det. Por ejemplo, el determinante de la
matriz A
⎡ ⎤
1 3 5
A =⎣ 2 5 1 ⎦
2 3 8
es
⃒ ⃒ ⃒ ⃒ ⃒ ⃒
⃒ 5 1 ⃒ ⃒ 2 1 ⃒ ⃒ 2 5 ⃒
|A| = 1 ⃒⃒ ⃒ − 3⃒
⃒ 2 8 ⃒ + 5⃒ 2 3 ⃒
⃒ ⃒ ⃒
3 8 ⃒
= 1 (5 · 8 − 3 · 1) − 3 (2 · 8 − 2 · 1) + 5 (2 · 3 − 2 · 5) = −25.
12.5 Ejercicios
∫︁ 1 √ ∫︁ 2𝜋
2𝜋 𝑥 3
1 + 9𝑥4 𝑑𝑥 𝑒−𝑥 sin (10𝑥) 𝑑𝑥
0 0
∫︁ 1 ∫︁ 4 ∫︁ 2𝜋
−𝑥2 𝑑𝑥 𝑑𝑥 2𝜋
𝑒 𝑑𝑥 = 0.746 = tan−1 (4) = 1.3258 = √ = 3.627
0 0 1 + 𝑥2 0 2 + cos 𝑥 3
1
𝑥𝑚 − 𝑥𝑛
∫︁
𝑚+1
𝜋 𝑑𝑥 = ln
0 ln 𝑥 𝑛+1
6. Las leyes de Kirchhoff de circuitos, indican que la suma de los voltajes en cada malla de
un circuito es igual∑︀a la suma
∑︀ de las resistencias multiplicadas por la corriente que circula
por ellas, es decir 𝑉 = 𝑅𝐼, que no es más que la ley de Ohm generalizada y que las
∑︀ ∑︀
corrientes en los nodos se conserva, es decir, 𝐼𝑒𝑛𝑡𝑟𝑎𝑛 = 𝐼𝑠𝑎𝑙𝑒𝑛 . Utilizando las leyes de
Kirchhoff crear un sistema de ecuaciones para el siguinte circuito y resolverlo numéricamen-
te para conocer cada una de las corrientes que circulan por él.
⎡ ⎤ ⎡ ⎤
1 3 5 7 1
⎢ 2 −1 3 5 ⎥ ⎢ 2 ⎥
A =⎢
⎣ 0
⎥ 𝑦 B =⎢ ⎥
0 2 5 ⎦ ⎣ 3 ⎦
−2 −6 −6 1 4
8. Utilice las leyes de Kirchhoff para conocer las corrientes que circulan por este circuito,
sabiendo que V1 = 7.5 V, V2 = 5.4 V y las resistencias valen 6.7, 2.3, 9.0, 1.0 y 5.6 Ω.
Cualquier sistema operativo se puede utilizar para el análisis de datos y computación científica en
general, aunque en ambientes científicos y técnicos GNU/Linux es muy empleado y será nuestro
sistema operativo de referencia. En las aulas de informática de la facultad utilizamos Bardinux, la
distribución Linux de la Universidad de la Laguna, que junto con Ubuntu, una de las distribuciones
Linux más populares, son las que recomendamos y se pueden descargar gratuitamente de sus sitios
web.
Si utilizas MS Windows en cualquier versión, puedes crear una partición en tu ordenador para que
éste tenga ambos sistemas operativos, aunque hay que recordar que no es indispensable usar Linux.
Esta opción no es posible para los usuarios de Mac, aunque el MacOS es un sistema operativo
similar a Linux por lo que no aportaría tanto.
Existe la opción de usar una máquina virtual para instalar en Windows o Mac una distribución de
Linux, pero no es recomendable porque se hace muy pesado para el ordenador y la idea es tener
un sistema en que trabajar habitualmente.
Existen literalmente cientos de lenguajes de programación, pero sólo algunos son adecuados para el
análisis interactivo de datos. En los últimos años, los más usados son Matlab, IDL o Mathematica,
que son programas con licencia privada (o sea que hay que comprar) pensados para uso científico y
en ingeniería. Por otro lado, Python es un lenguaje interactivo de uso general publicado en código
abierto (open source, compatible con licencia GPL) que es muy usado en ciencia y tecnología,
siendo una alternativa más flexible y completa que los anteriores lenguajes.
Ventajas de Python
Lenguaje de código abierto y gratuito para todos los sistemas operativos.
137
Curso de Computación Científica, Publicación 2016-09-15
Ya que Python es un lenguaje de uso general, existen funcionalidades para prácticamente cualquier
proyecto o actividad. Aunque la instalación de Python ya viene con gran cantidad de funcionali-
dades o módulos, para trabajos específicos como el análisis científico de datos, son necesarios
módulos adicionales. Para este curso usaremos los módulos científicos en Python más populares,
que es necesario instalar además del propio Python.
Los módulos básicos que usaremos son los siguientes:
Scipy/Numpy - Paquete científico y numérico básico.
Matplotlib - Librería gráfica para gráficos 2D.
ipython - No es un módulo de Python, sino una consola avanzada, que usaremos en lugar de
la de estándar de Python
Si usas Linux, Python ya viene instalado y sólo tienes que instalar los módulos adicionales usando
el administrador de paquetes de tu distribución. La manera más fácil instalar software en Linux
es usar la consola de comandos. Para distribuciones basadas en Debian como Ubuntu o Bardinux,
basta ejecutar lo siguiente en una consola:
El comando sudo te permite ejecutar un comando como superusuario o administrador del orde-
nador (necesario para instalar), por lo que te pedirá la contraseña de administrador e instalará los
Tanto en Windows como en Mac es posible instalar Python y luego instalar los módulos científicos
necesarios, aunque hay varios proyectos que hacen esto por nosotros, es decir, instalar Python y
todos los módulos científicos en un paso. El proyecto más completo y recomendado es Anaconda ,
un software que reúne Python y gran cantidad de módulos. Basta descargar la versión de Anaconda
- Python 2.7 para el sistema operativo que usemos y seguir las instrucciones.
Veremos que tenemos la opción de usar dos versiones distintas, Python 2.7 o Python 3.5, y aunque
podemos emplear cualquiera y no hay gran diferencia entre ambas, es recomendable usar por ahora
Python 2.7 ya que es el que viene por defecto en Linux y Mac y de esta manera todos usaremos la
misma versión.
Además del uso de Python interactivamente por terminal de comandos, también necesitaremos
hacer programas ejecutables, por lo que necesitaremos un editor de texto/código adecuado. Existen
muchas opciones para todos los sistemas operativos y suelen todos venir con alguno útil, aunque a
nosotros nos interesa un editor sencillo que de destaque con colores el código y ayude a la edición.
Gedit: Editor por defecto para escritorios Gnome (como Ubuntu). Simple pero muy funcio-
nal.
Kedit: Editor por defecto para escritorios KDE (como Bardinux). Muy similar a Gedit.
Kate: Pensado para programadores, muy completo y con muchas herramientas para la edi-
ción de código.
Spyder: Entorno de desarrollo y análisis de datos. Ideal para manipular datos de manera
interactiva y pensado para científicos, aunque algo complejo para nuestro curso.
Si usas Linux, probablemente ya tengas instalado alguno de estos editores, si no, utiliza el admi-
nistrador de paquetes de tu distribución. También puedes instalar programas usando la terminal de
comandos de Linux:
En Mac existen varias buenas opciones también. Mac OSX incluye por defecto un editor de texto,
llamado TextEdit.app que se puede utilizar para editar código, aunque es muy limitado. Reco-
mendamos alguna de las siguientes opciones:
Spyder. Entorno de desarrollo y análisis de datos. Ideal para manipular datos de manera
interactiva y pensado para científicos. Está incluído en Python-Anaconda.
TextWrangler. Editor de texto sencillo de caracter general.
Windows incluye por defecto dos editores de texto, el Notepad y Wordpad o similares. El primero
es demasiado sencillo y el segundo está más orientado a escribir documentos en lugar de progra-
mas, por lo que no recomendamos usar ninguno de los dos. En su lugar existen buenas opciones:
Spyder.: Entorno de desarrollo y análisis de datos con Python. Ideal para manipular datos de
manera interactiva y pensado para científicos. Está incluído en Python-Anaconda.
Notepad++. Editor muy completo y para muchos lenguajes de programación.
Python es un lenguaje de programación muy completo y con extensa documentación, pero convie-
ne tener a mano algunas guías:
Python Scientific Lecture Notes. Completo curso de Python científico; Incluye algunos temas
avanzados. En inglés.
Introducción a la programación con Python, Andrés Marzal, Isabel Gracia y Pedro García
Sevilla, Universitat Jaume I (2014). Guía de programación muy completa y detallada, aunque
orientada a estudiantes de informática.
Python para todos, Raúl González Duque. Buen libro de introducción de Python, aunque no
incluye paquetes científicos.
Think Python - How to Think Like a Computer Scientist, Allen Downey – Otra buena guía
de introducción a Python. En inglés.
Documentación oficial (sólo de consulta)
Tutorial oficial de Python - http://docs.python.org/tutorial/
Documentación de Scipy y Numpy - http://docs.scipy.org/doc/
Documentación de Matplotlib - http://matplotlib.sourceforge.net/contents.html
Documentación de Ipython - http://ipython.scipy.org/doc/stable/html/
Linux es un sistema multiusuario, por lo que requiere una cuenta de usuario y su contraseña para
entrar en el sistema y en él cada usuario posee una zona de trabajo reservada con una configuración
propia única. Para empezar, entra en el sistema escribiendo tu nombre de usuario y contraseña. Una
vez dentro, verás un escritorio de trabajo similar al de la Figura 1, en el que se reconocen varias
zonas.
143
Curso de Computación Científica, Publicación 2016-09-15
La barra horizontal inferior es la barra de tareas y aplicaciones, desde donde se accede a los pro-
gramas presionando el botón de la esquina inferior izquierda (menú de aplicaciones). Desde el
menú de aplicaciones podemos ver los programas disponibles en el ordenador, organizados por
categorías. Para este curso, utilizaremos básicamente el navegador Firefox, que se encuentra en la
categoría Internet y también un editor de texto (no procesador de textos gráfico) como Kate. En
la pestaña “Máquina” al lado de Aplicaciones hay un icono de acceso al directorio personal de
nuestro usuario. En general, lo más práctico es buscar el programa deseado en el buscador, en la
parte superior del menú.
Aunque gracias al interfaz gráfico de Linux (el que usa Bardinux se llama KDE) podemos hacer
prácticamente cualquier operación con el ordenador de manera gráfica como con Windows o Mac,
para muchos trabajos conviene saber cómo hacerlos manualmente en lugar de usar la interfaz
gráfico. Para ello usaremos la terminal de comandos, que no es más que un programa en el que
escribimos órdenes o comandos al ordenador. Aunque esto puede parecer anticuado, en cuanto
se conoce un poco cómo funciona a menudo se trabaja de forma más rápida y directa con ella
y además es indispensable para ciertos trabajos. Para iniciar una terminal, hay que ir al menú de
aplicaciones en la sección Sistema, donde está la aplicación Konsole (o buscarlo en el buscador);
al ejecutarlo aparecerá una ventana como la de la Figura 3.
Una vez lanzada la terminal de comandos, ésta está lista para recibir instrucciones. En ella veremos
una pantalla en negro (u otro color, según la configuración). La única línea que aparece en ella es
la línea de comandos o “Prompt” en inglés, que indica el nombre del usuario y el de la máquina,
separados por una arroba. En el ejemplo anterior vemos:
japp@paquito:~$
japp@paquito:~$ pwd
/home/japp
japp@paquito:~$
Vemos que nos devuelve una línea con nuestro directorio de trabajo actual /home/japp, y regresa
al prompt. Podemos ver el contenido de nuestro directorio y cualquier otro usado el comando ls:
Figura 14.3: Listado del contenido de un directorio en la terminal de comandos usando ls.
Generalmente los comandos de Linux tienen varias opciones que cambian su funcionalidad; por
ejemplo, para ver un listado más completo del directorio, usamos la opción -l del comando ls:
Figura 14.4: Listado detallado del contenido de un directorio en la terminal de comandos usando
ls -l.
Ahora vemos un fichero o directorio por línea, cuyo nombre aparece a la derecha de todo. A la
izquierda aparecen unos caracteres del tipo “drwxr-xr-x” que se refieren a los permisos de acceso
al fichero, que veremos más adelante. Después se muestra el usuario y grupo al que pertenecen los
ficheros, que en el ejemplo de arriba es “japp” para ambos. La siguiente columna indica el tamaño
en bytes del fichero y finalmente se muestra la fecha y hora de su creación.
Desde la terminal podemos crear un fichero o ejecutar cualquier programa. Podemos, por ejemplo,
lanzar el editor de texto kwrite o cualquier otro (kate, p.e.) y crear un fichero de texto. :
japp@paquito:~$ kate prueba.txt &
Vemos que ahora aparece el nuevo fichero de texto, con un tamaño y fecha de creación distintos.
Nótese que el primer carácter de la tabla de permisos de cada fichero es “d”, excepto para prue-
ba.txt. La “d” indica que es un directorio y por tanto todos los documentos en el listado anterior
son directorios, excepto el fichero de texto que acabamos de crear.
Podemos crear nuestro propio directorio usando el comando mkdir:
japp@paquito:~$ mkdir practicas_16-septiembre-2011
Fíjate que el prompt ha cambiado y ahora muestra el nuevo directorio; comprobamos que esta-
mos en él usado pwd nuevamente. Para regresar al directorio anterior, podemos hacerlo con cd
/home/japp o bien bajando un nivel de directorio con cd ... Aquí ”..” (dos puntos seguidos)
es una abreviatura que se refiere al directorio anterior. Igualmente, como el símbolo “~” es una
abreviatura de nuestro home, podemos ir a él estemos donde estemos escribiendo cd ~ o incluso
cd solamente.
Linux tiene una estructura de directorio cuyo raíz o directorio base es */* y en él están otros di-
rectorios del sistema, como el home. Podemos ver su contenido haciendo ls -l / en la terminal.
Verás que el propietario de todos los ficheros del directorio raíz o del sistema es el usuario root.
El usuario root es el administrador del sistema y tiene privilegios especiales en el ordenador y sus
usuarios; un usuario normal no puede por tanto alterar estos ficheros ni instalar nuevos programas,
sólo el usuario root puede hacerlo.
Cualquier fichero o directorio se puede copiar con el comando cp, indicando las rutas de los fiche-
ros de origen y destino:
En el primer ejemplo se hace una copia del mismo fichero en el mismo directorio con otro nombre
y en el segundo se copia el fichero a otro directorio con el mismo nombre. Como es habitual, se
puede indicar la ruta (o path, en inglés) como en el segundo ejemplo o usar direcciones relativas
como en el primero. En caso de querer cambiar el nombre de un fichero o su localización, se
emplea el comando mv, cuyo uso es idéntico a cp, sólo que mv reemplaza un fichero o directorio
por otro en lugar de copiarlo:
en el primer ejemplo se cambia el nombre del fichero prueba.txt por prueba2.txt, en el segun-
do se mueve el prueba.txt del home al directorio Documentos/, conservando el mismo nombre.
Fíjate que “cambiar de nombre” y “mover” son exactamente lo mismo. En realidad, para Linux
el nombre de un fichero es su ruta completa. Por ejemplo, nuestro fichero prueba.txt, para Li-
nux en realidad se llama /home/japp/prueba.txt y al “moverlo” realmente lo renombramos a /ho-
me/japp/Documentos/prueba.txt y de ahí la equivalencia entre mover y renombrar.
Para eliminar un fichero, puedes hacerlo con rm, pero debes usarlo con precaución, porque no hay
vuelta atrás y el borrado es permanente. Para borrar un directorio vacío, se puede hacer con rmdir,
pero daría un error si éste no está vacío. Si queremos borrar un directorio no vacío, podemos usar
el comando rm de manera recursiva con la opción -r:
Si no recuerdas cómo se usa un comando o las opciones que tiene, recuerda que para cualquier
comando de Linux tienes acceso a ayuda escribiendo comando correspondiente con la opción
--help. Para una descripción más detallada, se puede acceder a su manual escribiendo man
<comando>:
Para muchos comandos de la terminal que implican tratar con ficheros de nombre similar, podemos
utilizar caracteres comodín “*”, ”?” y “[ ]”. El * reemplaza a cualquier cadena de caracteres, por
ejemplo:
japp@paquito:~$ ls pr*
listará todos los ficheros que empiezan por “pr”. El ”?” reemplaza cualquier carácter, pero única-
mente uno, por ejemplo:
japp@paquito:~$ ls prueba?.txt
japp@paquito:~$ ls prueba[2-4].txt
La terminal de Linux tiene muchos comandos y utilidades para trabajar con ficheros de texto nor-
males. Por ejemplo, aunque habitualmente el resultado de un comando cualquiera se muestra por
pantalla, como un listado con ls, podemos pasar este resultado a un fichero de texto en lugar de la
pantalla poniendo “> nombre_del_fichero.txt” después del comando:
el comando anterior lista todos los ficheros que hay en el directorio del sistema /usr, pero en lugar
de mostrar el listado por pantalla lo copia a un fichero llamado “listado_usr.txt”. La extensión
.txt que indica que es un fichero de texto no es obligatoria, pero sí muy recomendable. El fichero
resultante es un fichero de texto normal que podemos abrir por ejemplo con kwrite o kate, que son
editores de texto. Podemos echar un vistazo rápido a un fichero de texto como este con el comando
cat que nos muestra contenido del fichero en la pantalla. Como vemos que se trata de un fichero
bastante largo y difícil manipular, podemos usar el comando more que nos muestra su contenido
pasando pantallas con la barra espaciadora y del que podemos salir con la letra “q”. Un comando
más completo es less que nos muestra el contenido como more pero permitiéndonos avanzar y
retroceder en el fichero usando las flechas de dirección del teclado:
recuerda que puedes ver más opciones de los comando con la opción --help.
Como curiosidad útil, podemos encadenar varios comandos con una “tubería” o “pipeline” usan-
do el carácter “|” entre comandos. Por ejemplo, si queremos ver las diez primeras líneas del direc-
torio /usr/ sin grabarlo a un fichero, basta con hacer:
aquí lo primero que hace la terminal es crear el listado como antes, pero en lugar de mostrarlo por
pantalla, envía el resultado al comando head, que lo procesa y nos muestra el resultado. Podemos
usar el comando wc para contar las líneas, palabras y caracteres de un fichero:
japp@paquito:~$ wc listado_usr.txt
5987 5973 77062 listado_usr.txt
los números que aparecen como resultado indican el número de líneas (5987), palabras (5973) y
caracteres (77062) del fichero. Si queremos buscar un fichero en un directorio o en todo el disco,
usamos find con la notación siguiente:
este ejemplo busca todos los ficheros de extensión “txt” en /home/japp. Para buscar en todos los
directorios:
Al igual que ficheros sueltos, podemos buscar dentro de ficheros de texto. Eso lo podemos hacer
con “grep <cadena de búsqueda> <fichero>”. Por ejemplo, el comando:
busca dentro de listado_usr.txt todas las cadenas “ark” que hay e imprime por pantalla la línea en
la que está.
Puesto que Linux es un sistema multiusuario con usuarios independientes, los ficheros que crea en
su home son suyos y únicamente suyos. El usuario puede administrar el acceso a sus documentos
cambiando su tabla de permisos, que se muestra cuando se hace un listado largo. Por ejemplo, para
el fichero de texto que creamos:
japp@paquito:~$ ls -l prueba.txt
japp@paquito:~$ -rw-r--r-- 1 japp japp 0 2009-09-14 19:47 prueba.txt
como dijimos, el primer carácter es “d” si es un directorio y “-” si no lo es, el resto se agrupan en
tres grupos de caracteres “rwx” que se refieren al dueño del fichero, su grupo y a todos los demás y
las letras rwx indican permisos de lectura (r), escritura (w) y ejecución (x). Esto se entiende mejor
con unos ejemplos:
Por defecto, los ficheros se crean con permisos -rw-r–r–, y estos se pueden modificar con el co-
mando chmod. Su modo de empleo es chmod <XXX> <nombre del fichero> en el que <XXX>
son tres números de 0 a 7 que son los permisos para el usuario, grupo y el resto. Cada valor de X
es la suma cada permiso: X=Lectura+Escritura+Ejecución; los valores de los permisos se asignan
de la siguiente manera:
0 ningún permiso
1 permiso de ejecución
2 permiso de escritura
3 permisos de escritura y ejecución
4 permiso de lectura
5 permisos de lectura y de ejecución
6 permisos de lectura y de escritura
7 todos los permisos
Una manera alternativa de cambiar permisos es asignarlos por tipo de categoría de usuario (usuario,
grupo o resto) usando esta notación:
donde [ugoa] se refiere a al usuario (u), grupo (g), otros (o) o todos (a), el [+-] para dar (+) o quitar
(-) el permiso y [rwx] y indica qué permiso cambiar. Aquí van unos ejemplos:
Si se desea cambiar los permisos a un directorio completo, debe incluirse la opción -R (recursivo)
después del comando. Por ejemplo, el comando:
A menudo tenemos que guardar y transportar ficheros de un ordenador a otro o como copias de
seguridad y en ocasiones los ficheros pueden ser muchos o muy grandes. Se puede comprimir un
fichero con el comando gzip y descomprimirlo con gunzip:
Fíjate que gzip sólo comprime ficheros sueltos. Si queremos comprimir varios ficheros o un direc-
torio, debemos empaquetar los ficheros antes con el comando tar:
En realidad la operación anterior se puede hacer en una sola línea con tar y las opciones adecua-
das:
Hay que notar que aquí usamos -c para empaquetar y -x para desempaquetar; la z se refiere a
que el tar debe comprimirse con gzip y la f indica que después viene el nombre del fichero con
el que empaquetará.
Otro comando similar que empaqueta y comprime al mismo tiempo es zip:
en donde hemos comprimido el directorio Documentos con todo su contenido (opción -r) en un
archivo llamado Documentos.zip. Para recuperarlo usamos el comando unzip.
14.8 Ejercicios
1. Genere un listado de todos los archivos que se encuentren en el directorio /usr/bin que em-
piecen con “a” y que en algún lugar tengan una “o”.
2. ¿Qué comando usaría para borrar todos los ficheros que empiecen por un número y que
tengan extensión mp3 que estén en el directorio Música/ del home?
3. Lista el contenido del home ordenado por fecha de creación (averigua la opción adecuada).
4. ¿Cómo listaría el contenido de /usr/* que se muestre página a página como hace el comando
more?
5. En el directorio /bin del sistema hay programas ejecutables ¿cómo contaría cuántos hay?
6. En el home hay un fichero oculto llamado konqueror, que utiliza el administrador gráfico de
archivos. Encuentre la ruta o path de ese fichero. Los ficheros ocultos tienen un ”.” delante
del nombre.
7. ¿Cual es la diferencia entre intentar borrar un fichero 700 y otro 400, siendo ambos del
propietario? Comprobarlo en la terminal.
8. Cambie los permisos a prueba.txt de manera que absolutamente todos puedan leerlo y mo-
dificarlo.
9. El fichero anterior, prueba.txt, tiene ahora permisos de lectura y escritura para todos. Ha
cambiado de opinión y ahora quiere que el resto (el grupo y otros) sólo pueda leerlo sin
modificarlo, ¿cómo les quitaría esos permisos?
10. El fichero del sistema /proc/meminfo, es un fichero de texto que contiene información sobre
la memoria del ordenador. La memoria total está indicada dentro del fichero con la pala-
bra MemTotal (fíjese en las mayúsculas). ¿Cómo buscarías esa cadena de texto o palabra?
¿Cuánta memoria tiene el ordenador en kB?
11. El comando “ps -ux” muestra una lista larga de procesos que se están ejecutando en el orde-
nador. Muestra sólo los 10 últimos.
12. El comando “history” muestra por pantalla todos los comandos ejecutados en la consola; si
pones “history 25”, muestra los últimos 25, etc. En lugar de mostrarlos por pantalla, pásalos
a un fichero llamado comandos_[tu_nombre].txt en el home.
13. Empaqueta y comprime todos los ficheros de texto en tu directorio de trabajo (con extensión
txt), incluyendo el fichero de textos con la lista de comandos, en un fichero tar.gz.
La distribución gaussiana puede considerarse como el límite de una distribución binomial cuando el
número posible de resultados es infinitamente grande, tan grande que podemos hablar del continuo
(números reales). Es decir, la variable en una distribución gaussiana puede tomar valores entre −∞
y +∞, contrariamente a lo que sucedía con las distribuciones anteriores. No obstante, en la práctica
el número de realizaciones de cualquier suceso es siempre limitado y es importante saber si este
conjunto finito de realizaciones (la distribución subyacente) se puede aproximar a (o representar
por) una distribución gaussiana. En estos casos podemos utilizar sus propiedades para analizar la
distribución subyacente de nuestro conjunto limitado de datos
Tiene multitud de aplicaciones en todos los campos puesto que parece que sea la preferida por la
naturaleza como modelo para los experimentos y observaciones, de tal suerte que suele llamár-
sela también distribución normal. Aunque fue obtenida primero por Demoivre en 1733 cuando
estudiaba un problema de lanzamiento de monedas, fue más tarde obtenida independientemente
por Laplace y por Gauss; de ahí lo de distribución gaussiana, que fue aplicada inicialmente a los
errores accidentales (o aleatorios) en medidas astronómicas y de otros experimentos científicos.
La forma de una distribución gaussiana (la famosa “Campana de Gauss”) es la siguiente:
1 (𝑥 − 𝑥)2
℘𝐺 (𝑥, 𝑥, 𝜎) = √ exp[− ]
2𝜋𝜎 2𝜎 2
y al ser una función continua consideramos esta función como una densidad de probabilidad; es
decir que la probabilidad de que una observación se encuentre entre 𝑥 y 𝑥 + 𝑑𝑥 será ℘𝐺 (𝑥, 𝑥, 𝜎)𝑑𝑥.
Nótese que 𝑥 es la media y 𝜎 es la desviación estándar de la distribución.
155
Curso de Computación Científica, Publicación 2016-09-15
∫︀ ∞
−∞
℘𝐺 (𝑥, 𝑥, 𝜎)𝑑𝑥 = 1; esto correspondería a la probabilidad de obtener una observación
cualquiera,
mostrarse (Gauss fue quien primero lo demostró) que la media ponderada con los pesos 𝑤𝑖 = 1/𝜎𝑖2
es el valor más probable de la medida que buscamos (de la distribución subyacente).
Es decir, como se dice en [6.1]:
𝑥𝑖 /𝜎𝑖2
∑︀
𝑥 = ∑︀𝑖 2
(15.1)
𝑖 1/𝜎𝑖
y de la sección anterior podemos concluir que el error asociado al valor medio será:
√︃ ∑︀
𝑖 (𝑥𝑖 − 𝑥)2 /𝜎𝑖2
𝜖= (15.3)
(𝑛 − 1) 𝑖 1/𝜎𝑖2
∑︀
Démonos cuenta que si todas las medidas están tomadas con igual precisión esta definición se
reduce a:
√︃ ∑︀
2
𝑖 (𝑥𝑖 − 𝑥) (15.4)
𝜖=
𝑛(𝑛 − 1)
Acabamos de ver que si el peso 𝑤𝑖 asignado a una medida 𝑥𝑖 es igual (o proporcional) a 1/𝜖2𝑖 ,
donde 𝜖𝑖 es el error estándar de 𝑥𝑖 , entonces el error estándar de la media pesada 𝑥 podemos
calcularlo como en la ecuación (15.3). A esta forma de obtener el error estándar le llamaremos el
error externo, para distinguirlo de otra forma de calcularlo, el error interno que obtendremos a
continuación.
En efecto, el error estándar de la media pesada también podemos obtenerla de otra forma. En el
capítulo II de esta serie hemos definido como calcular la propagación de errores en una función
cualquiera:
[︃ (︂ )︂2 ]︃
∑︁ 𝜕𝑥
𝜎𝑥2 = 𝜎𝑖2 (15.5)
𝑖
𝜕𝑥 𝑖
En este caso si lo aplicamos a la definición de 𝑥 podemos llegar a que el resultado final es:
1
𝜎𝑥2 = ∑︀ 2 (15.6)
𝑖 (1/𝜎𝑖 )
Las expresiones (15.3) (error externo, 𝜖𝑒𝑥𝑡 ) y (15.6) (error interno, , 𝜖𝑖𝑛𝑡 ) de la media son muy dife-
rentes aparentemente, pero ambas deberian coincidir. Fijémonos que mientras la segunda expresión
((15.6)) depende tan sólo de los errores estándar de cada una de las observaciones o medidas, la
primera expresión ((15.3)) depende también de las diferencias entre ellas y el valor medio.
Su cociente será:
]︂1/2
− 𝑥)2 /𝜖2𝑖
[︂∑︀
𝜖𝑒𝑥𝑡 𝑖 (𝑥𝑖 (15.7)
𝑍= =
𝜖𝑖𝑛𝑡 (𝑛 − 1)
y debería ser igual a 1. Para muestras de medidas tomadas de una población normal infinita puede
demostrarse√︀que 𝜖𝑖𝑛𝑡 y 𝜖𝑒𝑥𝑡 deben coincidir o, mejor dicho, siendo estrictos 𝑍 debe ser 1 con un error
estándar 1/ 2(𝑛 − 1). Si esto es así podemos decir que las observaciones pueden considerarse
consistentes. En este caso, lo mejor es considerar el mayor de los dos errores como el error estándar
de la media pesada y el resultado de las observaciones podemos tomarlo como 𝑥 ± 𝑚𝑎𝑥(𝜖𝑖𝑛𝑡 , 𝜖𝑒𝑥𝑡 ).
En la práctica, generalmente encontrarán que esto no es así y que 𝑍 no coincidirá con la unidad.
Pero esto no debería sorprenderles ya que, por un lado depende fuertemente de los valores de 𝜖𝑖 ;
algunos de ellos pueden ser considerablemente imprecisos o inexactos. Por otro lado, 𝑍 también
depende de lo que se separan las medidas de su valor medio ( 𝑖 (𝑥𝑖 − 𝑥)2 ) y por lo tanto pueden
∑︀
estar afectados de errores sistemáticos. Por tanto, el conjunto de observaciones no es consistente.
Si el resultado difiere significativamente de la unidad podemos concluir que tenemos errores siste-
máticos presentes en el conjunto de observaciones. Si 𝑍 ≫ 1, podemos tener errores sistemáticos
entre las observaciones o podemos haber subestimado los errores en cada observación, o ambos;
deberemos repasar el experimento viendo las posibles fuentes de error sistemático que han podido
pasar por alto y/o deberemos asignar nuevos errores a cada una de las observaciones. Esto último
será más bien arbitrario dependiendo inevitablemente del juicio y conocimiento del científico de
las condiciones experimentales. El caso contrario, el de 𝑍 ≪ 1 es mucho menos frecuente y se
debería a una sobreestimación de los errores de cada una de las observaciones.
15.5 Bibliografía
Estas notas se han redactado con ayuda de otros trabajos que merecen ser citados:
1. Topping, J. (1972, 4th ed.). Errors of Observation and their Treatment, Science paperbacks,
Chapman & Hall Ed.
2. Fernández Soto, A. (2005). Técnicas experimentales y Análisis de Datos I. Proyecto Docen-
te. OA - UVEG
15.6 Ejercicios
1. El control de calidad de una fábrica que produce bombillas eléctricas comprueba 1000, ele-
gidas al azar, cada mes. Encuentran que el número de horas de encendido de las bombillas
sigue una distribución normal con una vida media de 950 horas de encendido y una des-
viación estándar de 150 horas. Se pide que calculen el número de bombillas que podemos
esperar que tengan: a) menos de 650 horas de encendido, b) entre 800 y 1100 horas y c) entre
1100 y 1250 horas.
2. Los diámetros de las esporas del lycopodium pueden medirse por métodos interferométricos.
Los resultados de uno de estos experimentos son los siguientes:
k * diametro (cm) 14 15 16 17 18 19 20 21 22 23
numero de esporas 1 1 8 24 48 58 35 16 8 1
donde k=5880. Se pide:
Encuentren el diámetro medio de las esporas y su error estándar y
representen los resultados por un histograma y dibujen encima la correspondiente distribu-
ción normal.
1. Los ficheros dssn2002.txt y dssn2007.txt contienen las observaciones del índice 𝐼𝑚 que es
proporcional al número de manchas solares en un determinado día del año 2002 o 2007. Las
columnas de la tabla contienen: 1) el día correspondiente, 2) el día en fracción de año, 3) el
𝐼𝑚 en toda la superficie visible solar, 4) el 𝐼𝑚 solo en el hemisferio norte visible y 5) el 𝐼𝑚
solo en el hemisferio sur visible. Para uno cualquiera de los dos ficheros, se pide que:
hagan un histograma de estas observaciones (rango de 0 a 180),
calculen el promedio y la desviación éstandar anual, para cada mes, para cada uno de los
hemisferios y el global,
ofrezcan, como salida del programa, un fichero con una tabla en la que las columnas sean: 1)
mes, 2) promedio, 3) error, 4) desviación éstandar; de la 5) a la 7) lo mismo que las 2), 3) y
4) pero sólo para el hemisferio norte; de la 8) a la 10) igual que antes pero para el hemisferio
sur. En la últimafila obtengan los valores para todo el año.
1. El experimento MarkI, situado en el Observatorio de El Teide viene observando, desde 1975,
la velocidad de la superficie solar respecto del observatorio por un método de dispersión
resonante usando el efecto Doppler. Una de sus medidas consiste en la velocidad hacia el
rojo debido a la diferencia entre el campo gravitatorio en el Sol y en el Observatorio. En los
ficheros file2008.txt y similares, hay tablas en las que se muestran los resultados obtenidos
en diferentes días del año. Se pide que:
calculen el promedio de la velocidad y su error así como la varianza, utilizando como pesos
estadísticos la inversa del cuadrado del error en cada una de las medidas,
calculen los mismos parámetros pero con igual peso estadístico para todas las medidas,
repitan los cálculos anteriores eliminando del conjunto de medidas aquéllas que difieran de
la media en más de tres veces la desviación estándar.
En el capítulo anterior vimos cómo es posible usar técnicas numéricas para resolver problemas ma-
temáticos complejos, como integrales, sistemas de ecuaciones, etc. En Física e ingeniería es prác-
ticamente más habitual resolver ciertos problemas numéricamente debido a la dificultad o incluso
imposibilidad de hacerlo analíticamente. Sin embargo, a menudo necesitamos resolver problemas
metemáticos de manera analítica necesariamente, en ocasiones pueden ser problemas complejos.
Por ejemplo, es posible que para obtener la primitiva cierta integral recorramos a técnicas de subs-
titución, integración por partes, etc. o bien tengremos que consultar un libro de tablas de integrales.
Afortunadamente, los ordenadores también nos pueden ayudar al cálculo analítico y Python posee
para ello módulo Sympy. Sympy es una librería de cálculo simbólico que permite resolver analí-
ticamente múltiples problemas matemáticos, entre ellos derivadas, integrales, series numéricas o
límites. Veamos algunas posibilidades de esta librería.
Al igual que en Python existen varios tipos de datos numéricos como enteros (int), decimales (float)
o booleanos (bool: True, False, etc.), Sympy posee tres tipos de datos propios: Real, Rational e In-
teger, es decir, números reales, racionales y enteros. Esto quiere decir que Rational(1,2) representa
1/2, Rational(5,2) a 5/2, etc. y no 0.5 o 2.5.
>>> from sympy import *
>>> a = Rational(1,2)
>>> a
1/2
>>> a*2
1
>>> Rational(2)**50/Rational(10)**50
1/88817841970012523233890533447265625
161
Curso de Computación Científica, Publicación 2016-09-15
También existen algunas constantes especiales, como el número e o 𝜋, si embargo éstos se tratan
con símbolos y no tienen un valor númerico determinado. Eso quiere decir que no se puede obtener
un valor numérico de una operación usando el pi de Sympy, como (1+pi), como lo haríamos con
el de Numpy, que es numérico:
>>> pi**2
pi**2
>>> pi.evalf()
3.14159265358979
>>> (pi+exp(1)).evalf()
5.85987448204884
como se ve, sin embargo, se puede usar el método evalf() para evaluar una expresión para tener un
valor en punto flotante (float). Es posible incluso representar matemáticamente el símbolo infinito,
mediante oo:
Para hacer operaciones simbólicas hay que definir explícitamente los símbolos que vamos a usar,
que serán en general las variables y otros elementos de nuestras ecuaciones:
>>> x = Symbol('x')
>>> y = Symbol('y')
>>> x+y+x-y
2*x
>>> (x+y)**2
(x + y)**2
>>> ((x+y)**2).expand()
2*x*y + x**2 + y**2
>>> ((x+y)**2).subs(x, 1)
(1 + y)**2
>>> ((x+y)**2).subs(x, y)
4*y**2
>>> (x+1)/(x-1)
-(1 + x)
---------
1 - x
>>> apart((x+1)/(x-1), x)
2
1 - ------
1 - x
Sympy puede calcular límites usando la función limit() con la siguientesintaxis: limit(función,
variable, punto), lo que calcularía el limite de f(x) cuando variable -> punto::
>>> x = Symbol("x")
>>> limit(sin(x)/x, x, 0)
1
>>> limit(x**x, x, 0)
1
>>> x = Symbol('x')
>>> diff(sin(x), x)
cos(x)
>>> diff(sin(2*x), x)
2*cos(2*x)
>>> diff(tan(x), x)
1 + tan(x)**2
>>> limit((tan(x+y)-tan(x))/y, y, 0)
1 + tan(x)**2
También se pueden calcular derivadas de orden superior indicando el orden de la derivada como
un tercer parámetro opcional de la función diff():
e = 1/(x + y)
s = e.series(x, 0, 5)
pprint(s)
4 3 2
1 x x x x
- + -- - -- + -- - -- + O(x**5)
y 5 4 3 2
y y y y
16.6 Integración
>>> integrate(6*x**5, x)
x**6
>>> integrate(sin(x), x)
-cos(x)
>>> integrate(log(x), x)
-x + x*log(x)
>>> integrate(exp(-x**2)*erf(x), x)
pi**(1/2)*erf(x)**2/4
Algunas integrales definidas complejas es necesario definirlas como objeto Integral() y luego
evaluarlas con el método evalf():
Otra sorprendente utilidad de Sympy es su capacidad para resolver sistemas de ecuaciones fácil-
mente:
Sympy tiene su propio tipo de dato Matriz, independiente del de Numpy/Scipy. Con él se pueden
definir matrices numéricas o simbólicas y operar con ellas:
>>> x = Symbol('x')
>>> y = Symbol('y')
>>> A = Matrix([[1,x], [y,1]])
>>> A
[1, x]
[y, 1]
>>> A**2
[1 + x*y, 2*x]
[ 2*y, 1 + x*y]
Hay que fijarse en que muchas de las funciones anteriores ya existen en Numpy con el mismo
nombre (ones(), eye(), etc.), por lo que si queremos usar ambas debemos importar los paquetes
con otro nombre, Por ejemplo:
Es posible operar entre ellas, salvo que las matrices de Numpy no pueden operar con símbolos,
algo que se puede hacer con Sympy. La selección de elementos de matrices de Sympy se hace de
manera idéntica a los arrays o matrices de Numpy:
>>> # Multiplico la matriz identidad por x
>>> x = Symbol('x')
>>> M = eye(3) * x
>>> M
[x, 0, 0]
[0, x, 0]
[0, 0, x]
>>> eye(3).applyfunc(f)
[1.5, 0, 0]
[ 0, 1.5, 0]
[ 0, 0, 1.5]
16.8 Ejercicios
(︀ 2 )︀
1. Calcular la derivada de 𝑥3 arccos 𝑥
.
2. Calcular la derivada segunda de 𝑒−𝑥 log (𝑥).
3. Calcular los diez primeros elementos de la serie:
∑︁ 1
.
𝑛=0
(1 + 𝑛)5/2
Historial de cambios
171
Curso de Computación Científica, Publicación 2016-09-15