Introducción

Ejecutar este documento en forma dinámica: Binder

¿Qué hace una computadora?

Si bien parece que actualmente las computadoras son capaces de realizar cualquier tarea, incluso aprender cosas que a los humanos nos cuesta mucho esfuerzo (tal como el machine learning, muy en auge en estos días), las computadoras actuales pueden realizar mucho mejor que los humanos solamente dos cosas:

  • Realizar cálculos matemáticos con mucha precisión y gran velocidad
  • Recordar muy bien los resultados (GB a TB de almacenamiento)

Los tipos de cálculo que puede realizar una computadora son de dos tipos: aquellos que están incluidos en un lenguaje, o los que se definen en un programa.

No hay que perder de vista que las computadoras hacen solo lo que les digamos que hagan. Esto es una ventaja en muchos sentidos, pero también son nuestra responsabilidad los resultados que obtenemos.

¿Qué es un programa?

Un programa es una secuencia de instrucciones que especifica cómo realizar un cómputo. Este cómputo puede ser algo matemático, tal como resolver un sistema de ecuaciones o encontrar las raíces de un polinomio, pero también puede ser algo simbólico como buscar y reemplazar texto en un documento o construir un gráfico.

Los detalles de un programa pueden tener aspectos muy diferentes en lenguajes distintos, pero algunas instrucciones básicas aparecen en todos los lenguajes:

  • input: Obtener datos desde el teclado, un archivo, la red o cualquier otro dispositivo.
  • output: Mostrar datos en la pantalla, guardarlos en un archivo, enviarlo a través de la red, etc.
  • matemática: Realizar operaciones matemáticas básicas como suma y multiplicación.
  • ejecución condicional: Evaluar alguna condición y ejecutar el código correspondiente.
  • repetición: Realizar alguna acción repetidamente, por lo general con alguna variación.

Estas instrucciones básicas constituyen prácticamente todos los programas. Podemos pensar que programar es el proceso de descomponer una tarea compleja en tareas mas y mas pequeñas hasta que estas subtareas sean lo suficientemente simples como para ser realizadas con una de estas instrucciones básicas.

Un ejemplo es el cálculo de la raíz cuadrada de un número. Herón de Alejandría fue el primero en documentar una forma de calcular la raíz cuadrada de un número mediante un conjunto de reglas simples. Este programa, para calcular $\sqrt{x}$, puede escribirse como:

  1. Comenzar con una estimación $g$.
  2. Si $g^2$ está lo suficientemente cerca de $x$, detenerse y decir que $g$ es la respuesta.
  3. Si no, obtener una nueva estimación promediando $g$ y $x/g$, es decir $g_n = (g+x/g)/2$.
  4. Asignamos este promedio a $g$ y repetimos el proceso volviendo al punto 2.

Si queremos calcular la raíz cuadrada de 16, comenzando con una estimación $g = 3$, obtenemos:

$g$ $g^2$ $x/g$ $g_n$
3 9 16/3 4.17
4.17 17.36 3.837 4.0035
4.0035 16.0277 3.997 4.000002

El siguiente código muestra una implementación en Python de la receta:

In [6]:
x = 16.0
i = 0
g = 3.0
while abs(g*g - x) > 1.0E-8:
    g = (g + x/g)/2
    i = i + 1
    print(i, g, g*g, x/g, abs(g * g - x))
1 4.166666666666666 17.361111111111107 3.8400000000000007 1.3611111111111072
2 4.003333333333334 16.02667777777778 3.9966694421315565 0.02667777777778113
3 4.000001387732445 16.00001110186149 3.999998612268036 1.110186148878256e-05
4 4.000000000000241 16.000000000001926 3.9999999999997593 1.9255708139098715e-12

Notar que la descripción del método es una secuencia de pasos simples, junto con un control de flujo que especifica cuando debe ejecutarse cada paso. Tal descripción se denomina algoritmo. En este caso es un ejemplo de algoritmo de "prueba y error", basado en el hecho de que es simple verificar cuándo una estimación es suficientemente buena.

Un poco más formalmente, un algoritmo es una lista finita de instrucciones que describe un cómputo que cuando se ejecuta sobre un conjunto de entradas, procederá a través de un conjunto bien definifo de estados y eventualmente producirá una salida.

Esta secuencia de instrucciones se almacena en la memoria de una computadora, y se construye a partir de un conjunto predefinido de instrucciones primitivas:

  1. aritméticas y lógicas
  2. evaluaciones simples
  3. movimiento de datos

Un programa especial (intérprete) ejecuta cada instrucción en orden. Este orden puede alterarse utilizando evaluaciones para cambiar el flujo de control durante una secuencia, y se detiene cuando se cumple una condición de finalización.

Para crear los programas, o secuencias de instrucciones, necesitamos un lenguaje de programación que permita comunicarle a la computadora dichas instrucciones. Alan Turing, en 1936, describió un dispositivo hipotético de cómputo que se ha dado en llamar Máquina Universal de Turing, que tenía una memoria ilimitada en forma de "cinta" en la cual se pueden escribir ceros y unos, y unas instrucciones primitivas muy simples para mover, leer y escribir en la cinta. La tesis de Church-Turing afirma que si una función es computable, una Máquina de Turing puede ser programada para computarla.

La tesis de Church-Turing conduce directamente a la noción de Completitud de Turing. Un lenguaje de programación se dice que es Turing-completo si puede utilizarse para simular una Máquina Universal de Turing. Todos los lenguajes modernos son Turing-completos, como consecuencia, cualquier cosa que pueda ser programada en un lenguaje (por ejemplo, Python), puede programarse en cualquier otro (por ejemplo C/C++). Por supuesto, algunas cosas son más fáciles de programar en un lenguaje que en otro, pero todos los lenguajes son fundamentalmente iguales con respecto de la potencia computacional.

