Este documento interactivo es la primera versión sobre la estructura de un programa en Python
. La idea con el documento es presentar los elementos básicos para la construcción de módulos y funciones. En esta primera parte se presenta el formato de programación utilizado para el desarrollo de las bibliotecas. Como referencia se utiliza el programa bisection.py
.
Como primer elemento, se encuentra el encabezado del programa, este incluye información sobre el autor, versión del programa y descripción del mismo. Para este proposito es posible utilizar el formato de bloque de comentarios o el tipo docstring
. El primero puede presentarse como:
#==================================================================
#file : bisection.py
#description : This is a function that calculates the roots of
# a polinomial function using the bisection
# numerical method.
#inputs: : rf ; raw function, string of the form a*x+b
# a ; left range interval
# b ; right range interval
# n ; number of iterations
# t ; tolerance of calculation
#output: : x ; the aproximated value of x such that f(x)=0
#version : 1.01
#usage : python bisection.py
#dependencies : none
#python_version : 3.3.4
#author : JJ.Cadavid - SFTC - EAFIT University
#date : 24/08/2014.
#===================================================================
El bloque de comentarios, utiliza el indicativo #
de comentario al inicio de cada línea, donde cada una establece un elemento de la información del programa. El formato docstring
puede establecerse línea por línea o de manera multilínea.
"""
file: bisection.py
description: This is a function that calculates the roots of a
polinomial function using the bisection numerical method.
inputs: : rf ; raw function, string of the form a*x+b
a ; left range interval
b ; right range interval
n ; number of iterations
t ; tolerance of calculation
output: : x ; the aproximated value of x such that f(x)=0
version: 1.01
usage: python bisection.py
dependencies: none
python_version: 3.3.4
author: JJ.Cadavid - SFTC - EAFIT University
date: 24/08/2014.
"""
Este formato utiliza las tres comillas dobles para abrir y cerrar el entorno. Usualmente este formato es utilizado para establecer la documentación de la clase o función. Otro posible formato es creando variables de string:
__file__ = "bisection.py"
__description__ = "This is a function that calculates the roots of a polinomial function using the bisection numerical method"
__version__ = "1.01"
__usage__ = "bisection(rf,a,b,n,t)"
__dependencies__ = "none"
__python_version__ = "3.3.4"
__author__ = "JJ.Cadavid - SFTC - EAFIT University"
__date__ = "24/08/2014"
La segunda sección está en el llamado de modulos vía la función "import". Éste primer llamado carga todas las funciones y métodos que están predeterminados en el módulo. Para cargar un módulo partícular se utiliza la secuencia de llamado "from module import function":
#required modules
%matplotlib inline
import parser
import matplotlib
from sys import exit as error
La línea %matplotlib inline
permite integrar los gráficos generados directamente en el notebook
en lugar de visualizarlos en ventanas emergentes. En el caso de tener una función como exit
, se puede agregar una secuencia de llamado diferente agregando as
que permite renombrar el nombre de llamado en el programa. La tercera parte elemental de la estructura es la función principal o el conjunto de métodos dentro de una clase.
def bisection(rf,a,b,n,t):
"""Numerical method that finds roots of first grade polynomials"""
f=parser.expr(rf).compile() #Parse string function into operable code by replacing x string with a number
x=a;
fa=eval(f); #f(a) interval eval
x=b;
fb=eval(f); #f(b) interval eval
if ((fa*fb)>0): #Both images must cross the x-axis, a way to check is with sign
sys.exit('[a,b] Interval does not cross the x-axis');
fp=10;
i=0;
while (abs(fp)>t):
x=a+(b-a)/2; #Aproximation Calculation - changes every iteration
fp=eval(f);
print("f(p)={}".format(fp));
if(fa*fp)>0:
a=x;
else:
b=x;
if(i>n):
print("p={}".format(x));
error('Tolerance not met by set iterations');
i=i+1
print("p={}".format(x));
return(x);
Hasta el momento, se ha definido un método con cinco parámetros, éstos deberán ser introducidos por el usuario. Un segundo programa nos permitirá una secuencia de llamado del método y los parámetros necesarios:
function = "6.1*x + 23.64";
a = -5.0;
b = 0.0;
n = 10.0;
t = 0.05;
root = bisection(function, a, b, n, t);
print(root);
f(p)=8.39 f(p)=0.7650000000000006 f(p)=-3.0474999999999994 f(p)=-1.1412499999999994 f(p)=-0.18812499999999943 f(p)=0.28843750000000057 f(p)=0.05015625000000057 f(p)=-0.06898437499999943 f(p)=-0.009414062499999432 p=-3.876953125 -3.876953125
Una de las recomendaciones durante la asignación de valores en un programa, está en asignar todas las variables antes de la ejecución del método, incluso las variables que no son necesarias en la función. El motivo de ello está en que los objetos en memoria son localizados en un bloque de registros cercanos, agilizando el computo y evitando reservas de memoria en otros bloques.
Para visualizar el resultado del método de bisección, se puede importar el módulo de "matplotlib", una librería especializada en la visualización y representación de datos. En éste caso, la función "pyplot" permite la representación bidimensional de datos
import matplotlib.pyplot as plt
from numpy import linspace
function=lambda x:6.1*x+23.64; # Function of obtained root
x = linspace(a, b, 100); # x domain values defined by [a,b] interval
y=function(x);
font = {'family' : 'serif','color' : 'darkred', 'weight' : 'normal', 'size' : 16} # font used in axes legend
plt.plot(x,y);
plt.axis('auto') # according to list data, axes are adjust in the plot
plt.title('First order polynomial root', fontdict=font);
plt.text(-3, 12, r'$6.1x+23.64$', fontdict=font);
plt.text(root, 0, r'$\circ$ Root', fontdict=font);
plt.xlabel('x position [a.u]', fontdict=font);
plt.ylabel('y position [a.u]', fontdict=font);
plt.grid();
plt.show();
En la primera línea de la anterior sección, se ha utilizado la función "lambda", éste método permite manejar objetos que permitirá evaluar una función. Éste es sumamente útil para el caso de definir una función algebráica que se desea evaluar. De manera similar en la definición de "bisección" se utilizó un análogo, "parser.expr().compile()". A diferencia de "lambda", "parser" permite el manejo partícular de objetos tipo string, mientras que éste último permite el manejo de obtjetos matématicos. Una comparación para evaluar expresiones algebraícas sería:
x = 0.64; # Asumiendo que la variable de la expresión es x.
function_1 = ('1.2*x**3-0.6-x*2+0.85*x-78.9'); # Entrada de función polinómica tipo string
function_1P = parser.expr(function_1).compile();
print("Parser evaluated -> {}".format(eval(function_1P)));
function_2 = lambda x: 1.2*x**3-0.6-x*2+0.85*x-78.9; # Entrada de función polinómica como objeto matemático
function_2P= function_2(x);
print("Lambda evaluated -> {}".format(function_2P));
Parser evaluated -> -79.92142720000001 Lambda evaluated -> -79.92142720000001
Ambos resultados son idénticos, la diferencia está en el tipo de entrada. Para trabajar con strings, suponiendo que el usuario ha ingresado un valor desde "input", "parser" es más adecuado para largas cadenas de carácteres ya que trabaja en parte en bajo nivel, mientras que "lambda" permite evaluar funciones definidas como objeto matemático utilizando el interprete.
Otro de los elementos para mencionar es la diferencia entre "list" y "range". En python 3.0, hay una diferencia respecto al python 2.0 con éstos métodos. Para reducir el tiempo, "range" se ha definido como un objeto de memoria que define la posición, ello implica que no es un objeto que se pueda llamar y visualizar. Principalmente puede ser utilizado como iterador. El otro método "list" permite manejar objetos representables que tienen un elemento asociado que se puede visualizar.
Una función recurrente en la programación de los métodos numéricos es "linspace", que permite generar un listado de elementos numéricamente equidistantes en un intervalo. Por defecto, se utilizan 50 elementos y se puede variar éste parámetro. Un método similar es "arange" que permite generar un listado de elementos un intervalo. Ambos métodos son importados desde "numpy".
from numpy import arange
AR=arange(0.0,11.0,1);
print(AR);
LN=linspace(0.0,10.0,11);
print(LN);
plt.plot(AR,LN,'ro')
plt.show()
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]
Ambos métodos son equivalentes, pero nótese que para un intervalo desde [0,10] "arange" requiere sumarle uno al extremo final, ventaja de arange es que permite ajustar la distancia en que los objetos creados son equidistante. Por su parte "linspace" permite ajustar el total de elementos a utilizar, siendo de utilidad para discriminar un dominio discreto, particularmente útil en el caso de que se desee muestrear una función, evaluando cuándo el dominio está submuestreado o sobremuestreado.
# Cambiar el formato del documento
from IPython.core.display import HTML
def css_styling():
styles = open('../styles/custom_ketch.css', 'r').read()
return HTML(styles)
css_styling()