¿Qué es Python?

Python es un lenguaje de programación moderno, de alto nivel, multipropósito y orientado a objetos. Algunas de sus características principales son:

  • Lenguaje claro y simple: El código es muy intuitivo y fácil de aprender. Su mantenimiento escala bien con el tamaño de los proyectos.
  • Lenguaje expresivo: Es posible expresar ideas en pocas líneas de código, lo que implica menos bugs y fácil mantenimiento.
  • Tipado dinámico: No es necesario definir el tipo de las variables, de los argumentos de las funciones ni de los valores devueltos.
  • Administración automática de la memoria: No hay necesidad explícita de asignar ni liberar espacio de memoria para variables o arrays. No hay "goteo de memoria" (memory leaks).
  • Interpretado: No hay necesidad de compilar el código. El intérprete de Python lee y ejecuta directamente el código.

¿Por qué usar Python?

En el ámbito científico/tecnológico y académico las necesidades frecuentes son:

  • Obtener datos (obtenidos de sensores o simulaciones numéricas)
  • Manipular y procesar los datos
  • Visualizar los resultados (para comprender fenómenos o generar reportes y publicaciones)

En este contexto, Python ofrece:

  • Baterías incluídas. Enorme colección de herramientas disponibles para cálculo numérico, generación de gráficos o procesamiento de datos.
  • Fácil de aprender. Nuestra profesión no es la programación pero muchas veces necesitamos levantar datos, ajustar modelos, generar gráficos o hacer una transformada de Fourier en poco tiempo.
  • Comunicación simple. Es muy fácil leer código en Python lo que significa que escribir un programa, mantenerlo y compartirlo no requiere mucho esfuerzo. La sintaxis es simple en comparación con otros lenguajes. Suele decirse que mejor que explicar una idea es escribir directamente un código en Python.
  • Código eficiente. Los módulos numéricos son muy eficientes computacionalmente. Dada la simpleza del lenguaje, que permite escribir código en poco tiempo, Python ofrece la posibilidad de desarrollo rápido y tiempos cortos de ejecución.
  • Universal. Python es un lenguaje utilizado para una gran diversidad de problemas, por lo que no es necesario aprender una nueva herramienta para cada nueva situación.

Los principales módulos numéricos de Python son:

  • Numpy. Herramientas de cálculo numérico con poderosos objetos de arrays numéricos y rutinas para manipularlos.
  • Scipy. Rutinas numéricas de alto nivel: optimización, regresión, interpolación, etc.
  • Matplotlib. Visualización en 2D con gráficos de alta calidad.

También ofrece módulos especializados, entre los que podemos destacar:

  • Mayavi para visualización 3D.
  • Pandas y seaborn para estadística.
  • sympy para cálculo simbólico.
  • scikit-image para procesamiento de imágenes.
  • scikit-learn para machine learning.

Instalación

En cualquier Linux actualizado, los módulos mencionados se encuentran disponibles mediante el sistema de gestión de paquetes correspondientes, por lo que lo más conveniente es usar el administrador de paquetes. En el caso de Windows, la mejor opción es eliminarlo e instalar Linux, pero si es necesario mantener Windows (¡por algo pagaron la licencia!), puede instalarse algunos de los paquetes Anaconda, Canopy, WinPython o Python(x,y).

¿Cómo ejecutar código en Python?

Existen diversas maneras de escribir y ejecutar código en Python. La mas sencilla es escribir el programa en un archivo de texto simple, usualmente denominado script, guardarlo en el disco (por convención con extensión .py, y ejecutarlo desde una consola con:

python3 mi_programa.py

En el archivo mi_programa.py, cada línea se asume que es una instrucción, o una parte de ella, o un comentario (que comienza con # y son usualmente ignoradas por el intérprete de Python). En sistemas UNIX es común definir un path al intérprete en la primera línea del programa:

#!/usr/bin/env python3

Si adicionalmente damos permisos de ejecución al programa, con chmod +x mi_programa.py, éste puede ejecutarse simplemente desde una consola con

./mi_programa.py

De esta forma el intérprete de Python ejecuta línea por línea hasta que finaliza la tarea. Una forma práctica de probar fragmentos cortos de código es ejecutando el intérprete interactivo:

Intérprete interactivo

De esta forma, cada instrucción es ejecutada inmediatamente luego de ingresarla. El intérprete muestra el resultado (si lo hay) y vuelve a dejar el prompt >>> para ingresar un nuevo comando.

También es posible generar y ejecutar código a través de una IDE. Una de las más populares es Spyder:

Spyder

Finalmente, en este curso utilizaremos Jupyter notebooks ya que nos permite intercalar código con documentación y gráficos, compartir estos notebooks vía GitHub, y ejecutarlos en forma remota utilizando Binder. Estos notebooks no son códigos puros de Python, sino que además del código, almacenan la salida junto con texto en formato Markdown. Cuando guardamos este archivo, se envía mediante el navegador a un notebook server, que lo guarda en disco con formato JSON con extensión .ipynb. Jupyter notebooks El notebook server, no el kernel, es el responsable de guardar y leer notebooks, por lo que podemos editarlos aún si no tenemos el kernel para el lenguaje (solo no podremos ejecutar el código). El kernel no sabe nada acerca del documento contenido en el notebook, solo envía las celdas de código a ejecutarse cuando el usuario lo decide.