Apuntes
Apuntes
Apuntes
curso de
PROGRAMACIÓN Y
MÉTODOS
NUMÉRICOS
José Rogan C.
Víctor Muñoz G.
EDICIÓN 2009
Apuntes de un curso de
PROGRAMACIÓN Y MÉTODOS
NUMÉRICOS
Novena edición, revisión 090924-02
José Rogan C.
Vı́ctor Muñoz G.
ii
Agradecimientos:
Xavier Andrade.
Denisse Pastén.
De la promoción del 2004 a: Daniel Asenjo y Max Ramı́rez.
De la promoción del 2005 a: Alejandro Varas y Marı́a Daniela Cornejo.
De la promoción del 2006 a: Nicolás Verschueren y Paulina Chacón,
Sergio Valdivia y Elizabeth Villanueva.
De la promoción del 2007 a: Sebastián Godoy y Carola Cerda,
Rodrigo Pedrasa y Felipe Fuentes.
iv
Índice
I Computación. 1
1. Elementos del sistema operativo unix. 3
1.1. Introducción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. Ingresando al sistema. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1. Terminales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2. Login. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3. Passwords. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.4. Cerrando la sesión. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3. El Proyecto Debian. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4. Archivos y directorios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5. Órdenes básicas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5.1. Órdenes relacionadas con archivos. . . . . . . . . . . . . . . . . . . . . 9
1.5.2. Órdenes relacionadas con directorios. . . . . . . . . . . . . . . . . . . . 10
1.5.3. Visitando archivos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.5.4. Copiando, moviendo y borrando archivos. . . . . . . . . . . . . . . . . 11
1.5.5. Espacio de disco. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5.6. Links. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5.7. Protección de archivos. . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5.8. Filtros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5.9. Otros usuarios y máquinas . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.5.10. Utilitarios, fecha y calculadora . . . . . . . . . . . . . . . . . . . . . . . 24
1.5.11. Diferencias entre sistemas. . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.6. Shells. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.6.1. Variables de entorno. . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.6.2. Redirección. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.6.3. Ejecución de comandos. . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.6.4. Aliases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.6.5. La shell bash. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.6.6. Archivos de script. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.7. Ayuda y documentación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.8. Procesos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.9. Editores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.9.1. El editor vi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
1.9.2. Editores modo emacs. . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
1.10. El sistema X Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
v
vi ÍNDICE
2. Introducción a programación. 45
2.1. ¿Qué es programar? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.2. Lenguajes de programación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.2.1. Código de Máquina binario. . . . . . . . . . . . . . . . . . . . . . . . . 45
2.2.2. Lenguaje de Ensamblador (Assembler). . . . . . . . . . . . . . . . . . . 46
2.2.3. Lenguaje de alto nivel. . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.2.4. Lenguajes interpretados. . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.2.5. Lenguajes especializados. . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.3. Lenguajes naturales y formales. . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.3.1. Lenguajes naturales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.3.2. Lenguajes formales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.4. Desarrollando programas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.5. La interfaz con el usuario. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.6. Sacar los errores de un programa. . . . . . . . . . . . . . . . . . . . . . . . . . 50
5. Gráfica. 157
5.1. Visualización de archivos gráficos. . . . . . . . . . . . . . . . . . . . . . . . . . 157
5.2. Modificando imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
5.3. Conversión entre formatos gráficos. . . . . . . . . . . . . . . . . . . . . . . . . 158
5.4. Captura de pantalla. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
5.5. Creando imágenes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
5.6. Graficando funciones y datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
5.7. Graficando desde nuestros programas. . . . . . . . . . . . . . . . . . . . . . . . 161
10.1. Sistema de bloques acoplados por resortes anclados entre paredes. . . . . . . . 303
10.2. Representación gráfica del método de Newton. . . . . . . . . . . . . . . . . . . 305
xvii
xviii ÍNDICE DE FIGURAS
G.1. Esquema de una tabla en html, utilizando los elementos de una matriz. . . . . 405
G.2. Los 256 colores posibles de desplegar en una página en html, con su respectivo
código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
Parte I
Computación.
1
Capı́tulo 1
1.1. Introducción.
En este capı́tulo se intentará dar los elementos básicos para poder trabajar en un am-
biente unix. Sin pretender cubrir todos los aspectos del mismo, nuestro interés se centra en
entregar las herramientas al lector para que pueda realizar los trabajos del curso bajo este
sistema operativo. Como comentario adicional, conscientemente se ha evitado la traducción
de gran parte de la terminologı́a técnica teniendo en mente que documentación disponible
se encuentre, por lo general, en inglés y nos interesa que el lector sea capaz de reconocer los
términos.
El sistema operativo unix es el más usado en investigación cientı́fica, tiene una larga
historia y muchas de sus ideas y métodos se encuentran presentes en otros sistemas operativos.
Algunas de las caracterı́sticas relevantes del unix moderno son:
3
4 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
1.2.1. Terminales.
Para iniciar una sesión es necesario poder acceder a un terminal. Pueden destacarse dos
tipos de terminales:
Terminal de texto: consta de una pantalla y de un teclado. Como indica su nombre, en
la pantalla sólo es posible imprimir caracteres de texto.
Terminal gráfico: Consta de pantalla gráfica, teclado y mouse. Dicha pantalla suele ser
de alta resolución. En este modo se pueden emplear ventanas que emulan el comporta-
miento de un terminal de texto (xterm o gnome-terminal).
1
Incluyendo el caso en que la máquina es un PC normal corriendo Linux u otra versión de unix.
2
SunOS 4.1.x también se conoce como Solaris 1.
3
También conocido como SunOS 5.x, solaris 2 o Slowaris :-).
4
También conocido como Dec Unix.
5
También conocido como Unixware y Novell-Unix.
6
También conocido como Aches.
1.2. INGRESANDO AL SISTEMA. 5
1.2.2. Login.
El primer paso es encontrar un terminal libre donde aparezca el login prompt del sistema:
hostname login:
Otra persona ha dejado una sesión abierta. En este caso existe la posibilidad de intentar
en otra máquina o bien finalizar la sesión de dicha persona (si ésta no se halla en las
proximidades).
Una vez que se haya superado el paso anterior de encontrar el login prompt se procede
con la introducción del Username al prompt de login y después la contraseña (password)
adecuada.
1.2.3. Passwords.
El password puede ser cualquier secuencia de caracteres a elección. Deben seguirse las
siguientes pautas:
Debe ser fácil de recordar por uno mismo. Si se olvida, deberá pasarse un mal rato
diciéndole al administrador de sistema que uno lo ha olvidado.
Para evitar que alguna persona no deseada obtenga el password y tenga libre acceso a
los archivos de tu cuenta:
Debe cambiarlo si cree que su password es conocido por otras personas, o descubre que
algún intruso7 está usando su cuenta.
El árbol que observamos muestra el tı́pico árbol de directorios en Linux. Pueden haber
pequeñas variaciones en algunos de los nombres de estos directorios dependiendo de la distri-
bución o versión de Linux que se esté usando. Entre los directorios más destacados tenemos:
/etc, aquı́ se encuentran los archivos de configuración de todo los diferentes softwares
de la máquina.
/dev (device) (dispositivo). Aquı́ se guardan los archivos asociados a los dispositivos.
Se usan para acceder los dispositivos fı́sicos del sistema y recursos tales como discos
duros, modems, memoria, mouse, etc. Algunos dispositivos:
• hd: hda1 será el disco duro IDE, primario (a), y la primera partición (1).
• fd: los archivos que empiecen con las letras fd se referirán a los controladores de
las disketteras: fd0 serı́a la primera diskettera, fd1 serı́a la segunda y ası́ sucesi-
vamente.
• ttyS: se usan para acceder a los puertos seriales como por ejemplo ttyS0, que es
el puerto conocido como com1.
• sd: son los dispositivos SCSI y/o SATA. Su uso es muy similar al del hd. También
se usa para denominar a los dispositivos de almacenamiento conectados vı́a USB
(pendrives).
• lp: son los puertos paralelos. lp0 es el puerto conocido como LPT1.
• null: éste es usado como un agujero negro, ya que todo lo que se dirige allı́ desa-
parece.
• tty: hacen referencia a cada una de las consolas virtuales. Como es de suponer,
tty1 será la primera consola virtual, tty2 la segunda, etc.
/usr/local - Zona con las aplicaciones no comunes a todos los sistemas unix, pero no
por ello menos utilizadas.
Los nombres de archivos y directorios pueden usar un máximo de 255 caracteres, cual-
quier combinación de letras y sı́mbolos (el caracter / no se permite).
Los caracteres comodı́n (wildcard ) pueden ser empleados para acceder a un conjunto
de archivos con caracterı́sticas comunes. El signo * puede sustituir cualquier conjunto de
caracteres11 y el signo ? a cualquier caracter individual. Por ejemplo:12
10
Normalmente se acude a la imagen de una carpeta que puede contener informes, documentos o bien otras
carpetas, y ası́ sucesivamente.
11
Incluido el punto ‘.’, unix no es dos.
12
bash$ es el prompt en todos los ejemplos.
10 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
bash$ ls
f2c.1 flexdoc.1 rcmd.1 rptp.1 zforce.1
face.update.1 ftptool.1 rlab.1 rxvt.1 zip.1
faces.1 funzip.1 robot.1 zcat.1 zipinfo.1
flea.1 fvwm.1 rplay.1 zcmp.1 zmore.1
flex.1 rasttoppm.1 rplayd.1 zdiff.1 znew.1
bash$ ls rp*
rplay.1 rplayd.1 rptp.1
bash$ ls *e??
face.update.1 zforce.1 zmore.1
Los archivos cuyo nombre comiencen por . se denominan ocultos, ası́ por ejemplo en el
directorio de partida de un usuario.
bash$ ls -a user
. .alias .fvwmrc .login .xinitrc
.. .cshrc .joverc .profile
.Xdefaults .enviroment .kshrc .tcshrc
Algunos caracteres especiales para el acceso a archivos son:
. Directorio actual
.. Directorio superior en el árbol
~ Directorio $HOME
~user Directorio $HOME del usuario user
-l (Long listing) proporciona un listado extenso, que consta de los permisos13 de cada
archivo, el usuario, el tamaño del archivo, . . . , etc. Adicionalmente la opción -h imprime
los tamaños en un formato fácil de leer (Human readable).
cp archivo1.txt archivo2.txt
-a copia en forma recursiva, no sigue los link simbólicos y preserva los atributos de lo
copiado.
-i (interactive), impide que la copia provoque una pérdida del archivo destino si éste
existe14 .
mv (MoVe)
Mueve un archivo(s) a otro nombre y/o a otro directorio, por ejemplo, el comando para mover
el archivo1.txt al nombre archivo2.txt es:
14
Muchos sistemas tienen esta opción habilitada a través de un alias, para evitar equivocaciones.
12 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
mv archivo1.txt archivo2.txt
1.5.6. Links.
ln (LiNk)
Permite realizar un enlace (link) entre dos archivos o directorios. Un enlace puede ser:
hard link : se puede realizar sólo entre archivos del mismo sistema de archivos. El archivo
enlazado apunta a la zona de disco donde se ubica el archivo original. Por tanto, si se
elimina el archivo original, el enlace sigue teniendo acceso a dicha información. Es el
enlace por omisión.
Un enlace permite el uso de un archivo en otro directorio distinto del original sin necesidad
de copiarlo, con el consiguiente ahorro de espacio. Veamos un ejemplo. Creemos un enlace
clásico en Linux, al directorio existente linux-2.6.12.5 nombrémoslo sencillamente linux.
Se distinguen tres grupos de personas sobre las que se deben especificar permisos:
other: el resto de los usuarios (excepto el usuario y los usuarios que pertenezcan al
grupo)
También se puede emplear all que es la unión de todos los anteriores. Para visualizar las
protecciones de un archivo o directorio se emplea la orden ls -l, cuya salida es de la forma:
-rw-r--r-- ...otra información... nombre
Los 10 primeros caracteres muestran las protecciones de dicho archivo:
• - archivo
• d directorio
• l enlace (link )
• c dispositivo de caracteres (p.e. puerta serial)
• b dispositivo de bloques (p.e. disco duro)
• s socket (conexión de red)
Modo simbólico o literal. Se realiza empleando una cadena (o cadenas separadas por
comas) para especificar los permisos. Esta cadena se compone de los siguientes tres
elementos: who operation permission
◦ - : eliminar permiso.
◦ = : asignar permiso, el resto de permisos de la misma categorı́a se anulan.
• permission: es una combinación de los caracteres:
◦ r : read.
◦ w : write.
◦ x : execute.
◦ s : en ejecución fija el usuario o el grupo.
Por ejemplo:
chmod u+x tarea
Permite la ejecución por parte del usuario17 del archivo tarea.
chmod u=rx, go=r *.txt
Permite la lectura y ejecución del usuario, y sólo la lectura por parte del grupo y el
resto de usuarios.
umask
Esta es una orden intrı́nseca del Shell que permite asignar los permisos que se desea tengan
los archivos y directorios por omisión. El argumento que acompaña a la orden es un número
octal que aplicará una xor sobre los permisos por omisión (rw-rw-rw-) para archivos y
(rwxrwxrwx) para directorios. El valor por omisión de la máscara es 022 que habilita al
usuario para lectura-escritura, al grupo y al resto para lectura. Sin argumentos muestra el
valor de la máscara.
chgrp (CHange GRouP)
Cambia el grupo propietario de una serie de archivos/directorios
chgrp grupo files
El usuario que efectúa esta orden debe pertenecer al grupo mencionado.
chown (CHange OWNer)
Cambia el propietario y el grupo de una serie de archivos/directorios
chown user:group files
La opción -r hace que la orden se efectúe recursivamente.
id
Muestra la identificación del usuario18 , ası́ como el conjunto de grupos a los que el usuario
pertenece.
user@hostname:~$ id
uid=1000(user) gid=1000(group) groups=1000(group),25(floppy),29(audio)
user@hostname:~$
17
Un error muy frecuente es la creación de un archivo de órdenes (script file) y olvidar permitir la ejecución
del mismo.
18
A pesar de que el usuario se identifica por una cadena denominada username, también existe un número
denominado uid que es un identificativo numérico de dicho usuario.
16 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
1.5.8. Filtros.
Existe un conjunto de órdenes en unix que permiten el procesamiento de archivos de texto.
Se denominan filtros (Unix Filters), porque normalmente se trabaja empleando redirección
recibiendo datos por su stdin19 y retornándolos modificados por su stdout20 .
Para facilitar la comprensión de los ejemplos siguientes supondremos que existen tres
archivos llamados mylist.txt, yourlist.txt y tercero.txt que tienen en su interior:
1 190 1 190 11 b
2 280 2 281 33 c
3 370 3 370 222 a
echo
Éste no es propiamente un filtro, pero nos será muy útil más adelante. Despliega sobre la
pantalla un mensaje, sin argumento despliega una lı́nea en blanco. La opción -n elimina el
cambio de lı́nea al final del mensaje.
chao
user@hostname:~$
19
Entrada estándar.
20
Salida estándar.
1.5. ÓRDENES BÁSICAS. 17
seq
Genera una secuencia de números naturales consecutivos.
user@hostname:~$ seq 4 8
4
5
6
7
8
cut
Para un archivo compuesto por columnas de datos, permite escribir sobre la salida cierto
intervalo de columnas. La opción -b N-M permite indicar el intervalo en bytes que se escribirán
en la salida.
user@hostname:~$ cut -b 3-4 mylist.txt
19
28
37
user@hostname:~$
paste
Mezcla lı́neas de distintos archivos. Escribe lı́neas en el stdout pegando secuencialmente las
lı́neas correspondientes de cada uno de los archivo separadas por tab. Ejemplo, supongamos
que tenemos nuestros archivos mylist.txt y yourlist.txt y damos el comando
user@hostname:~$ paste mylist.txt yourlist.txt
1 190 1 190
2 280 2 281
3 370 3 370
user@hostname:~$
sed
Es un editor de flujo. Veamos algunos ejemplos
user@hostname:~$ sed = mylist.txt
1
1 190
2
2 280
3
3 370
user@hostname:~$
Numera las lı́neas.
Sólo muestra la lı́nea 3. El modificador -n suprime la impresión de todas las lı́neas excep-
to aquellas especificadas por p. El modificador -e corre un script, secuencia de comandos.
Separando por coma damos un rango en el número de lı́neas.
Reemplaza todos los 0 del archivo por la letra a. Éste es uno de los usos más comunes.
Busca las lı́neas con la secuencia 2 2 y en ellas reemplaza todos los 0 por la letra a.
Expresiones regulares:
^ Matches al comienzo de la lı́nea
$ Matches al final de la lı́nea, se pone después del caracter a buscar.
. Matches cualquier caracter.
[] Matches con todos los caracteres dentro de los paréntesis
diff
Permite comparar el contenido de dos archivos o directorios
-v Invierte la búsqueda mostrando todas las lı́neas donde no aparece la cadena pedida.
head
Muestra las primeras diez lı́neas de un archivo.
head -30 file Muestra las 30 primeras lı́neas de file.
tail
Muestra las diez últimas lı́neas de un archivo.
tail -30 file Muestra las 30 últimas lı́neas de file.
tail +30 file Muestra desde la lı́nea 30 en adelante de file.
Imprime en pantalla el número respectivo a la última columna de cada fila, que equivalente-
mente es la cantidad de columnas por fila en el archivo archivo.txt.
Supongamos que tenemos el archivo notas.txt que contiene lo siguiente:
El comando
Imprime en pantalla
El comando
hugo 4.7
paco 4.3
luis 4.7
El comando
Crea archivos cuyos nombres correspondan a las palabras de la columna N en cada fila.
Además, cada archivo contiene la fila correspondiente. Por ejemplo si aplicamos este filtro a
nuestro archivo notas.txt por la primera columna, es decir,
tar
Este comando permite la creación/extracción de archivos contenidos en un único archivo
denominado tarfile (o tarball). Este tarfile suele ser luego comprimido con gzip, la
versión de compresión gnu,21 o bien con bzip2.
La acción a realizar viene controlada por el primer argumento:
c (Create) creación
x (eXtract) extracción
r añadir al final
u (Update) añadir aquellos archivos que no se hallen en el tarfile o que hayan sido
modificados con posterioridad a la versión que aparece.
A continuación se muestran algunas de las opciones:
v Verbose (indica qué archivos son agregados a medida que son procesados)
user@hostname:~$ wc mylist.txt
3 6 18 mylist.txt
user@hostname:~$
El archivo tiene 3 lı́neas, 6 palabras, considerando cada número como una palabra i.e. 1 es la
primera palabra y 190 la segunda, y finalmente 18 caracteres. ¿Cuáles son los 18 caracteres?
ping
Verifica si una máquina está conectada a la red y si el camino de Internet hasta la misma
funciona correctamente.
finger
finger user, muestra información22 sobre el usuario user en la máquina local.
finger user@hostname, muestra información sobre un usuario llamado user en una máquina
hostname.
finger @hostname, muestra los usuarios conectados en la máquina hostname.
Este comando suele estar desabilitado en las máquinas actuales.
date
Muestra el dı́a y la hora actual.
Dar el comando
1.000
user@hostname:~$
En unix no existe obligatoriedad respecto a que los archivos llevan extensión. Incluso
pueden tener más de una extensión algo.v01.tar.gz, esto puede complicar a otros
sistemas que usan sólo una extensión de tres caracteres.
Usando el comando tr se puede transformar un archivo con cambios de lı́neas para DOS
en uno para unix. Sabiendo que ^M es ascii 13 decimal, pero 15 en octal:
En Debian, instalando el paquete sysutils, queda instalado el comando dos2unix que tam-
bién lo hace.
1.6. Shells.
El sistema operativo unix soporta varios intérpretes de comandos o shells, que ayudan a
que la interacción con el sistema sea lo más cómoda y amigable posible. La elección de cuál
es la shell más cómoda es algo personal; en este punto sólo indicaremos algunos de los más
populares:
csh : C-SHell, shell con sintaxis como el lenguaje “C”. El archivo de configuración es
.cshrc (en el directorio $HOME).
ksh : Korn-SHell, shell diseñada por David Korn en los Laboratorios AT&T Bell. Es
un intento para una shell interactiva y para uso en script. Su lenguaje de comandos es
un superconjunto de el lenguaje de shell sh.
26 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
bash : Bourne-Again Shell, con lo mejor de sh, ksh y tcsh. El archivo de configuración
es .bash_profile cuando se entra a la cuenta por primera vez, y después el archivo de
configuración es .bashrc siempre en el directorio $HOME. La lı́nea de comando puede
ser editada usando comandos (secuencias de teclas) del editor emacs. Es el shell por
defecto de Linux.
Si queremos cambiar de shell en un momento dado, sólo será necesario que tecleemos el
nombre del mismo y estaremos usando dicho shell. Si queremos usar de forma permanente otro
shell del que tenemos asignado por omisión23 podemos emplear la orden chsh que permite
realizar esta acción.
En los archivos de configuración se encuentran las definiciones de las variables de entorno
(enviroment variables) como camino de búsqueda PATH, los aliases y otras configuraciones
personales. Veamos unos caracteres con especial significado para el Shell:
PATH - El camino de búsqueda, una lista de directorios separados con ‘:’ para buscar
programas.
DISPLAY - Bajo el sistema de X windows, el nombre de máquina y pantalla que está usan-
do. Si esta variable toma el valor :0 el despliegue es local.
1.6.2. Redirección.
Cuando un programa espera que se teclee algo, aquello que el usuario teclea se conoce
como el Standard Input: stdin. Los caracteres que el programa retorna por pantalla es lo que
se conoce como Standard Output: stdout (o Standard Error : stderr27 ). El signo < permite
que un programa reciba el stdin desde un archivo en vez de la interacción con el usuario. Por
ejemplo: mail root < file, invoca el comando mail con argumento (destinatario del men-
saje) root, siendo el contenido del mensaje el contenido del archivo file en vez del texto que
usualmente teclea el usuario. Más a menudo aparece la necesidad de almacenar en un archivo
la salida de un comando. Para ello se emplea el signo >. Por ejemplo, man bash > file,
invoca el comando man con argumento (información deseada) bash pero indicando que la
información debe ser almacenada en el archivo file en vez de ser mostrada por pantalla.
En otras ocasiones uno desea que la salida de un programa sea la entrada de otro. Esto
se logra empleando los denominados pipes, para ello se usa el signo |. Este signo permite que
27
Si estos mensajes son de error.
28 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
>& o &> (sólo csh, tcsh y bash) Redireccionar el stdout y stderr. Con 2> redirec-
cionó sólo el stderr.
En caso contrario:
1.6.4. Aliases.
Para facilitar la entrada de algunas órdenes o realizar operaciones complejas, los shells
interactivos permiten el uso de aliases. La orden alias permite ver qué aliases hay definidos
y también definir nuevos. Es corriente definir el alias rm =‘rm -i’, de esta forma la orden
siempre pide confirmación para borrar un archivo. Si alguna vez quieres usar rm sin alias, sólo
hace falta poner delante el sı́mbolo \, denominado backslash . Por ejemplo \rm elimina los alias
aplicados a rm. Otro ejemplo, bastante frecuente podrı́a ser (debido a la complejidad de la
orden): alias ffind =’find . -name \!*’. Para emplearlo: ffind tema.txt, el resultado
es la búsqueda recursiva a partir del directorio actual de un archivo que se llame tema.txt,
mostrando el camino hasta el mismo.
help
Ayuda interna sobre los comandos del shell.
set
Muestra el valor de todas las variables.
VARIABLE=VALUE
Permite asignar el valor de una variable de entorno. Para que dicha variable sea “heredada”
es necesario emplear: export VARIABLE o bien combinarlas: export VARIABLE=VALUE.
for var in wordlist do comandos done
A la variable var, que puede llamarse de cualquier modo, se le asignan sucesivamente los
valores de la cadena wordlist, y se ejecuta el conjunto de comandos. El contenido de dicha
variable puede ser empleado en los comandos: $var. Ejemplo:
alias
En bash, alias sólo sirve para substitución simple de una cadena por otra. Por ejemplo:
alias ls=’ls -F’. Para crear alias con argumentos se usan funciones, ver la documentación.
unalias name
Elimina un alias asignado.
history
Muestra las últimas órdenes introducidas en el shell. Algunos comandos relacionados con el
Command history son:
!!
Repite la última orden.
!n
Repite la orden n-ésima.
!string
Repite la orden más reciente que empiece por la cadena string.
!?string
Repite la orden más reciente que contenga la cadena string.
∧
str1∧ str2 o !!:s/str1/str2/
30 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
!!:gs/str1/str2/
(global substitute) Repite la última orden reemplazando todas las ocurrencias de la
cadena str1 por la cadena str2.
!$
Es el último argumento de la orden anterior que se haya tecleado.
source file
Ejecuta las órdenes del fichero file en el shell actual.
umask value
Asigna la máscara para los permisos por omisión.
29
Los comandos umask , source , history , unalias y hash , funcionan igual en la
shell tcsh.
#!/bin/bash
variable="/home/yo"
cp $1 /tmp/$2
rm $1
cd $variable
# Hecho por mi
La primera lı́nea declara la shell especı́fica que se quiere usar. En la segunda lı́nea hay una
declaración de una variable interna. La tercera contiene los dos primeros argumentos con que
fue llamado el script. Por ejemplo, si el anterior script está en un archivo llamado ejemplo,
el comando ejemplo file1 file2 asocia $1 a file1 y $2 a file2. La lı́nea 5 hace uso de la
variable interna dentro de un comando. La última lı́nea, que comienza con un # corresponde
a un comentario. Notemos que la primera también es un comentario, pero la combinación #!
en la primera lı́nea fuerza a que se ejecute esa shell.
Esto sólo es una mı́nima pincelada de una herramienta muy poderosa y útil. Los comandos
disponibles en la shell conforman un verdadero lenguaje de programación en sı́, y los scripts
pueden diseñarse para realizar tareas monótonas y complejas. Éste es un tema que le será útil
profundizar.
29
En bash y sh la hash table se va generando dinámicamente a medida que el usuario va empleando las
órdenes. Ası́ el arranque del shell es más rápido, y el uso de orden equivalente hash -r casi nunca hace falta.
1.7. AYUDA Y DOCUMENTACIÓN. 31
1.8. Procesos.
En una máquina existen una multitud de procesos que pueden estar ejecutándose si-
multáneamente. La mayorı́a de ellos no corresponden a ninguna acción realizada por el usua-
rio y no merecen que se les preste mayor atención. Estos procesos corresponden a programas
ejecutados en el arranque del sistema y tienen que ver con el funcionamiento global del
servidor. En general, los programas suelen tener uno de estos dos modos de ejecución:
foreground: Son aquellos procesos que requieren de la interacción y/o atención del
usuario mientras se están ejecutando, o bien en una de sus fases de ejecución (i.e.
introducción de datos). Ası́ por ejemplo, la consulta de una página de manual es un
proceso que debe ejecutarse claramente en foreground.
Sin embargo, esta división que a primera vista pueda parecer tan clara y concisa, a menudo
en la práctica aparece la necesidad de conmutar de un modo al otro, detención de tareas
indeseadas, etc. Ası́ por ejemplo, puede darse el caso de que estemos leyendo una página de
manual y de repente necesitemos ejecutar otra tarea. Un proceso viene caracterizado por:
process number
job number
30
comando & Ejecución de un comando en el background.
ps x Lista todos los procesos que pertenezcan al usuario, incluyendo los que no están
asociados a un terminal.
jobs Lista los procesos que se hayan ejecutado desde el shell actual, mostrando el job
number.
kill (process number) Envı́a una señal31 a un proceso unix. En particular, para
enviar la señal de término a un programa, damos el comando kill -KILL, pero no
hace falta al ser la señal por defecto.
Cuando se intenta abandonar una sesión con algún proceso aún detenido en el background
del shell, se informa de ello con un mensaje del tipo: There are stopped jobs si no importa,
el usuario puede intentar abandonar de nuevo el shell y éste matará los jobs, o puedes utilizar
fg para traerlos al foreground y ahı́ terminar el mismo.
1.9. Editores.
Un editor es un programa que permite crear y/o modificar un archivo. Existen una mul-
titud de editores diferentes, y al igual que ocurre con los shells, cada usuario tiene alguno de
su predilección. Mencionaremos algunos de los más conocidos:
emacs (xemacs) - Editor muy configurable escrito en lenguaje Lisp. Existen muchos
modos para este editor (lector de mail, news, www,. . . ) que lo convierten en un ver-
dadero shell para multitud de usuarios. Las últimas versiones del mismo permiten la
ejecución desde X-windows o terminal indistintamente con el mismo binario. Posee un
tutorial en lı́nea, comando C-H t dentro del editor. El archivo de configuración perso-
nalizada es: $HOME/.emacs.
jove - Basado en Emacs, (Jonathan’s Own Version of Emacs). Posee tutorial en una uti-
lidad asociada: teachjove. El archivo de configuración personalizada es: $HOME/.joverc.
xjed - Versión de jed para el X-windows system. Presenta como ventaja que es capaz
de funcionar en muchos modos: lenguaje C, Fortran, TeX, etc., reconociendo palabras
clave y signos de puntuación, empleando un colorido distinto para ellos. El archivo de
configuración personalizada es el mismo que el de jed.
Dado que los editores del tipo de gedit disponen de menús auto explicativos, daremos a
continuación unas ligeras nociones sólo de vi y emacs.
~
~
~
/tmp/vi.9Xdrxi: new file: line 1
~
~
~
nombre.de.archivo: new file: line 1
Estando en modo órdenes podemos movernos por el archivo que se está editando usando
las flechas hacia la izquierda, derecha, abajo o arriba. Con la tecla 0 nos movemos al comienzo
de la lı́nea y con la tecla $ nos movemos al final de la misma.
Con las teclas w y b nos movemos al comienzo de la siguiente palabra o al de la palabra
anterior respectivamente. Para moverme hacia la pantalla siguiente la combinación de teclas
CTRL F y para volver a la pantalla anterior CTRL B. Para ir hasta el principio del archivo se
presiona la tecla G.
Opciones de comandos.
:e file1.txt Si deseo editar otro archivo al que se le pondrá por nombre file1.txt.
:r! comando Si se quiere ejecutar algún comando del shell y que su salida aparezca en
el archivo que se está editando.
1.10. EL SISTEMA X WINDOWS. 35
.fvwmrc archivo de configuración del fvwm. Ver las páginas del manual de fvwm.
.olwmrc archivo de configuración del olwm. Ver las páginas del manual de olwm.
En caso de que tengas que correr una aplicación de X que no esté disponible en la máquina
que estás usando, eso no representa ningún problema. Las órdenes necesarias son (por ejemplo,
para arrancar un gnome-terminal remoto):
Las opciones XC en el comando ssh corresponden a que exporte el DISPLAY y que com-
prima, respectivamente. La forma antigua
Si todo está previamente configurado, es posible que no haga falta dar el password.
Cuando quieres salir, normalmente puedes encontrar un ı́cono con la opción Log out, en
un menú o panel de la pantalla.
Existen dos modos para determinar cuál es la ventana activa, aquélla que recibe las
entradas de teclado:
33
Las aplicaciones que son conscientes de un uso anormal y están realizadas por programadores inteligentes,
muestran en pantalla la función de cada botón cuando son posibles varias alternativas.
1.12. INTERNET. 37
Focus Follows Mouse: La ventana que contenga al ratón es la que es activa. No usado
por defecto actualmente.
Click To Focus: La ventana seleccionada es la activa. El modo que esté activo depende
de la configuración del Window Manager.
1.12. Internet.
En esta sección denominaremos unix1 a la máquina local (desde donde ejecutamos la
orden) y unix2 a la máquina remota (con la que interaccionamos). Ambos son los hostnames
de las respectivas máquinas. Existen algunos conceptos que previamente debemos comentar:
hostname: es el nombre que tiene asociada la máquina (p.e. macul). A este nombre se le
suelen añadir una serie de sufijos separados por puntos que constituyen el denominado
dominio (p.e. macul.ciencias.uchile.cl). Una máquina por tanto puede tener más
de un nombre reconocido (se habla en este caso de alias). Se denomina resolución
a la identificación entre un hostname y el IP-number correspondiente. La consulta
se realiza inicialmente en el archivo /etc/hosts, donde normalmente se guardan las
identificaciones de las máquinas más comúnmente empleadas. En caso de que no se
logre se accede al servicio DNS (Domain Name Service), que permite la identificación
(resolución) entre un hostname y un IP-number.
mail-address: es el nombre que se emplea para enviar correo electrónico. Este nombre
puede coincidir con el nombre de una máquina, pero se suele definir como un alias, con
objeto de que la dirección no deba de cambiarse si la máquina se estropea o se cambia
por otra.
talk usuario1@unix2, intenta hacer una conexión para hablar con el usuario1 en la
máquina unix2. Existen varias versiones de talk en los diferentes sistemas operativos,
de forma que no siempre es posible establecer una comunicación entre máquinas con
sistemas operativos diferentes.
ftp unix2, (file transfer protocol) aplicación para copiar archivos entre máquinas de
una red. ftp exige un nombre de cuenta y password para la máquina remota. Algunas
de las opciones más empleadas (una vez establecida la conexión) son:
Existen versiones mejoradas de ftp con muchas más posibilidades, por ejemplo, ncftp.
También existen versiones gráficas de clientes ftp donde la elección de archivo, el sentido
de la transferencia y el modo de ésta, se elige con el mouse (p.e. wxftp).
1.12. INTERNET. 39
rlogin -l nombre unix2, (remote login), hace un login a la máquina unix2 como el
usuario nombre por defecto, sin los argumentos -l nombre rlogin usa el nombre de la
cuenta local. Normalmente rlogin pide el password de la cuenta remota, pero con el
uso del archivo .rhosts o /etc/hosts.equiv esto no es siempre necesario.
rsh -l nombre unix2 orden, (remote shell ), ejecuta la orden en la máquina unix2
como usuario nombre. Es necesario que pueda entrar en la máquina remota sin password
para ejecutar una orden remota. Sin especificar orden actúa como rlogin.
Nomenclatura.
Veamos algunos conceptos relacionados con el correo electrónico:
Subject : Es una parte de un mensaje que piden los programas al comienzo y sirve
como tı́tulo para el mensaje.
Cc (Carbon Copy) : Permite el envı́o de copias del mensaje que está siendo editado a
terceras personas.
Forward : Permite reenviar un mensaje completo (con modificaciones o sin ellas) a una
tercera persona. Notando que Forward envı́a también los archivos adjuntos, mientras
que la opción Reply no lo hace.
40 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
In-Box : Es el archivo donde se almacena el correo que todavı́a no ha sido leı́do por el
usuario. Suele estar localizado en /var/spool/mail/user.
Aplicación mail.
Es posiblemente la aplicación más simple. Para la lectura de mail teclear simplemente:
mail y a continuación aparece un ı́ndice con los diferentes mensajes recibidos. Cada mensaje
tiene una lı́nea de identificación con número. Para leer un mensaje basta teclear su número y a
continuación return. Para enviar un mensaje: mail (address) se pregunta por el Subject:
y a continuación se introduce el mensaje. Para acabar se teclea sólo un punto en una lı́nea o
bien Ctr-D. Por último, se pregunta por Cc:. Es posible personalizar el funcionamiento me-
diante el archivo $HOME/.mailrc. Para enviar un archivo de texto a través del correo se suele
emplear la redirección de entrada: mail (address) < file. Si queremos enviar un archivo
binario en forma de attach en el mail, el comando es mpack archivo-binario address.
1.12.4. WWW.
WWW son las siglas de World-Wide Web. Este servicio permite el acceso a información
entrelazada (dispone de un texto donde un término puede conducir a otro texto): hyperlinks.
Los archivos están realizados en un lenguaje denominado html. Para acceder a este servicio
34
Este comando debe usarse con conocimiento pues en caso contrario podrı́a provocar un loop indefinido y
no recibir nunca correo.
1.13. IMPRESIÓN. 41
1.13. Impresión.
Cuando se quiere obtener una copia impresa de un archivo se emplea el comando lpr.
lpr file - Envı́a el archivo file a la cola de impresión por defecto. Si la cola está activa-
da, la impresora lista y ningún trabajo por encima del enviado, nuestro trabajo será procesado
de forma automática.
A menudo existen varias posibles impresoras a las que poder enviar los trabajos. Para
seleccionar una impresora en concreto (en vez de la por defecto) se emplea el modificador:
lpr -Pimpresora, siendo impresora el nombre lógico asignado a esta otra impresora. Para
recibir una lista de las posibles impresoras de un sistema, ası́ como su estado, se puede em-
plear el comando /usr/sbin/lpc status. La lista de impresoras y su configuración también
está disponible en el archivo /etc/printcap.
Otras órdenes para la manipulación de la cola de impresión son:
lpq [-Pimpresora], permite examinar el estado de una determinada cola (para ver la
cantidad de trabajos sin procesar de ésta, por ejemplo).
lprm [-Pimpresora] jobnumber, permite eliminar un trabajo de la cola de impresión.
Uno de los lenguajes de impresión gráfica más extendidos en la actualidad es PostScript.
La extensión de los archivos PostScript empleada es .ps. Un archivo PostScript puede ser
visualizado e impreso mediante los programas: gv, gnome-gv o ghostview. Por ello muchas
de las impresoras actuales sólo admiten la impresión en dicho formato.
En caso de desear imprimir un archivo ascii deberá previamente realizarse la conversión
a PostScript empleando la orden a2ps: a2ps file.txt Esta orden envı́a a la impresora
el archivo ascii file.txt formateado a 2 páginas por hoja. Otro programa que permite
convertir un archivo ascii en postscript es enscript.
Otro tipo de archivos ampliamente difundido y que habitualmente se necesita imprimir
es el conocido como Portable Document Format. Este tipo de archivo poseen una extensión
.pdf y pueden ser visualizados e impresos usando aplicaciones tales como: xpdf, acroread
o gv.
1.14. Compresión.
A menudo necesitamos comprimir un archivo para disminuir su tamaño, o bien crear un
respaldo (backup) de una determinada estructura de directorios. Se comentan a continuación
una serie de comandos que permiten ejecutar dichas acciones.
El compresor compress está relativamente fuera de uso35 pero aún podemos encontrarnos
con archivos comprimidos por él.
35
Este comando no se incluye en la instalación básica. Debemos cargar el paquete ncompress para tenerlo
42 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
zcat file.Z : muestra por el stdout el contenido descomprimido del archivo (sin
destruir el original).
Otra alternativa de compresor mucho más usada es gzip, el compresor de GNU que posee
una mayor razón de compresión que compress. Veamos los comandos:
zless file.gz : muestra por el stdout el contenido descomprimido del archivo pagi-
nado por less.
La extensión empleada en los archivos comprimidos con gzip suele ser .gz, pero a veces
se usa .gzip. Adicionalmente el programa gunzip también puede descomprimir archivos
creados con compress.
La opción con mayor tasa de compresión que gzip es bzip2 y su descompresor bunzip2.
bzcat file.bz2 : muestra por el stdout el contenido descomprimido del archivo. De-
bemos usar un paginador, adicionalmente, para verlo por páginas.
Compresor Tamaño
Sin comprimir 230 Mb
compress 91 Mb
gzip 51 Mb
bzip2 40 Mb
7z 33 Mb
Cuadro 1.1: Tabla de compresión del archivo linux-2.6.18.tar que contiene el kernel 2.6.18
de Linux.
En caso que se desee crear un archivo comprimido con una estructura de directorios debe
ejecutarse la orden:
tar cvzf nombre.tgz directorio o bien tar cvjf nombre.tbz directorio
En el primer caso comprime con gzip y en el segundo con bzip2. Para descomprimir y
restablecer la estructura de directorio almacenada se usan los comandos:
tar xvzf nombre.tgz directorio
si se realizó la compresión con gzip o bien
tar xvjf nombre.tbz directorio
si se realizó la compresión con bzip2.
44 CAPÍTULO 1. ELEMENTOS DEL SISTEMA OPERATIVO UNIX.
Capı́tulo 2
Introducción a programación.
versión 1.0, 30 de Agosto del 2007
En este capı́tulo se intentará dar los elementos básicos de lo que es un lenguaje de pro-
gramación y lo que es programar.
Hacer un programa.
Escribir una secuencia de instrucciones para que un computador haga algo que uno le
pide.
Darle, de alguna forma, una secuencia de pasos lógicos para que un computador los
ejecute con la intención de alcanzar algún objetivo.
Un programa es un archivo que puede ser tan corto como una sola lı́nea de código, o tan
largo como varios millones de lı́neas de código.
45
46 CAPÍTULO 2. INTRODUCCIÓN A PROGRAMACIÓN.
Un programa simple, como Hola mundo, se verı́a en código binario algo ası́ como:
10001010101010011110010
10001011010101000111010
11000101000101010000111
00101010101010010110000
11110010101010101000011
10001010010101010101001
00101010101101010101001
Lenguajes Compilados.
En este caso, otro programa (el compilador) lee el programa fuente, un archivo en ASCII
donde se encuentran el listado de instruciones y lo reescribe en un archivo binario, en lenguaje
de máquina para que la CPU pueda entenderlo. Esto se hace de una sola vez y el programa
2.2. LENGUAJES DE PROGRAMACIÓN. 47
final se guarda en esta nueva forma (un ejecutable). El ejecutable de un programa que es
compilado se estima que será considerablemente más largo que el original, programa fuente.
Algunos de los lenguajes compilados más notables son Fortran, C y C++. Un ejemplo del
programa Hola mundo escrito en C++ es dado a continuación:
//
// Programa Hola Mundo
//
#include <iostream>
int main()
{
cout << "Hola mundo" << endl;
return 0;
}
Sintaxis.
Los lenguajes, tanto naturales como formales, tienen reglas de sintaxis. Por una parte,
están los tokens, que corresponden a los elementos básicos (i.e. letras, palabras, sı́mbolos) del
lenguaje:
Por otro lado, tenemos las estructuras, esto es la manera en que los tokens son organizados:
1. Errores de sintaxis
Usar un token incorrecto o usar token correctos pero estructurarlos en forma in-
correcta.
Caso compilado, no genera el ejecutable.
Caso interpretado, el programa termina abruptamente con un mensaje de error.
3. Errores lógicos
En este capı́tulo se intentará dar los elementos más básicos del lenguaje de programación
Python. No se pretende más que satisfacer las mı́nimas necesidades del curso, sirviendo como
un ayuda de memoria de los tópicos abordados, para futura referencia. Se debe consignar que
no se consideran todas las posibilidades del lenguaje y las explicaciones están reducidas al
mı́nimo.
3.1. Python.
El Lenguaje Python fue inventado alrededor de 1990 por el cientı́fico en computación
holandés Guido van Rossem y su nombre es un tributo a la grupo cómico Monty Python del
cual Guido es admirador. El sitio oficial del lenguage en la web es http://www.python.org.
username@host:~$ python
Python 2.4.4 (#2, Apr 5 2007, 20:11:18)
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
El programa ofrece un prompt (>>>), esperando instrucciones del usuario. Las instruccio-
nes son interpretadas y ejecutadas de inmediato. Esta forma de usar Python tiene la ventaja
de la retroalimentación inmediata; de inmediato el programador sabe si la instrucción está co-
rrecta o incorrecta. Sin embargo, tiene la desventaja de que el código no es guardado, y no
puede por tanto ser reutilizado.
Por otra parte, cuando escribimos un archivo de instrucciones en Python (script), tenemos
la ventaja de que el código sı́ es almacenado, pudiendo ser reutilizado. En este caso las
desventajas son que la retroalimentación no es inmediata y que habitualmente requiere más
debugging para que el código funcione correctamente.
51
52 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
jrogan@manque:~/InProgress/python$ ./archivo.py
Números enteros: Los números, que pertenecen al conjunto Z, es decir, sin decimales.
No pueden ser mayores que un tamaño fijo, en torno a dos billones (2 × 1012 ) en un
sistema de 32 bits usando signo ±. Cuando se dividen entre ellos sólo dan valores
enteros. Por ejemplo: 4/3=1.
Números con punto flotante: Los números, que pertenecen al conjunto R, es decir,
con decimales (un número finito de ellos).
Enteros largos: Números enteros mayores que 2 × 1012 . Los distinguimos por una L
al final del número, por ejemplo: 23434235234L.
Número complejos.
Los números complejos son también soportados en Python. Los números imaginarios puros
son escritos con un sufijo j o J. Los números complejos con parte real no nula son escritos
como real +imagj, o pueden ser creados con la función complex(real,imag). Ejemplos
>>> 1j*1J
(-1+0j)
>>> 1j*complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j)
>>> a=1.5+0.5j
>>> a.real
1.5
>>> a.imag
0.5
Algunas cadenas de caracteres con significado especial empiezan con el caracter \ (String
Backslash Characters).
\\ = Incluya \.
\’ = Apóstrofe simple.
54 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
\n = Lı́nea nueva.
3.2.4. Variables.
Las variable son un nombre, usado dentro del programa, para referirse a un objeto o
valor. Las limitaciones y consideraciones que hay que tener en cuenta para darle nombre a
una variable son:
No puede ser una palabra reservada del lenguaje (i.e. print, and, or, not).
MAL : diy=365
BIEN: days_in_year=365
3.2. LENGUAJE PYTHON. 55
num=8.0
num=pi*3.0**2
O bien podemos hacer asignasiones diferentes valores a diferentes variables en una misma
asignación
>>> a,b=0,1
>>> print a,b
0 1
>>> a,b=b,a+b
>>> print a,b
1 1
>>> a,b,c=0,1,2
>>> print a,b,c
0 1 2
card_value=card1+card2
print card_value
card_value=card1+card2+card3
print card_value
Suma y resta.
56 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
De izquierda a derecha.
Como ejemplo de la importancia de saber el orden de precedencia veamos los siguiente
ejemplos:
2 ∗ (3 − 1) = 4 y 2∗3−1=5
3.2.9. Composición.
Se pueden combinar sentencias simples en una compuesta, a través del operador ”,”:
>>> x = "Elizabeth"
>>> print "Tu nombre es : ", x
>>> Tu nombre es : Elizabeth
En el ejemplo, x fue asignado explı́citamente a una variable, pero naturalmente cualquier
tipo de asignación es posible, por ejemplo:
>>> promedio=(nota+extra_creditos)/posibles
>>> print "Tu promedio es : ", promedio
3.2.10. Comentarios.
Los comentarios son anotaciones que usted escribe para ayudar a explicar lo que está ha-
ciendo en el programa. Los comentarios comienzan con #. Lo escrito después de #, hasta el
final de la lı́nea, es ignorado por el intérprete. Por ejemplo:
dias = 60 #disponibles para el proyecto
Naturalmente, los comentarios no son muy útiles cuando se trabaja interactivamente
con Python, pero sı́ lo son cuando se escribe un script. De este modo se pueden insertar
explicaciones en el código que ayuden a recordar qué hace el programa en cada una de sus
secciones, o explicarlo a terceros. Es buena costumbre de programación que las primeras
lı́neas de un código sean comentarios que incluyan el nombre del programador y una breve
descripción del programa.
3.3. CONDICIONALES. 57
3.3. Condicionales.
Los condicionales son expresiones que puede ser ciertas o falsas. Por ejemplo, ¿el usuario
tipeó la palabra correcta? o ¿El número es mayor que 10? El resultado de la condición decide
que sucederá, por ejemplo, a todos los números mayores que 100 réstele 20, cuando la palabra
ingresada sea la correcta, imprima “¡Bien!”
x != y
x no es igual a y.
x >y
x es mayor que y.
x <y
x es menor que y.
x >= y
x es mayor igual a y.
58 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
x <= y
x es menor igual a y.
x == 125
passwd == "nix"
num >= 0
letter >"L"
num/2 == (num1-num)
num %5 != 0
3.3.2. El if.
A continuación, estudiemos la instrucción if, partamos de la forma general de la instruc-
ción:
if condition:
statements
Primero la palabra clave if, luego la condición condition, que puede ser algo como x<y
o x==y, etc. La lı́nea termina con : requerido por la sintaxis del if. En las lı́neas siguientes
statements, viene las instrucciones a seguir si la condición es cierta. Estas instrucciones
deben ir con sangrı́a (indent).
Un ejemplo de una construcción if simple.
if condition:
statements_1
else:
statements_2
El else debe de estar después de una prueba condicional. Sólo se ejecutará cuando la
condición evaluada en el if sea falsa. Use esta construcción cuando tenga dos conjuntos
diferentes de instrucciones a realizar dependiendo de la condición. Un ejemplo
3.3. CONDICIONALES. 59
if x%2 == 0:
print "el numero es par"
else:
print "el numero es impar"
3.3.4. El if...elif...else.
La forma general de la construcción if...elif...else, a continuación:
if condition_1:
statements_1
elif condition_2:
statements_2
else:
statements_3
Para más de dos opciones use la construcción con elif. elif es la forma acortada de las
palabras else if. Las instrucciones asociadas a la opción else se ejecutarán si todas las otras
fallan. Un ejemplo concreto
if x<0 :
print x," es negativo"
elif x==0 :
print x," es cero"
elif x>0 :
print x," es positivo"
else:
print x," Error, no es un numero"
if pwd=="code" or pwd=="monster":
if y>0 or x<0:
if not(x<y):
if x>y or not(x<0):
if 0<x<100:
if 1000>=x >=0:
De cálculo matemático
log, sen, cos, tan, exp, hypot.
Funciones que generan números al azar, funciones de ingreso, funciones que hacen
cambios sobre un string.
Hay un grupo de funciones que vienen hechas, es decir, listas para usar. Para encon-
trar qué funciones están disponibles tenemos la documentación del Python y un sitio web
http://www.python.org/doc/current/modindex.html
Estas funciones pre-hechas vienen en grupos llamados módulos. Para importar en nuestro
código el módulo apropiado, que contiene la función que nos interesa, usamos el comando
Una vez importado el módulo, cuando queremos llamar a la función para usarla, debemos
dar el comando
modulo name.function(arguments)
3.4. FUNCIONES PRE-HECHAS. 61
import math
math.hypot(8,9)
Si analizamos las lı́neas anteriores de código debemos decir que el módulo que contiene las
funciones matemáticas se llama math y éste incluye la función hypot que devuelve el largo
de la hipotenusa. El sı́mbolo . separa el nombre del módulo del de la función. Por supuesto
hypot es el nombre de la función y () es el lugar para los argumentos. Una función podrı́a
no tener argumentos, pero aún ası́ deben ir los paréntesis, son obligatorios. Los números 8,9
son enviados a la función para que los procese. En el ejemplo, estos números corresponden a
los dos catetos de un triángulo rectángulo.
En las secciones anteriores vimos funciones especializadas en el ingreso de strings y de
números. Nos referimos a input() para números y a raw input() para strings. En este caso,
input e raw input corresponden al nombre de las funciones, y entre los paréntesis se acepta
un string como argumento, el cual es desplegado como prompt cuando se da el comando.
Como vimos, este argumento es opcional en ambas funciones, sin embargo, lo incluyan o no,
siempre se deben poner los paréntesis.
Funciones como input() y raw input() están incorporadas al lenguaje y no necesitamos
importar ningún módulo para usarlas.
long(obj) Convierte un string u otro número a un número entero largo. Sin decimales.
len(s) Retorna el largo de un string u otro tipo de dato (una lista o diccionario).
round(x,n) Retorna el valor del punto flotante x redondeado a n digitos después del
punto decimal. Si n es omitido el valor por defecto es cero.
62 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
replace(string,old,new) reemplaza todas las palabras old en string por las pala-
bras new.
prmoth(year, month) Imprime un calendario para el mes month del año year.
def nombre(argumentos):
comandos
# Definiendo la funcion
def mi_function():
print "Nos gusta mucho la Fisica"
# Usando la funcion
mi_function()
La definición de una función puede ser en cualquier parte del programa con la salvedad
que debe ser antes de que la función misma sea llamada. Una vez definidas las funciónes ellas
se ejecutarán cuando sean llamadas. Cuando enviamos valores a nuestras funciones se crean
las variables nombradas en la definición. Por ejemplo:
Los nombres de las variables de una función sólo serán válidos dentro de la misma función,
esto es lo que se conoce como variables locales). Todas las funciones usan por defecto variables
locales.
64 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
x = 2
print ’x cambiada a’, x
#main
x = 50
func()
print ’El valor de x es’, x
La salida del programa
x es 50
Cambiada a 2
El valor de x es 2
mi_function("azul","rojo")
#!/usr/bin/python
say(’Hola’)
say(’Mundo’, 5)
Hola
MundoMundoMundoMundoMundo
Solamente los parámetros que están en el extremo de la lista de parámetros pueden tener
valores por defecto; es decir, no puedes tener un parámetro con un valor por defecto antes
de uno sin un valor, en el orden de los parámetros declarados, en la lista del parámetro de la
función. Esto se debe a que los valores son asignados a los parámetros por la posición. Por
ejemplo def func(a, b=5) es válido, pero def func(a=5, b) no lo es.
#!/usr/bin/python
func(3, 7)
func(25, c=24)
func(c=50, a=100)
La salida es:
a es 3 y b es 7 y c es 10
a es 25 y b es 5 y c es 24
a es 100 y b es 5 y c es 50
66 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
#!/usr/bin/python
if x > y:
print x, ’es maximo’
else:
print y, ’es maximo’
printMax(3, 5)
print printMax.__doc__
La salida
5 es maximo
Imprime el maximo de 2 numeros.
import math
def raiz(num):
if num<0:
print "Ingrese un numero positivo"
return
print math.sqrt(num)
3.5. FUNCIONES HECHAS EN CASA. 67
Los condicionales como el if son especialmente útil para atrapar y manejar errores. Use
el else para atrapar el error cuando la condición no es satisfecha.
Los if pueden ser anidados. Sea cuidadoso, ya que la anidación puede producir confusión
y deberı́a ser usada con moderación y mesura.
def sumalos(x,y):
new = x+y
return new
# Llamada a la funcion
sum = sumalos(5,6)
3.5.9. Recursión.
Se llama recursión cuando una función se llama a si misma. La recursión permite repetir
el uso de una función incluso dentro de la misma función. Un ejemplo es
def count(x):
x=x+1
print x
count(x)
En este caso la función nunca para, este tipo de recursión es llamada recursión infinita.
Para prevenir este situación creamos un caso base. El caso base es la condición que causará que
la función pare de llamarse a si misma. Un ejemplo
def count(x):
if x<100:
x=x+1
print x
count(x)
else:
return
time.sleep(1)
Para hacer una sección de código reusable, en vez de usar valores constantes use variables.
Primero un ejemplo no generalizado
Utilicemos la instrucción while para hacer una salida ordenada para un programa. El
código de escape del tabulador (\t) en un string permite hacer tabulaciones. Los tabuladores
mantienen los items alineados dando una salida ordenada. Ejemplo, en este caso combinando
la instrucción while y el código de escape del tabulador haremos una tabla:
palabra = "computador"
letra = palabra[0]
Para acceder un conjunto de caracteres dentro de un string lo podemos hacer como sigue:
>>> a="hola"
>>> a[0]
’h’
>>> a[-1]
’a’
>>> a[-2]
’l’
>>> a[-3]
’o’
>>> a[-4]
’h’
>>> a[-5]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: string index out of range
palabra = "computador"
largo = len(palabra)
palabra = "computador"
indice = 0
while indice < len(palabra):
letra = palabra[indice]
print letra
indice=indice +1
70 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
De acuerdo a Python, todas las letras minúsculas. son mayores que las letras mayúsculas
Ası́ .a”>”Z”.
Una buena idea es convertir todos los strings a mayúscula o minúscula, según sea el caso,
antes de hacer comparaciones. Recordemos que el módulo string contiene varias funciones
útiles incluyendo: lower(string), upper(string) y replace(string,string,string). Re-
vise la documentación.
palabra = "computador"
for letra in palabra:
print letra
Notemos que hemos creado la variable letra cuando iniciamos el ciclo for. A continuación,
un ejemplo más completo del ciclo for:
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# Programa que cuenta vocales
import string
Notemos la segunda lı́nea de este programa que nos permite ingresar e imprimir strings
con caracteres acentuados y caracteres especiales.
3.9. LISTAS. 71
for n in range(1,10):
c=d*d
if c > 50:
print n, "al cuadrado es ",c," > 50"
print "PARO"
break
else:
print n,"su cuadrado es ",c
for n in range(2,10):
for x in range(2,n):
if n % x ==0:
print n, "igual a", x,"*", n/x
break
else:
print n,"es un numero primo"
3.9. Listas.
Una lista es un conjunto ordenado de elementos. Las listas están encerradas entre parénte-
sis [ ]. Cada item en una lista está separado por una coma. Veamos ejemplos de listas
Un elemento de una lista puede ser otra lista. Una lista dentro de otra lista es llamada
lista anidada. A continuación un ejemplo de listas anidadas
Para accesar un item en una lista anidada hay que proveer dos ı́ndices. Ejemplo
3.9.2. Mutabilidad.
A diferencia de los strings las listas son mutables, lo que significa que se pueden cambiar.
Ejemplo
Notemos que las operaciones que modifican la lista la modificarán de manera tal que si
multiples variables apuntan a la misma lista todas las variables cambiarán al mismo tiempo.
>>> L=[]
>>> M=L
>>> # modifica ambas listas
>>> L.append(obj)
Para crear una lista separada se puede usar el “rebanado” o la función list para crear una
copia.
>>> L=[]
>>> M=L[:] # creando una copia
>>> N=list(L) # crea otra copia
>>> # modifica solo a L
>>> L.append(obj)
Para remover items desde una lista sin usar el ı́ndice de posición, use el siguiente coman-
do nombre_lista.remove("item") que borra la primera aparición del item en la lista. Un
ejemplo interactivo
La palabra clave not puede ser combinada con in para hacer la pregunta contraria, es decir,
si un item no está en un lista. Veamos un ejemplo
3.10. Tuplas.
Una tupla es una lista inmutable. Una tupla no puede modificarse de ningún modo después
de su creación.
>>> t = ("a", "b", 8)
>>> t[0]
’a’
Una tupla se define del mismo modo que una lista, salvo que el conjunto se encierra entre
paréntesis ( ), en lugar de entre corchetes [ ]. Los elementos de la tupla tienen un orden
definido, como los de la lista. Las tuplas tienen primer ı́ndice 0, como las listas, de modo que
el primer elemento de una tupla t, no vacı́a es siempre t[0]. Los ı́ndices negativos cuentan
desde el final de la tupla, como en las listas. Las porciones funcionan como en las listas.
Advierta que al extraer una porción de una lista, se obtiene una nueva lista; al extraerla
de una tupla, se obtiene una nueva tupla. No hay métodos asociados a tuplas ( tal como
append() en una lista).
No pueden añadirse elementos a una tupla, no pueden eliminarse elementos de una tupla,
no pueden buscarse elementos en una tupla, se puede usar in para ver si un elemento existe
en la tupla.
Las tuplas son más rápidas que las listas. Si está definiendo un conjunto constante de
valores y todo lo que va ha hacer con él es recorrerlo, utilice una tupla en lugar de una
lista. Una tupla puede utilizarse como clave en un diccionario, pero las listas no. Las tuplas
pueden convertirse en listas y vice versa. La función incorporada tuple(lista) toma una
lista y devuelve una tupla con los mismos elementos. La función list(tupla) toma una
tupla y devuelve una lista.
for i in sys.argv:
print i
print sys.argv[0]
Si ejecutamos el programa con la lı́nea de comando
jrogan@huelen:~$ ./main.py h -l --mas xvzf
La salida será
./main.py
h
-l
--mas
xvzf
./main.py
Otro ejemplo, un programa que suma dos números desde la lı́nea de comando,
#!/usr/bin/python
import sys
if (len(sys.argv)>2):
n1=float(sys.argv[1])
n2=float(sys.argv[2])
print n1+n2
else:
pass
Si ejecutamos el programa con la lı́nea de comando
jrogan@huelen:~$ ./suma.py 1.2 3.5
La salida será
4.7
Si se llama el programa con menos argumentos, el programa no hará nada.
def raiz(x):
Debido a la naturaleza de la función raı́z cuadrada, raiz() no tendrı́a sentido, y por tanto
no corresponde declararla con un valor default.
Ahora debemos pensar en cómo calcular la raı́z cuadrada. Usando una variante del método
de Newton-Raphson, se obtiene que la secuencia
1 a
xn+1 = xn +
2 xn
√
converge a a cuando n → ∞. Por tanto, podemos calcular la raı́z cuadrada con aproxima-
ciones sucesivas. El cálculo terminará en el paso N , cuando la diferencia entre el cuadrado
de la aproximación actual, xN , y el valor de a, sea menor que un cierto número pequeño:
| x2N − a | < ǫ ≪ 1. El valor de ǫ determinará la precisión de nuestro cálculo. Un ejemplo de
código lo encontramos a continuación:
#!/usr/bin/python
#
# Programa que calcula la raiz cuadrada
import math
def raiz(a):
x =a/2.0 # para comenzar
dx, epsilon = 1e3, 1e-8;
while (math.fabs(dx)>epsilon):
x = (x + a/x)/2;
dx = x*x - a;
print "x = ", x, ", precision = ", dx
return x
# main
función matemática fabs, disponible en el módulo math incluido al comienzo del programa.
Observar que inicialmente dx=1e3, esto es un valor muy grande; esto permite que la condición
del while sea siempre verdadera, y el ciclo se ejecuta al menos una vez.
Dentro del ciclo, se calcula la nueva aproximación, y se envı́a a pantalla un mensaje con
la aproximación actual y la precisión alcanzada (dada por dx). Eventualmente, cuando la
aproximación es suficientemente buena, se sale del ciclo y la función devuelve al main el valor
de x actual, que es la última aproximación calculada.
3.12.2. Factorial.
Otro ejemplo útil es el cálculo del factorial, definido para números naturales:
n! = n · (n − 1) · · · 2 · 1 , 0! ≡ 1 .
Una estrategia es utilizar un ciclo for, determinado por una variable entera i, que va
desde 1 a n, guardando los resultados en una variable auxiliar que contiene el producto de
todos los números naturales desde 1 hasta i:
#!/usr/bin/python
#
# Programa que calcula el factorial
def factorial(i):
f = 1
for j in range(2,i+1):
f = f*j
return f
# main
Observar que la variable auxiliar f, que contiene el producto de los primeros i números
naturales, debe ser inicializada a 1. Si se inicializara a 0, factorial(n) serı́a 0 para todo n.
Esta funcion responde correctamente en el caso n = 0, pero retorna el valor 1 para todos
los enteros negativos.
Otra estrategia para calcular el factorial es hacer uso de su propiedad recursiva
n! = n × (n − 1)! 1! = 0! ≡ 1
else:
return n*fact(n-1)
#main
i=input("Ingrese un natural :")
print "El factorial de",i," es ",fact(i)
salida= open("datos.txt","w")
salidaAppend= open("programa.log","a")
entrada= open("archivo.txt","r")
entrada.readlines() Lee el archivo completo, cada lı́nea llega a ser un item tipo
string en una lista.
3.13. TRABAJANDO CON ARCHIVOS. 81
salida.writelines(list) Escribe todos los items tipo string en la lista list. Cada
elemento en la lista estará en la misma lı́nea a menos que un elemento contenga una
caracter de newline
Si queremos usar la instruccion print para escribir sobre un archivo abierto, digamos
salida, podemos usar la instrucción
salida = open("resultados.txt", "w")
print >> salida, datos # Imprime datos en el archivo resultados.txt
print >> salida # Imprime una linea en blanco en el archivo resultados.txt
salida.close()
nueva = []
3.14. Excepciones.
Las palabras reservadas try y except pueden ser usadas para atrapar errores de ejecución
en el código. Primero en el bloque try se ejecutan un grupo de instrucciones o alguna función.
Si estas fallan o devuelven un error, el bloque de comandos encabezados por except se
ejecutará. Lo anterior, puede ser usado para manejar programas que pueden fallar bajo
alguna circunstancia de una forma muy elegante.
3.15. Diccionarios.
Las listas son colecciones de items (strings, números o incluso otras listas). Cada item en
la lista tiene un ı́ndice asignado.
Los diccionarios son mutables. Uno no tiene que reasignar el diccionario para hacer cam-
bios en él.
Los diccionarios son útiles cada vez que usted tiene items que desea ligar juntos. También
son útiles haciendo sustituciones (reemplace todos los x por y). Almacenando resultados
para una inspección rápida. Haciendo menús para programas. Creando mini bases de datos
de información.
# Programa
num1 = input("Entre el primer numero: ")
num2 = input("Entre el segundo numero: ")
menu = {’S’:add, ’M’:mult}
print "[S] para sumar, [M] para multiplicar: "
choice = string.upper(raw_input())
menu[choice] (num1,num2)
Dentro de un script usamos para importar mi módulos mis_funciones que está salvado en
mi directorio de módulos
import sys
sys.path.append(’/home/usuario/mis_modulos’)
import mis_funciones
def mi_funcion(x,y):
"""mi_funcion( primer nombre, ultimo nombre) """
Use al principio y al final triple comilla. El texto en triple comilla deberı́a explicar lo que
la función, clase o módulo hace. Estas lı́neas de documentación las podré ver, en el modo
interactivo, si da el comando help(module.mi_funcion).
86 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
# Sean f(x,y) una funcion y C una clase con un metodo m(x) del modulo stuff
import stuff
print stuff.f(1,2)
print stuff.C(1).m(2)
# Sean f(x,y) una funcion y C una clase con un metodo m(x) del modulo stuff
from stuff import f, C
print f(1,2)
print C(1).m(2)
# Sean f(x,y) una funcion y C una clase con un metodo m(x) del modulo stuff
from stuff import *
print f(1,2)
print C(1).m(2)
# Sean f(x,y) una funcion y C una clase con un metodo m(x) del modulo stuff
import stuff as st
print st.f(1,2)
print st.C(1).m(2)
if __name__ == ’__main__’:
funcion_a_correr()
El módulo shelve almacena estructuras de datos de Python pero permite más de una
estructura de datos y puede ser indexado por una llave.
class MiClase:
def hola(self):
print "Bienvenido. "
def math(self,v1,v2):
print v1+v2
class MiClase:
def hola(self):
print "Bienvenido. "
def math(self,v1,v2):
print v1+v2
fred = MiClase()
fred.hola()
fred.math(2,4)
Creamos una instancia de una clase al asignarla a una variable, fred = MiClase(). Para
aplicar una función o método a una nueva instancia debemos especificar en forma completa
la instancia y el método fred.hola(). Si el método toma argumentos debemos estar seguro
de incluir todos los valores necesitados, por ejemplo fred.math(2,4).
Las variables dentro de una clase deben ser accesadas poniendo el prefijo a su nombre la
palabra self.
La función __init__ es la que correrá si una nueva instancia de la clase es creada. Esta
función es especial y se conoce como el constructor de la clase.
Usando la clase LibretaNotas
eli.sumanota(6.2)
mario.sumanota(6.1)
carmen.sumanota(6.3)
eli.sumanota(6.8)
mario.sumanota(6.7)
carmen.sumanota(6.6)
eli.promedio()
mario.promedio()
carmen.promedio()
Cada nueva instancia de la clase LibretaNotas debe tener un nombre y una primera nota
porque ası́ lo requiere la función constructor __init__. Notemos que cada instancia tiene su
propio promedio.
class Dinero:
def __init__(self, amount = 0) :
self.amount=amount
def print(self):
print "Tienes", self.amount, "de dinero"
# Llamadas posibles
mi_dinero = Dinero(100)
tu_dinero = Dinero()
90 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
mi_dinero.print()
tu_dinero.print()
class Cuentas:
alumnos=[]
def __init__(self,nombre):
self.nombre=nombre
Cuentas.alumnos.append(self.nombre)
def __del__(self)
Cuentas.alumnos.remove(self.nombre)
Notese que la lista de alumnos es siempre llamada por su nombre completo Cuentas.alumnos.
Para acceder a la lista fuera de la clase, use su nombre completo Cuentas.alumnos.
class Vec2d:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def module(self):
return sqrt(self.x**2+self.y**2)
def __repr__(self):
return "(%11.5f,%11.5f)" % (self.x,self.y)
def __add__(self,newvec):
return Vec2d(self.x+newvec.x,self.y+newvec.y)
def __sub__(self,newvec):
return Vec2d(self.x-newvec.x,self.y-newvec.y)
def __mul__(self,newvec):
return self.x*newvec.x+self.y*newvec.y
#!/usr/bin/env python
a=Vec2d(1.3278,2.67)
b=Vec2d(3.1,4.2)
print a, b
print a+b
print a-b
print a*b
print a.module(),b.module()
Donde cada elemento esta determinado por una Funcion, la variable master es la que
determina a la ventana particular (como en el ejemplo anterior lo era vent), y todas las
opciones siguientes tienen un valor por defecto que podemos cambiar solo enunciándolas y
dandole el nuevo valor. Algunos de los elementos que podemos agregar son:
1. Etiquetas
Se enuncian llamando a la función Label(...), cuyas opciones son:
2. Botones
Se enuncian llamando a la función Button(...), tiene las mismas opciones que la
etiqueta y además se le agragan las siguientes:
relief : Es el relieve que tendra el boton, esta variable puede tomar los valores FLAT,
RAISED, SUNKEN, GROOVE y RIDGE, el valor por defecto es RAISED, estéti-
camente se ven:
cursor : Es el nombre del cursor que aparece cuando pasamos el mouse sobre el boton,
algunos de los valores que puede tomar esta opción son: arrow, mouse, pencil,
question arrow, circle, dot, star, fleur, hand1, heart, xterm, etc.
bitmap : Los bitmaps son imágenes prehechas que vienen incorporadas en Tkinter,
algunos de los valores que puede tomar ésta variable son: error, hourglass,
info, questhead, question, warning, etc, estéticamente se ven:
3. Input
Es equivalente a la opción raw input, y se enuncia llamando a la función Entry(...),
tiene disponibles las opciones witdh, bg, fg, cursor, entre otros. Se ve de la forma:
4. Boton de Checkeo
Se llama con la función Checkbutton(...) y tiene las mismas opciones que Button(),
se ve ası́:
5. Menú
Para crear un menú debemos generar una variable similar al master que determinaba
la ventana, pero esta vez para determinar al menú, la forma de hacer esto es con la
función Menu(...), cuyo argumento debe ser la variable que representa a la ventana.
Luego, con esta variable puedo crear un menú, veamos el siguiente ejemplo:
3.20. EL MÓDULO TKINTER 95
vent=Tk()
.
.
.
men=Menu(vent)
archivo=Menu(men, tearoff=0)
men.add_cascade(label="Archivo", men=archivo)
archivo.add_command(label="Abrir", command=A)
archivo.add_command(label="Nuevo", command=B)
archivo.add_command(label="Salir", command=C)
editar=Menu(men, tearoff=0)
men.add_cascade(label="Editar", men=editar)
editar.add_command(label="Copiar", command=D)
editar.add_command(label="Cortar", command=E)
vent.config(menu=men)
.
.
.
vent.mainloop()
El código anterior generará un menú llamado Archivo, que contiene las opciones Abrir
(cuya funcion es A()), Nuevo (cuya función es B()) y Salir (con función C()), y otro
menú llamado Editar, de similares caracterı́sticas.
vent=Tk()
.
.
.
A=Label(vent, text="hola")
B=Button(vent, text="aceptar", command=F)
A.grid(row=1, rowspan=2, column=1, columnspan=3)
B.grid(row=3, column=1)
.
96 CAPÍTULO 3. UNA BREVE INTRODUCCIÓN A PYTHON.
.
.
vent.mainloop()
El código anterior pondrá el texto “hola” desde la primera fila hasta la segunda y desde
la primera columna hasta la tercera, y debajo, en la fila 3 y la columna 1 pondrá un botón
que dice “aceptar” y ejecuta la función F.
master.atributo(valor) ó master.atributo(opcion1=valor1,...)
Geometrı́a : Se refiere a las dimensiones de la ventana (la unidad que se ocupa es 0,023
cm), se debe dar la orden:
master.geometry("nxm"), donde n=ancho y m=alto.
#IMPORTO EL PAQUETE
from Tkinter import *
#DEFINO EL MASTER
vent=Tk()
vent.geometry("350x120")
vent.configure(cursor="spider")
#EJECUTO EL CODIGO
vent.mainloop()
En este capı́tulo se intentará dar los elementos básicos del lenguaje de programación
C++. No se pretende más que satisfacer las mı́nimas necesidades del curso, sirviendo como
un ayuda de memoria de los tópicos abordados, para futura referencia. Se debe consignar que
no se consideran todas las posibilidades del lenguaje y las explicaciones están reducidas al
mı́nimo.
int main()
{
cout << "Hola." << endl;
return 0 ;
}
Las tres primeras lı́neas corresponden a comentarios, todo lo que está a la derecha de
los caracteres // son comentarios y no serán considerados en la compilación. En la lı́nea
siguiente se incluye un archivo de cabecera, o header, con la instrucción de preprocesador
#include. El nombre del archivo se puede escribir como <nombre> o bien "nombre.h". En
el primer caso el archivo nombre será buscado en el path por defecto para los include,
tı́picamente /usr/include o /usr/include/c++/3.x/ en el caso de headers propios de C++;
en el segundo caso la búsqueda se hace en el directorio local. También podrı́amos incluir
un path completo cuando se ocupan las comillas. En nuestro ejemplo se incluye el archivo
iostream, en el cual se hacen las definiciones adecuadas para el manejo de la entrada y salida
en C++. Este archivo es necesario para enviar luego un mensaje a pantalla.
99
100 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
La función int main es donde comienza a ejecutarse el programa; siempre debe haber una
función main en nuestro programa. Debido a imposiciones del sistema operativo la función
main devuelve un entero y por tanto debe ser declarada int. Los paréntesis vacı́os () indican
que el main no tiene argumentos de entrada (más adelante se verá que puede tenerlos). Lo
que está encerrado entre llaves {} corresponde al cuerpo de la función main. Cada una de
las lı́neas termina con el carácter ;. El identificador predefinido cout representa la salida a
pantalla. El operador << permite que lo que está a su derecha se le dé salida por el dispositivo
que está a su izquierda, en este caso cout. Si se quiere enviar más de un objeto al dispositivo
que está al inicio de la lı́nea agregamos otro operador <<, y en este caso lo que está a la
derecha del operador se agregará a lo que está a la izquierda y todo junto será enviado al
dispositivo. En nuestro caso se ha enviado endl, un objeto predefinido en el archivo iostream
que corresponde a un cambio de lı́nea, el cual será agregado al final del mensaje. La lı́nea
final contiene la instrucción de retorno del entero cero, return 0.
Si escribimos nuestro primer programa en el editor xemacs con el nombre de primero.cc
las instrucciones para editarlo, compilarlo y correrlo serán:
//
// Segunda version incluye funcion adicional
//
#include <iostream>
using namespace std;
4.1. ESTRUCTURA BÁSICA DE UN PROGRAMA EN C++. 101
void PrintHola()
{
cout << "Hola." << endl;
}
int main()
{
PrintHola();
return 0;
}
La función debe estar definida antes de que sea ocupada, por eso va primero en el código
fuente. Como ya se dijo antes, la ejecución del programa comienza en la función main a
pesar de que no está primera en el código fuente. Los paréntesis vacı́os indican que la función
PrintHola no tiene argumentos y la palabra delante del nombre de la función indica el tipo
de dato que devuelve. En nuestro caso la palabra void indica que no devuelve nada a la
función main.
Una alternativa al código anterior es la siguiente:
#include <iostream>
using namespace std;
void PrintHola();
int main()
{
PrintHola();
return 0 ;
}
void PrintHola()
{
cout << "Hola." << endl;
}
– Longitud arbitraria.
int i;
i=10;
Esta necesidad de declarar cada variable a usar se relaciona con la caracterı́stica de C++ de
ser fuertemente “tipeado”2 . Algunos de los errores más habituales en programación se deben
al intento de asignar a variables valores que no corresponden a sus tipos originales. Si bien
esto puede no ser muy grave en ciertos contextos, a medida que los programas se vuelven
más complejos puede convertirse en un verdadero problema. El compilador de C++ es capaz
de detectar los usos indebidos de las variables pues conoce sus tipos, y de este modo nuestro
código se vuelve más seguro.
Es posible reunir las acciones de declaración e inicialización en una misma lı́nea:
int i=10;
1
A esta tabla hay que agregar algunas palabras adicionales, presentes en versiones más recientes de C++,
como namespace y using
2
Una traducción libre del término inglés strongly typed.
4.1. ESTRUCTURA BÁSICA DE UN PROGRAMA EN C++. 103
o declarar más de una variable del mismo tipo simultáneamente, e inicializar algunas en la
misma lı́nea:
int r1, r2, r3 = 10;
A veces se requiere que una variable no varı́e una vez que se le asigna un valor. Por ejemplo,
podrı́amos necesitar definir el valor de π = 3.14159..., y naturalmente no nos gustarı́a que,
por un descuido, a esa variable se le asignara otro valor en alguna parte del programa. Para
asegurarnos de que ello no ocurra, basta agregar el modificador const a la variable:
const float pi = 3.14159;
Para números reales se puede usar la notación exponencial. Por ejemplo, 1.5e-3 repre-
senta el número 1.5 × 10−3 .
Una variable puede ser declarada sólo una vez, pero naturalmente se le pueden asignar
valores en un número arbitrario de ocasiones.
Los 15 tipos de datos aritméticos fundamentales disponibles son3 :
Booleanos y caracteres
bool Booleanas true o false.
char Caracteres de 0 a 255 o -128 a 127, usa 8 bits,
signed char Caracteres −128 a 127,
unsigned char Caracteres de 0 a 255.
Enteros
short Enteros entre −215 = −32768 y 215 − 1 = 32767
unsigned short Enteros entre 0 y 65535
int Enteros entre −231 = −2147483648 y 231 − 1 = 2147483647
unsigned int Enteros entre 0 y 232 = 4294967295
long (32 bits) Entero entre −2147483648 y 2147483647,
long (64 bits) Entero entre −9223372036854775808 y 9223372036854775807,
unsigned long (32 bits) Enteros entre 0 y 4294967295,
unsigned long (64 bits) Enteros entre 0 y 18446744073709551615,
long long Enteros entre −9223372036854775808 y 9223372036854775807
unsigned long long Enteros entre 0 y 18446744073709551615
Float
float Reales x tal que 1.17549435 × 10−38 ≤ |x| ≤ 3.40282347 × 1038 ,
(Precisión de 7 dı́gitos decimales.)
double Reales x tal que
2.2250738585072014 × 10−308 ≤ |x| ≤ 1.7976931348623157 × 10308 ,
(Precisión de 15 dı́gitos decimales.)
long double Reales x tal que
3.36210314311209350626 × 10−4932 ≤ |x| ≤ 1.18973149535723176502 × 104932 ,
(Precisión de 18 dı́gitos decimales.)
3
Los valores de los rangos indicados son simplemente representativos y dependen de la máquina utilizada
(32 bits o 64 bits). Además, estos valores no corresponden exactamente a las versiones más recientes de C++.
104 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
int main()
{
int i, j ;
cout << "Ingrese dos numeros enteros: " ;
cin >> i >> j ;
cout << "Los dos numeros ingresados fueron: " << i <<" "<< j << endl ;
return 0;
}
4.1. ESTRUCTURA BÁSICA DE UN PROGRAMA EN C++. 105
+ - * /
&& || !
4.1.8. Asignaciones.
a) Asignación simple. Podemos asignar a una variable un valor explı́cito, o el valor de otra
variable:
i = 1;
j = k;
x = x + 2;
Si x fuera una variable matemática normal, esta expresión no tendrı́a sentido. Esta
expresión es posible porque el compilador interpreta a x de modo distinto a cada lado
del signo igual: a la derecha del signo igual se usa el valor contenido en la variable x
(por ejemplo, 10); a la izquierda del signo igual se usa la dirección de memoria en la
cual está alojada la variable x. De este modo, la asignación anterior tiene el efecto de
colocar en la dirección de memoria que contiene a x, el valor que tiene x más 2. En
general, todas las variables tienen un rvalue y un lvalue: el primero es el valor usado a
la derecha (right) del signo igual en una asignación, y el segundo es el valor usado a la
izquierda (left), es decir, su dirección de memoria.
b) Asignación compuesta.
La expresión x = x + 2 se puede reemplazar por x += 2.
Existen los operadores += -= *= /=
106 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
int main()
{
int y ; int x = (y = 1) ;
int w = ++x + y++;
cout << w <<" " << x << " " << y << "-" ;
w = x-- - --y;
cout << w << " " << x << " " << y << endl ;
return 0;
}
Los operadores para asignación compuesta, y los de incremento y decremento, no son sólo
abreviaciones. En realidad hay que preferirlas porque implican optimizaciones en el ejecutable
resultante.
int i = 3;
float x = 43.8;
cout << "Suma = " << x + i << endl;
a) Conversión explı́cita.
Si i es un int, por ejemplo, entonces float(i) la convierte en float. Ası́, el programa
anterior se puede reescribir:
int i = 3;
float x = 43.8;
cout << "Suma = " << x + float(i) << endl;
Ahora la suma es claramente entre dos variables float, y se puede realizar. Sin em-
bargo, esto es bastante tedioso, por cuanto el programador debe realizar el trabajo de
conversión personalmente cada vez que en su código se desee sumar un real con un
número entero.
b) Conversión implı́cita.
En este caso, el compilador realiza las conversiones de modo automático, prefiriendo
siempre la conversión desde un tipo de variable de menor precisión a uno de mayor
precisión (de int a double, de short a int, etc.). Ası́, a pesar de lo que dijimos, el
código anterior habrı́a funcionado en su forma original. Evidentemente esto es muy
cómodo, porque no necesitamos hacer una conversión explı́cita cada vez que sumamos
un entero con un real. Sin embargo, debemos estar conscientes de que esta comodidad
sólo es posible porque ocurren varias cosas: primero, el compilador detecta el intento de
operar sobre dos variables que no son del mismo tipo; segundo, el compilador detecta,
en sus reglas internas, la posibilidad de cambiar uno de los tipos (int en este caso) al
otro (float); tercero, el compilador realiza la conversión, y finalmente la operación se
puede llevar a cabo. Entender este proceso nos permitirá aprovechar las posibilidades
de la conversión implı́cita de tipos cuando nuestro código involucre tipos de variables
más complicados, y entender varios mensajes de error del compilador.
Es interesante notar cómo las conversiones implı́citas de tipos pueden tener consecuen-
cias insospechadas. Consideremos las tres expresiones:
i) x = (1/2) * (x + a/x) ;
ii) x = (0.5) * (x + a/x) ;
iii) x = (x + a/x)/2 ;
Si inicialmente x=0.5 y a=0.5, por ejemplo, i) entrega el valor x=0, mientras ii) y iii)
entregan el valor x=1.5. Lo que ocurre es que 1 y 2 son enteros, de modo que 1/2 = 0.
De acuerdo a lo que dijimos, uno esperarı́a que en i), como conviven números reales
con enteros, los números enteros fueran convertidos a reales y, por tanto, la expresión
tuviera el resultado esperado, 1.5. El problema es la prioridad de las operaciones. No
todas las operaciones tienen igual prioridad (las multiplicaciones y divisiones se realizan
antes que las sumas y restas, por ejemplo), y esto permite al compilador decidir cuál
operación efectuar primero. Cuando se encuentra con operaciones de igual prioridad
(dos multiplicaciones, por ejemplo), se procede a efectuarlas de izquierda a derecha.
Pues bien, en i), la primera operación es 1/2, una división entre enteros, i.e. cero. En
ii) no hay problema, porque todas son operaciones entre reales. Y en iii) la primera
108 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
operación es el paréntesis, que es una operación entre reales. Al dividir por 2 éste es
convertido a real antes de calcular el resultado.
i) aún podrı́a utilizarse, cambiando el prefactor del paréntesis a 1.0/2.0, una práctica
que serı́a conveniente adoptar como standard cuando queremos utilizar enteros den-
tro de expresiones reales, para evitar errores que pueden llegar a ser muy difı́ciles de
detectar.
if (a==b) {
cout << "a es igual a b" << endl;
}
En este y en muchos de los ejemplos que siguen, los paréntesis cursivos son opcionales.
Ellos indican simplemente un grupo de instrucciones que debe ser tratado como una sola
instrucción. En el ejemplo anterior, los paréntesis cursivos después del if (o después de
un while, for, etc. más adelante) indican el conjunto de instrucciones que deben o no
ejecutarse dependiendo de si cierta proposición es verdadera o falsa. Si ese conjunto de
instrucciones es una sola, se pueden omitir los paréntesis:
if (c!=d) {
cout << "c es distinto de d" << endl;
}
else {
cout << "c es igual a d" << endl;
}
4.2. CONTROL DE FLUJO. 109
if (e > f) {
cout << "e es mayor que f" << endl;
}
else if (e == f) {
cout << "e es igual a f" << endl;
}
else {
cout << "e es menor que f" << endl;
}
Para C++, una expresión verdadera es igual a 1, y una falsa es igual a 0. Esto es, cuando
escribimos if(e>f), y e>f es falsa, en realidad estamos diciendo if(0). A la inversa, 0
es considerada una expresión falsa, y cualquier valor no nulo es considerado una expresión
verdadera. Ası́, podrı́amos hacer que una porción del código siempre se ejecute (o nunca)
poniendo directamente if(1) o if(0), respectivamente.
Naturalmente, lo anterior no tiene mucho sentido, pero un error habitual (y particular-
mente difı́cil de detectar) es escribir a = b en vez de a == b en una expresión lógica. Esto
normalmente trae consecuencias indeseadas, pues la asignación a = b es una función que se
evalúa siempre al nuevo valor de a. En efecto, una expresión como a=3 siempre equivale a
verdadero, y a=0 siempre equivale a falso. Por ejemplo, en el siguiente programa:
#include <iostream>
using namespace std;
int main(){
int k=3;
if (k==3){
cout << "k es igual a 3" << endl;
}
k=4;
if (k=3){
cout << "k es igual a 3" << endl;
}
return 0;
}
110 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
k es igual a 3
k es igual a 3
if (a==b) {
c = 1;
} else {
c = 0;
}
Existen dos maneras de compactar este código. Éste se puede reemplazar por
if (a==b) c = 1;
else c = 0;
Sin embargo, esto no es recomendable por razones de claridad al leer el código. Una expresión
más compacta y clara, se consigue usando el operador ternario ? :
c = (a==b) ? 1 : 0;
4.2.3. switch.
La instrucción switch permite elegir múltiples opciones a partir del valor de una variable
entera. En el ejemplo siguiente tenemos que si i==1 la ejecución continuará a partir del caso
case 1:, si i==2 la ejecución continuará a partir del caso case 2: y ası́ sucesivamente. Si
i toma un valor que no está enumerado en ningún case y existe la etiqueta default, la
ejecución continuará a partir de ahı́. Si no existe default, la ejecución continúa luego del
último paréntesis cursivo.
switch (i)
{
case 1:
{
cout << "Caso 1." << endl;
}
break;
case 2:
4.2. CONTROL DE FLUJO. 111
{
cout << "Caso 2." << endl;
}
break;
default:
{
cout << "Otro caso." << endl;
}
break;
}
La instrucción break permite que la ejecución del programa salte a la lı́nea siguiente después
de la serie de instrucciones asociadas a switch. De esta manera sólo se ejecutarán las lı́neas
correspondientes al case elegido y no el resto. Por ejemplo, si i==1 verı́amos en pantalla
sólo la lı́nea Caso 1. En el otro caso, si no existieran los break, y también i==1, entonces
verı́amos en pantalla las lı́neas Caso 1., Caso 2. y Otro caso. La instrucción default es
opcional.
4.2.4. for.
Una instrucción que permite repetir un bloque de instrucciones un número definido de
veces es el for. Su sintaxis comienza con una o varias inicializaciones, luego una condición
lógica de continuación mientras sea verdadera, y finalmente una o más expresiones que se
evalúan vuelta por vuelta no incluyendo la primera vez. Siguiendo al for(...) viene una
instrucción o un bloque de ellas encerradas entre paréntesis de llave. En el ejemplo siguiente
la variable entera i es inicializada al valor 1, luego se verifica que la condición lógica sea
cierta y se ejecuta el bloque de instrucciones. A la vuelta siguiente se evalúa la expresión a
la extrema derecha (suele ser uno o más incrementadores), se verifica que la condición lógica
se mantenga cierta y se ejecuta nuevamente el bloque de instrucciones. Cuando la condición
lógica es falsa se termina el loop, saltando la ejecución a la lı́nea siguiente al paréntesis que
indica el fin del bloque de instrucciones del for. En este ejemplo, cuando i=4 la condición
de continuación será falsa y terminará la ejecución del for.
Cualquier variable declarada en el primer argumento del for es local al loop. En este caso,
la variable i es local, y no interfiere con otras posibles variables i que existan en nuestro
código.
112 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
for es una instrucción particularmente flexible. En el primer y tercer argumento del for
se puede colocar más de una instrucción, separadas por comas. Esto permite, por ejemplo,
involucrar más de una variable en el ciclo. El código:
resulta en el output:
i + k = 20
i + k = 27
i + k = 34
i + k = 41
i + k = 48
Además, la condición de continuación (segundo argumento del for), no tiene por qué de-
pender de las variables inicializadas en el primer argumento. Y el tercer argumento no tiene
por qué ser un incremento o decremento de las variables del loop; puede ser cualquier expre-
sión que queramos ejecutar cada vez que un ciclo termina. En el siguiente ejemplo, además
de incrementar los contadores en cada ciclo, se envı́a un mensaje a pantalla:
for (int i=1, k=2;k<5 && i<20;k++, i+=2, cout << "Fin iteracion" << endl){
cout << " i = " << i <<’’,’’;
cout << " k = " << k << endl;
}
i = 1, k = 2
Fin iteracion
i = 3, k = 3
Fin iteracion
i = 5, k = 4
Fin iteracion
Todos los argumentos del for son opcionales (no los ;), por lo cual se puede tener un for
que carezca de inicialización y/o de condición de continuación y/o de una expresión que se
evalúe en cada iteración.
Un caso tı́pico en que se aprovecha la opcionalidad de los argumentos del for es para
tener un loop infinito, que puede servir para dejar el programa en pausa indefinida. Para salir
del loop (y en general, para detener cualquier programa en C++), hay que presionar ^C:
da la salida a pantalla:
0 1 2 3 4
for (int i=1, k=2;k<5 && i<8;k++, i+=2, cout << "Fin iteracion" << endl){
cout << " i = " << i << ", k = " << k << endl;
k = k+5;
}
El resultado es:
i = 1, k = 2
Fin iteracion
En vez de pasar por el ciclo tres veces, como ocurrı́a originalmente, el programa sale del loop,
al cabo del primer ciclo, k = 2 + 5 = 7 > 5.
En general no es una buena práctica modificar las variables internas del ciclo en medio
de él, porque no es muy ordenado, y el desorden normalmente conduce a los errores en
programación, pero ocasionalmente puede ser útil hacer uso de esta libertad que proporciona
el lenguaje. Los ciclos for pueden anidarse, tal que uno contenga a otro completamente.
4.2.5. while.
La instrucción while permite repetir un bloque de instrucciones encerradas entre parénte-
sis de llave mientras la condición lógica que acompaña al while se mantenga cierta. La con-
dición es evaluada antes de que comience la primera iteración; si es falsa en ésta o en una
posterior evaluación no se ejecuta el bloque de instrucciones que le siguen y se continúa la
ejecución en la lı́nea siguiente al paréntesis que indica el fin del bloque asociado al while.
Hay que notar que la instrucción while podrı́a no ejecutarse ni una sola vez si la condición
no se cumple inicialmente. Un ejemplo simple:
int i=1;
while (i < 3) {
cout << i++ << " ";
}
114 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
int k=5 ;
while(k) {
cout << k-- <<" ";
}
do {
cout << i++ << endl;
} while (i<=20);
do {
cout << "Este es un segundo loop infinito, ^C para detenerlo"<< endl;
} while (1);
4.2.7. goto.
Existe también en C++ una instrucción goto que permite saltar de un punto a otro del
programa (goto salto; permite saltar a la lı́nea que contiene la instrucción salto:). Sin
embargo, se considera una mala técnica de programación usar goto, y siempre se puede di-
señar un programa evitándolo. Es altamente no recomendable, pero si su utilización simplifica
el código se puede usar.
4.3. Funciones.
Las funciones nos permiten programar partes del procedimiento por separado. Un ejemplo
simple de ellas lo vimos en la subsección 4.1.2.
4.3.2. return.
Si deseamos definir una función que calcule una raı́z cuadrada, evidentemente esperamos
que la función nos entregue un resultado: el valor de la raı́z cuadrada. En este caso hay que
traspasar el valor de una variable desde la función al programa que la llamó. Esto se consigue
con return. Veamos un ejemplo muy simple:
int numero(){
int i = 3;
return i;
}
int main(){
cout << "Llamamos a la funcion" << endl;
cout << "El numero es: " << numero() << endl;
int i = 5;
i = i + numero();
cout << "El numero mas 5 es: " << i << endl;
return 0;
}
int numero(){
int i = 3;
return i;
}
Dos observaciones útiles:
a) La declaración de la función lleva antepuesto el tipo de variable que la función entrega.
En el ejemplo, la variable entregada es un entero, i, y la declaración debe ser, por tanto,
int numero(). Podemos tener funciones tipo double, char, long, etc., de acuerdo al
tipo de variable que corresponde a return.
b) La variable i que se usa dentro de main() y la que se usa dentro de numero() son
distintas. A pesar de que tienen el mismo nombre, se pueden usar independientemente
como si se llamaran distinto. Se dice que i es una variable local.
Después de return debe haber una expresión que se evalúe a una variable del tipo co-
rrespondiente, ya sea explı́citamente o a través de un cast implı́cito. Las siguientes funciones
devuelven un double al programa:
116 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
double f1(){
double l = 3.0;
return l;
}
double f2(){
double l = 3.0, m = 8e10;
return l*m;
}
double f3(){
int l = 3;
return l;
}
Sin embargo, la siguiente función hará que el compilador emita una advertencia, pues se
está tratando de devolver un double donde deberı́a ser un int, y la conversión implica una
pérdida de precisión:
int f4(){
double l=3.0;
return l;
}
Naturalmente, podemos modificar la función anterior haciendo una conversión explı́cita antes
de devolver el valor: return int(l).
int main(){
int i = 3;
cout << "El valor de la funcion es " << funcion(i)
<< endl;
cout << "El valor del parametro es " << i << endl;
return 0 ;
}
El valor de la funcion es 7
El valor del parametro es 3
La función funcion entrega el valor del parámetro más 4. Usamos el mismo nombre (i)
para las variables en main y funcion, pero son variables locales, ası́ que no interfieren. Lo
importante es notar que cuando se llama a la función, la reasignación del valor de i (i+=4)
ocurre sólo para la variable local en funcion; el parámetro externo mantiene su valor.
Separando declaración e implementación el ejemplo anterior se escribe:
int funcion(int);
int main(){...}
#include <iostream>
int main()
{
int i = 3;
cout << "El valor de la funcion es " << funcion(i)
<< endl;
cout << "El valor del parametro es " << i << endl;
return 0;
}
int funcion(int & i)
{
i+=4;
return i;
}
El valor de la funcion es 7
El valor del parametro es 7
Debido a que las variables dejan de ser locales, el paso de parámetros por referencia
debe ser usado con sabidurı́a. De hecho el ejemplo presentado es poco recomendable.
Peor aún, el problema es no sólo que las variables dejan de ser locales, sino que es
imposible saber que no lo son desde el main. En efecto, el main en ambas versiones de
funcion es el mismo. Lo único que cambió es la declaración de la función. Puesto que un
usuario normal usualmente no conoce la declaración e implementación de cada función
que desea usar (pues pueden haber sido hechas por otros programadores), dejamos al
usuario en la indefensión.
Por otro lado, hay al menos dos situaciones en que el paso de referencia es la única
opción viable para entregar los parámetros. Un caso es cuando hay que cuidar el uso
de la memoria. Supongamos que una función necesita un parámetros que es una matriz
de 10 millones de filas por 10 millones de columnas. Seguramente estaremos llevando al
4.3. FUNCIONES. 119
lı́mite los recursos de nuestra máquina, y serı́a una torpeza pasarle la matriz por valor:
ello involucrarı́a, primero, duplicar la memoria utilizada, con el consiguiente riesgo de
que nuestro programa se interrumpa; y segundo, harı́a el programa más lento, porque
la función necesitarı́a llenar su versión local de la matriz elemento por elemento. Es
decir, nada de eficiente. En esta situación, el paso por referencia es lo adecuado.
Un segundo caso en que el paso por referencia es recomendable es cuando efectivamente
nuestra intención es cambiar el valor de las variables. El ejemplo tı́pico es el intercambio
de dos variables entre sı́, digamos a1=1 y a2=3. Luego de ejecutar la función queremos
que a1=3 y a1=1. El siguiente código muestra la definición y el uso de una función para
esta tarea, y por cierto requiere el paso de parámetros por referencia:
#include <iostream>
void swap(int &,int &);
using namespace std;
int main(){
int i = 3, k=10;
swap(i,k);
cout << "Primer argumento: " << i << endl;
cout << "Segundo argumento: " << k << endl;
return 0 ;
}
El output es:
Primer argumento: 10
Segundo argumento: 3
En el ejemplo de la matriz anterior, serı́a interesante poder pasar el parámetro por refe-
rencia, para ahorrar memoria y tiempo de ejecución, pero sin correr el riesgo de que nuestra
matriz gigantesca sea modificada por accidente. Afortunadamente existe el modo de hacerlo,
usando una palabra que ya hemos visto antes: const. En el siguiente código:
int f5(const int &);
int main(){...}
int f5(const int & i){...};
f5 recibirá su único argumento por referencia, pero, debido a la presencia del modificador
const, el compilador avisará si se intenta modificar el argumento en medio del código de la
función.
120 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
int main(){
cout << "El resultado default es " << funcion() << endl;
int i = 3;
cout << "Cuando el parametro vale " << i <<
" el resultado es " << funcion(i) << endl;
return 0;
}
El output correspondiente es:
El resultado default es 9
Cuando el parametro vale 3 el resultado es 7
double raiz(double);
int main(){
double r;
cout.precision(20);
cout << "Ingrese un numero: " << endl;
cin >> r;
cout << raiz(r) << endl;
return 0 ;
122 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
while (fabs(dx)>epsilon){
x = (x + a/x)/2;
dx = x*x - a;
cout << "x = " << x << ", precision = " << dx << endl;
}
return x;
}
Factorial.
Otro ejemplo útil es el cálculo del factorial, definido para números naturales:
n! = n · (n − 1) · · · 2 · 1 , 0! ≡ 1 .
La estrategia natural es utilizar un ciclo for, determinado por una variable entera i, que
va desde 1 a n, guardando los resultados en una variable auxiliar que contiene el producto
de todos los números naturales desde 1 hasta i:
#include <iostream>
using namespace std;
4.3. FUNCIONES. 123
int factorial(int);
int main(){
int n=5 ;
cout << "El factorial de " << n << " es: " << factorial(n) << endl;
return 0 ;
}
int factorial(int i)
{
int f =1;
for (int j=1;j<=i;j++){
f = f*j;
}
return f;
}
Observar que la variable auxiliar f, que contiene el producto de los primeros i números
naturales, debe ser inicializada a 1. Si se inicializara a 0, factorial(n) serı́a 0 para todo n.
Esta función no considera el caso n=0, pero al menos para el resto de los naturales fun-
cionará bien.
Alcance (scope) La sección del código durante la cual el nombre de una variable puede ser
usado. Comprende desde la declaración de la variable hasta el final del cuerpo de la
función donde es declarada.
Si la variable es declarada dentro de una función es local . Si es definida fuera de todas
las funciones (incluso fuera de main), la variable es global.
Visibilidad Indica cuáles de las variables, actualmente al alcance, pueden ser accesadas. En
nuestros ejemplos (subsección 4.3.3), la variable i en main aún está al alcance dentro
de la función funcion, pero no es visible, y por eso es posible reutilizar el nombre.
Tiempo de vida Indica cuándo las variables son creadas y cuándo destruidas. En general
este concepto coincide con el alcance (las variables son creadas cuando son declaradas y
destruidas cuando la función dentro de la cual fueron declaradas termina), salvo porque
es posible definir: (a) variables dinámicas, que no tienen alcance, sino sólo tiempo de
vida; (b) variables estáticas, que conservan su valor entre llamadas sucesivas de una
función (estas variables tienen tiempo de vida mayor que su alcance). Para declarar
estas últimas se usa un modificador static.
124 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
#include <iostream>
int f();
using namespace std;
int main(){
int f(){
int x=0;
x++;
return x;
}
La función f simplemente toma el valor inicial de x y le suma 1. Como cada vez que la
función es llamada la variable local x es creada e inicializada, el resultado de este programa
es siempre un 1 en pantalla:
1
1
#include <iostream>
int f();
using namespace std;
int main(){
int f(){
static int x=0;
x++;
return x;
}
4.3. FUNCIONES. 125
1
2
#include <iostream>
factorial2(n);
n--;
4.3.7. Recursión.
C++ soporta un tipo especial de técnica de programación, la recursión, que permite que
una función se llame a sı́ misma (esto es no trivial, por cuanto si definimos, digamos, una
función f, dentro del cuerpo de la implementación no hay ninguna declaración a una función
f, y por tanto en principio no se podrı́a usar f porque dicho nombre no estarı́a en scope;
C++ permite soslayar este hecho). La recursión permite definir de modo muy compacto una
función que calcule el factorial de un número entero n.
#include <iostream>
int main(){
int n=5;
cout << "El factorial de "<< n << " es = " << factorial3(n) << endl;
return 0;
}
el archivo de header <cmath> y luego al compilar agregar al final del comando de compilación
-lm:
g++ -Wall -o <salida> <fuente>.cc -lm
si se desea crear un ejecutable <salida> a partir del código en <fuente>.cc.
Veamos algunas de estas funciones:
pow(x,y) Eleva a potencia, xy
fabs(x) Valor absoluto
sqrt(x) Raı́z cuadrada
sin(x) cos(x) Seno y coseno
tan(x) Tangente
atan(x) Arcotangente de x en [−π, π]
atan2(y, x) Arcotangente de y/x en [−π, π]
exp(x) Exponencial
log(x) log10(x) Logaritmo natural y logaritmo en base 10
floor(x) Entero más cercano hacia abajo (e.g. floor(3.2)=3)
ceil(x) Entero más cercano hacia arriba (e.g. ceil(3.2)=4)
fmod(x,y) El resto de x/y (e.g. fmod(7.3, 2)=1.3)
Para elevar a potencias enteras, es más conveniente usar la forma explı́cita en vez de la
función pow, i.e. calcular x^3 como x*x*x es más eficiente computacionalmente que pow(x,3),
debido a los algoritmos que usa pow para calcular potencias. Éstos son más convenientes
cuando las potencias no son enteras, en cuyo caso no existe una forma explı́cita en términos
de productos.
4.4. Punteros.
Una de las ventajas de C++ es permitir el acceso directo del programador a zonas de
memoria, ya sea para crearlas, asignarles un valor o destruirlas. Para ello, además de los tipos
de variables ya conocidos (int, double, etc.), C++ proporciona un nuevo tipo: el puntero.
El puntero no contiene el valor de una variable, sino la dirección de memoria en la cual dicha
variable se encuentra.
Un pequeño ejemplo nos permite ver la diferencia entre un puntero y la variable a la cual
ese puntero “apunta”:
#include <iostream>
int main(){
int i = 42;
int * p = &i;
cout << "El valor del puntero es: " << p << endl;
cout << "Y apunta a la variable: " << *p << endl;
return 0;
}
128 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
En este programa definimos una variable i entera. Al crear esta variable, el programa
reservó un espacio adecuado en algún sector de la memoria. Luego pusimos, en esa dirección
de memoria, el valor 42. En la siguiente lı́nea creamos un puntero a i, que en este caso
denominamos p. Los punteros no son punteros a cualquier cosa, sino punteros a un tipo
particular de variable. Ello es manifiesto en la forma de la declaración: int * p. En la
misma lı́nea asignamos a este puntero un valor. Ese valor debe ser también una dirección de
memoria, y para eso usamos &i, que es la dirección de memoria donde está i. Ya hemos visto
antes el uso de & para entregar una dirección de memoria, al estudiar paso de parámetros a
funciones por referencia (4.3.3).
Al ejecutar este programa vemos en pantalla los mensajes:
#include <iostream>
int main(){
int * p = new int;
*p = 42;
cout << "El valor del puntero es: " << p << endl;
cout << "Y apunta a la variable: " << *p << endl;
delete p;
return 0;
}
La primera lı́nea crea un nuevo puntero a int llamado p. new verifica que haya suficiente
memoria para alojar un nuevo int, y si es ası́ reserva ese espacio de memoria. En p queda la
dirección de la memoria reservada. Esto es equivalente a la declaración int i; del programa
anterior, salvo que ahora la única manera de accesar esa dirección de memoria es a través
del puntero p. A continuación se coloca dentro de esa dirección (observar la presencia del
4.5. MATRICES O ARREGLOS. 129
int a[5];
double r[3] = {3.5, 4.1, -10.8};
char palabra[5];
Una vez declarada la matriz (digamos a[5]), los valores individuales se accesan con a[i],
con i desde 0 a 4. Por ejemplo, podemos inicializar los elementos de la matriz ası́:
a[0] = 3;
a[3] = 5; ...
int main(){
double matriz[5] = {3.5, 5.2, 2.4, -0.9, -10.8};
PrintMatriz(5, matriz);
return 0;
}
Observemos que la función debe recibir dos parámetros, uno de los cuales es la dimensión
de la matriz. Esto se debe a que cuando las matrices son usadas como parámetros la infor-
mación de su dimensión no es traspasada, y debe ser comunicada independientemente. Una
ligera optimización al programa anterior es modificar main a:
#include <iostream>
int main()
{
int dim = 5;
double matriz[dim] = {3.5, 5.2, 2.4, -0.9, -10.8};
PrintMatriz(dim, matriz);
return 0;
}
De este modo, si eventualmente cambiamos de opinión y deseamos trabajar con matrices
de longitud distinta, sólo hay que modificar una lı́nea de código (la primera) en todo el
programa, el cual puede llegar a ser bastante largo por cierto. (En el ejemplo, también habrı́a
que cambiar la lı́nea de inicialización de la matriz, porque asume que la matriz requiere sólo 5
elementos, pero de todos modos deberı́a ser clara la enorme conveniencia.) Podemos reescribir
este programa con un comando de preprocesador para hacer la definición de la dimensión:
4.5. MATRICES O ARREGLOS. 131
#include <iostream>
#define DIM 5
using namespace std;
int main(){
double matriz[DIM] = {3.5, 5.2, 2.4, -0.9, -10.8};
PrintMatriz(DIM, matriz);
return 0;
}
#include <iostream>
using namespace std;
int main()
{
cout<<"Ingrese la dimension deseada :" ;
int dim ;
cin >> dim ;
double * matriz = new double[dim] ; // Reserva la memoria
for(int i=0; i < dim; i++) {
cout << "Ingrese elemento "<< i <<" : ";
cin >> matriz[i] ;
}
Este ejemplo permite apreciar una gran ventaja del uso de punteros, al permitirnos liberarnos
de definir la dimensión de una matriz como una constante. Aquı́, dim es simplemente un int.
La asignación dinámica permite definir matrices cuya dimensión se determina recién durante
la ejecución.
Observemos finalmente que la liberación de memoria, en el caso de arreglos, se hace con
el operador delete [], no delete como en los punteros usuales.
132 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
double array[10][8];
int array[2][3] = {{1, 2, 3},
{4, 5, 6}};
Una operación usual es definir primero las dimensiones de la matriz, y luego llenar sus
elementos uno por uno (o desplegarlos en pantalla), recorriendo la matriz ya sea por filas o
por columnas. Hay que tener cuidado del orden en el cual uno realiza las operaciones. En
el siguiente código, definimos una matriz de 10 filas y 3 columnas, la llenamos con ceros
elemento por elemento, y luego inicializamos tres de sus elementos a números distintos de
cero. Finalmente desplegamos la matriz resultante en pantalla:
#include <iostream>
using namespace std;
#define dimx 3
#define dimy 10
void imprime(int,int,double[][dimx]);
int main()
{
double a[dimy][dimx];
for (int i=0;i<dimy;i++)
{
for (int j=0;j<dimx;j++)
{
a[i][j]=0;
}
}
a[0][0]=1;
a[3][2]=2;
a[9][2]=3;
imprime(dimx,dimy,a);
return 0;
}
#include <iostream>
#define NROWS 3
#define NCOLUMNS 2
int main()
{
int array[NROWS][NCOLUMNS];
f(NROWS, NCOLUMNS, array);
for(int i=0;i<NROWS;i++) {
for(int j=0;j<NCOLUMNS;j++) array[i][j]=0;
}
f(NROWS, NCOLUMNS, array);
return 0;
}
el valor “Hola”, debe definirse como una matriz de dimensión 5 (una más que el número de
letras):
Para escribir “Hola” en pantalla basta recorrer los elementos de palabra uno a uno:
Si tuviéramos que hacer esto cada vez que queremos escribir algo a pantalla no serı́a muy
cómodo. Por ello, también podemos escribir “Hola” en pantalla simplemente con cout << "Hola",
y de hecho ése fue el primer ejemplo de este capı́tulo. De hecho, la declaración de palabra
podrı́a haberse escrito:
Esto ya es bastante más cómodo, aunque persiste la inconsistencia de definir palabra con
dimensión 5, cuando en realidad al lado derecho de la asignación hay un objeto con sólo 4
elementos (visibles).
Éste y otros problemas asociados con el manejo convencional de cadenas en C++ se
resuelven incluyendo el header string.
Usando string.
El código anterior se puede reescribir:
#include <iostream>
#include <string>
using namespace std;
int main(){
string palabra = "Hola";
cout << palabra << endl;
return 0;
}
Observar que la lı́nea a incluir es #include <string>, sin la extensión “.h”. Al incluir
string, las cadenas pueden ser declaradas como objetos tipo string en vez de arreglos
de char. El hecho de que ya no tengamos que definir a priori la dimensión de la cadena
es una gran ventaja. De hecho, permite ingresar palabras desde el teclado trivialmente, sin
preocuparse de que el input del usuario sea demasiado grande (tal que supere la dimensión del
arreglo que podamos haber declarado inicialmente) o demasiado corto (tal que se traduzca
en un despilfarro de memoria por reservar más memoria para el arreglo de la que realmente
se necesita):
4.5. MATRICES O ARREGLOS. 135
#include <iostream>
#include <string>
using namespace std;
int main(){
string palabra;
cin >> palabra;
return 0;
}
Además, este nuevo tipo string permite acceder a un sin número de funciones adicionales
que facilitan enormemente el manejo de cadenas. Por ejemplo, las cadenas se pueden sumar,
donde la suma de cadenas a y b está definida (siguiendo la intuición) como la cadena que
resulta de poner b a continuación de a:
#include <iostream>
#include <string>
using namespace std;
int main(){
string texto1 = "Primera palabra";
string texto2 = "Segunda palabra";
cout << texto1 << endl << texto2 << endl;
cout << texto1 + ", " + texto2 << endl;
// La ultima linea es equivalente a:
// string texto3 = texto1 + ", " + texto2;
// cout << texto3 << endl;
return 0 ;
}
El output de este programa será:
Primera palabra
Segunda palabra
Primera palabra, Segunda palabra
llamada get, y leer desde el teclado hasta que el usuario dé el primer cambio de lı́nea. Un
ejemplo simple lo encontramos en el siguiente código:
#include <iostream>
#include <string>
using namespace std;
int main(){
string texto1 = "El resultado es: " ;
string texto2 ="";
char ch;
#include <iostream>
#include <fstream>
using namespace std;
int main(){
ofstream nombre_logico("nombre_fisico.dat");
int i = 3, j;
cout << i << endl;
nombre_logico << i << endl;
cout << "Ingrese un numero entero: ";
4.6. MANEJO DE ARCHIVOS. 137
cin >> j;
cout << j << endl;
nombre_logico << j << endl;
nombre_logico.close();
return 0;
}
La primera lı́nea de main define un objeto de tipo ofstream (output file stream). Es-
to corresponde a un archivo de salida. Dentro de main este archivo será identificado por
una variable llamada nombre_logico, y corresponderá a un archivo en el disco duro llama-
do nombre_fisico.dat. Naturalmente, el identificador nombre_logico puede ser cualquier
nombre de variable válido para C++, y nombre_fisico.dat puede ser cualquier nombre de
archivo válido para el sistema operativo. En particular, se pueden también dar nombres que
incluyan paths absolutos o relativos:
ofstream nombre_logico_1("/home/vmunoz/temp/nombre_fisico.dat");
ofstream nombre_logico_2("../nombre_fisico.dat");
Cuando creamos un objeto del tipo archivo, sin importar si es de salida o de entrada,
podemos inicializarlo con un nombre de archivo fı́sico. Este nombre lo podemos almacenar
previamente en una variable de string, llamemosla mi_nombre_archivo. En este caso, cuan-
do creamos el objeto ofstream debemos usar un método del objeto string que devuelve un
puntero a char, para poder inicializar el objeto ofstream. Veamos la sintaxis explı́citamente
string mi_nombre_archivo=’’archivo.txt’’;
ofstream nombre_logico_1( mi_nombre_archivo.c_str());
Las lı́neas cuarta y octava de main envı́an a nombre_logico (es decir, escribe en
nombre_fisico.dat), las variables i y j. Observar la analogı́a que existe entre estas opera-
ciones y las que envı́an la misma información a pantalla.4 Si ejecutamos el programa y en
el teclado ingresamos el número 8, al finalizar la ejecución el archivo nombre_fisico.dat
tendrá los dos números escritos:
3
8
Finalmente, el archivo creado debe ser cerrado (nombre_logico.close()). Si esta última
operación se omite en el código, no habrá errores de compilación, y el programa se encar-
gará de cerrar por sı́ solo los archivos abiertos durante su ejecución, pero un buen programador
debiera tener cuidado de cerrarlos explı́citamente. Por ejemplo, un mismo programa podrı́a
desear utilizar un mismo archivo más de una vez, o varios programas podrı́an querer acceder
al mismo archivo, y si no se ha insertado un close en el punto adecuado esto podrı́a provocar
problemas.
El archivo indicado al declarar la variable de tipo ofstream tiene modo de escritura, para
permitir la salida de datos hacia él. Si no existe un archivo llamado nombre_fisico.dat es
4
Esta analogı́a no es casual y se entiende con el concepto de clases (Sec. 4.8). fstream e iostream definen
clases que heredan sus propiedades de un objeto abstracto base, común a ambas, y que en el caso de iostream
se concreta en la salida estándar —pantalla—, y en el de fstream en un archivo.
138 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
creado; si existe, los contenidos antiguos se pierden y son reemplazados por los nuevos. No
siempre deseamos este comportamiento. A veces deseamos agregar la salida de un programa
a un archivo de texto ya existente. En ese caso la declaración del archivo es diferente, para
crear el archivo en modo “append”:
#include <iostream>
#include <fstream>
using namespace std;
int main(){
ofstream nombre_logico("nombre_fisico.dat",ios::app);
int i = 3;
3
3
La lı́nea del tipo ofstream a("b") es equivalente a una del tipo int i=3, declarando
una variable (a/i) de un cierto tipo (ofstream/int) y asignándole un valor simultáneamente
"b"/3. Como para los tipos de variables predefinidos de C++, es posible separar declaración
y asignación para una variable de tipo ofstream:
ofstream a;
a.open("b");
es equivalente a ofstream a("b"). Esto tiene la ventaja de que podrı́amos usar el mismo
nombre lógico para identificar dos archivos fı́sicos distintos, usados en distintos momentos
del programa:
ofstream a;
a.open("archivo1.txt");
// Codigo en que "archivo1.txt" es utilizado
a.close();
a.open("archivo2.txt");
// Ahora "archivo2.txt" es utilizado
a.close();
4.6. MANEJO DE ARCHIVOS. 139
Observar la necesidad del primer close, que permitirá liberar la asociación de a a un nombre
fı́sico dado, y reutilizar la variable lógica en otro momento.
En los ejemplos hemos escrito solamente variables de tipo int en los archivos. Esto por
cierto no es restrictivo. Cualquiera de los tipos de variables de C++ —float, double, char,
etc.— se puede enviar a un archivo del mismo modo. Dicho esto, en el resto de esta sección
seguiremos usando como ejemplo el uso de int.
int main(){
ifstream nombre_logico("nombre_fisico.dat");
int i, j,k,l;
cout << i << "," << j << "," << k << "," << l << endl;
nombre_logico.close();
return 0;
}
será equivalente a asignar i=3, j=6, k=9, l=12, y luego enviar los datos a pantalla. Observar
que la sintaxis para ingresar datos desde un archivo, nombre_logico >> i, es idéntica a
cin >> i, para hacerlo desde el teclado. Al igual que cin, espacios en blanco son equivalentes
a cambios de lı́nea, de modo que el archivo podrı́a haber sido también:
3 6 9 12
Por cierto, el ingreso de datos desde un archivo se puede hacer con cualquier técnica, por
ejemplo, usando un for:
140 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
ifstream nombre_logico("nombre_fisico.dat");
int i;
for (int j=0;j<10;j++){
nombre_logico >> i;
cout << i << ",";
}
nombre_logico.close();
}
ifstream a;
a.open("b");
a.close();
#include <iostream>
#include <fstream>
using namespace std;
int main(){
fstream nombre_logico;
nombre_logico.open("nombre_fisico.dat",ios::out);
int i = 4,j;
nombre_logico.open("nombre_fisico.dat",ios::in);
nombre_logico >> j;
5
Nuevamente, este hecho se debe al concepto de clases que subyace a las definiciones de estos tres tipos
de variables; fstream es una clase derivada a la vez de ofstream y de ifstream, heredando las propiedades
de ambas.
4.7. MAIN COMO FUNCIÓN. 141
Por ejemplo:
142 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
#include <iostream>
using namespace std;
return 0;
}
4.8. CLASES. 143
}
o
void main(){
// Codigo
}
también compilan, pero el compilador emite una advertencia si es llamado con la opción
-Wall (Warning all ). En el primer caso, la advertencia es:
warning: ANSI C++ forbids declaration ‘main’ with no type
En el segundo:
return type for ‘main’ changed to ‘int’
En general, siempre es conveniente compilar con la opción -Wall, para lograr que nuestro
código esté realmente correcto (g++ -Wall <archivo>.cc -o <archivo>).
4.8. Clases.
C++ dispone de una serie de tipos de variables con las cuales nos está permitido operar:
int, double, char, etc. Creamos variables de estos tipos y luego podemos operar con ellas:
int x, y;
x = 3;
y = 6;
int z = x + y;
No hay, sin embargo, en C++, una estructura predefinida que corresponda a números
complejos, vectores de dimensión n o matrices, por ejemplo. Y sin embargo, nos agradarı́a
disponer de números complejos que pudiéramos definir como
z = (3,5);
w = (6,8);
y que tuvieran sentido las expresiones
144 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
a = z + w;
b = z * w;
c = z / w;
d = z + 3;
e = modulo(z);
f = sqrt(z);
Todas estas expresiones son completamente naturales desde el punto de vista matemático,
y serı́a bueno que el lenguaje las entendiera. Esto es imposible en el estado actual, pues, por
ejemplo, el signo + es un operador que espera a ambos lados suyos un número. Sumar cualquier
cosa con cualquier cosa no significa nada necesariamente, ası́ que sólo está permitido operar
con números. Pero los humanos sabemos que los complejos son números. ¿Cómo decı́rselo
al computador? ¿Cómo convencerlo de que sumar vectores o matrices es también posible
matemáticamente, y que el mismo signo + deberı́a servir para todas estas operaciones?
La respuesta es: a través del concepto de clases. Lo que debemos hacer es definir una clase
de números complejos. Llamémosla Complejo. Una vez definida correctamente, Complejo
será un tipo más de variable que el compilador reconocerá, igual que int, double, char, etc.
Y será tan fácil operar con los Complejos como con todos los tipos de variables preexistentes.
Esta facilidad es la base de la extensibilidad de que es capaz C++, y por tanto de todas las
propiedades que lo convierten en un lenguaje muy poderoso.
Las clases responden a la necesidad del programador de construir objetos o tipos de datos
que respondan a sus necesidades. Si necesitamos trabajar con vectores de 5 coordenadas,
será natural definir una clase que corresponda a vectores con 5 coordenadas; si se trata de
un programa de administración de personal, la clase puede corresponder a un empleado, con
sus datos personales como elementos.
Si bien es cierto uno puede trabajar con clases en el contexto de orientación al procedi-
miento, las clases muestran con mayor propiedad su potencial con la orientación al objeto,
donde cada objeto corresponde a una clase. Por ejemplo, para efectuar una aplicación para
X-windows, la ventana principal, las ventanas de los archivos abiertos, la barra de menú, las
cajas de diálogo, los botones, etc., cada uno de estos objetos estará asociado a una clase.
4.8.1. Definición.
Digamos que queremos una clase para representar a los empleados de una empresa.
Llamémosla Persona. La convención aceptada es que los nombres de las clases comiencen
con mayúscula. Esto es porque las clases, recordemos, corresponderán a tipos de variables
tan válidos como los internos de C++ (int, char, etc.). Al usar nombres con mayúscula
distinguimos visualmente los nombres de un tipo de variable interno y uno definido por el
usuario.
La estructura mı́nima de la definición de la clase Persona es:
class Persona
{
};
Todas las caracterı́sticas de la clase se definen entre los paréntesis cursivos.
4.8. CLASES. 145
4.8.2. Miembros.
Se denomina miembros de una clase a todas las variables y funciones declaradas dentro de
una clase. Por ejemplo, para personas, es natural caracterizarlas por su nombre y su edad. Y
si se trata de empleados de una empresa, es natural también tener una función que entregue
su sueldo:
class Persona
{
string nombre;
fecha nacimiento;
int rut;
double edad();
};
Los miembros de una clase pueden tener cualquier nombre, excepto el nombre de la propia
clase dentro de la cual se definen; ese nombre está reservado.
class Complejo
{
private:
double real, imaginaria;
public:
void setreal(double);
void setimag(double);
double getreal();
double getimag();
};
int main()
{
Complejo z, w;
z.setreal(3);
z.setimag(2.8);
w.setreal(1.5);
w.setimag(5);
cout << "El primer numero complejo es: " << z.getreal()
<< " + i*" << z.getimag() << endl;
cout << "El segundo es: " << w.getreal() << " + i*"
<< z.getimag() << endl;
return 0;
}
Vemos en la primera lı́nea de main cómo la clase Complejo se usa del mismo modo que
usarı́amos int o double. Ahora Complejo es un tipo de variable tan válido como los tipos
predefinidos por C++. Una vez definida la variable, el operador de selección (.) permite
acceder a las funciones públicas correspondientes a la clase Complejo, aplicadas a la variable
particular que nos interesa: z.setreal(3) pone en la parte real del Complejo z el número
3, y w.setreal(1.5) hace lo propio con w.
void Complejo::setimag(double x)
{
4.8. CLASES. 147
imaginaria = x;
}
double Complejo::getreal()
{
return real;
}
double Complejo::getimag()
{
return imaginaria;
}
Como toda función, primero va el tipo de la función (void o double en los ejemplos), luego
el nombre de la función y los argumentos. Finalmente la implementación. Lo diferente es que
el nombre va precedido del nombre de la clase y el operador “::” .
4.8.6. Constructor.
Al declarar una variable, el programa crea el espacio de memoria suficiente para alojarla.
Cuando se trata de variables de tipos predefinidos en C++ esto no es problema, pero cuando
son tipos definidos por el usuario, C++ debe saber cómo construir ese espacio. La función
que realiza esa tarea se denomina constructor.
El constructor es una función pública de la clase, que tiene el mismo nombre que ella.
Agreguemos un constructor a la clase Complejo:
class Complejo
{
private:
double real,imaginaria;
public:
Complejo(double,double);
void setreal(double);
void setimag(double);
double getreal();
double getimag();
};
En el constructor se inicializan las variables internas que nos interesa inicializar al mo-
mento de crear un objeto de esta clase.
Si una de las variables internas a inicializar es una cadena de caracteres, hay que inicia-
lizarla de modo un poco distinto. Por ejemplo, si estamos haciendo una clase OtraPersona
que sólo tenga el nombre de una persona, entonces podemos definir la clase y su constructor
en la forma:
class OtraPersona
{
private:
char nombre[20];
public:
Persona(char []);
};
Persona::Persona(char a[])
{
strcpy(nombre,a);
}
Si uno no especifica el constructor de una clase C++ crea uno default, pero en general
será insuficiente para cualquier aplicación realmente práctica. Es una mala costumbre ser
descuidado y dejar estas decisiones al computador.
4.8.7. Destructor.
Ası́ como es necesario crear espacio de memoria al definir una variable, hay que deshacerse
de ese espacio cuando la variable deja de ser necesaria. En otras palabras, la clase necesita
también un destructor . Si la clase es Complejo, el destructor es una función pública de ella,
llamada ~Complejo.
class Complejo
{
private:
double real, imaginaria;
public:
Complejo(double,double);
~Complejo();
void setreal(double);
void setimag(double);
double getreal();
double getimag();
};
Complejo::~Complejo()
{
}
Como con los constructores, al omitir un destructor C++ genera un default, pero es una
mala costumbre. . . , etc.
Complejo z[2];
4.9. Sobrecarga.
Para que la definición de nuevos objetos sea realmente útil, hay que ser capaz de hacer
con ellos muchas acciones que nos serı́an naturales. Como ya comentamos al introducir el
concepto de clase, nos gustarı́a sumar números complejos, y que esa suma utilizara el mismo
signo + de la suma usual. O extraerles la raı́z cuadrada, y que la operación sea tan fácil
como escribir sqrt(z). Lo que estamos pidiendo es que el operador + o la función sqrt()
sean polimórficos, es decir, que actúen de distinto modo según el tipo de argumento que
se entregue. Si z es un real, sqrt(z) calculará la raı́z de un número real; si es complejo,
calculará la raı́z de un número complejo.
La técnica de programación mediante la cual podemos definir funciones polimórficas se
llama sobrecarga.
Complejo sqrt(Complejo z)
{
return Complejo (z.getreal()/2, z.getimag()/2);
}
150 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
Observemos que definimos una función sqrt que acepta argumentos de tipo Complejo, y que
entrega un número del mismo tipo. Cuando pidamos la raı́z de un número, el computador
se preguntará si el número en cuestión es un int, double, float o Complejo, y según eso
escogerá la versión de sqrt que corresponda.
Con la definición anterior podemos obtener la raı́z cuadrada de un número complejo
simplemente con las instrucciones:
Complejo z(1,3);
Complejo raiz = sqrt(z);
4.9.3. Coerción.
Sabemos definir a + b, con a y b complejos. Pero ¿qué pasa si a o b son enteros? ¿O reales?
Pareciera que tendrı́amos que definir no sólo
Complejo operator + (Complejo a, Complejo b);
sino también todas las combinaciones restantes:
Complejo operator + (Complejo a, int b);
Complejo operator + (Complejo a, float b);
Complejo operator + (int a, Complejo b);
etcétera.
En realidad esto no es necesario. Por cierto, un número real es un número complejo con
parte imaginaria nula, y es posible hacerle saber esto a C++, usando la posibilidad de definir
funciones con parámetros default. Basta declarar (en el interior de la clase) el constructor de
los números complejos como
Complejo (double, double = 0);
Esto permite definir un número complejo con la instrucción:
Complejo c = Complejo(3.5);
resultando el número complejo 3.5 + i · 0. Y si tenemos una lı́nea del tipo:
Complejo c = Complejo(3,2.8) + 5;
el computador convertirá implı́citamente el entero 5 a Complejo (sabe cómo hacerlo porque
el constructor de números complejos acepta también un solo argumento en vez de dos), y
luego realizará la suma entre dos complejos, que es entonces la única que es necesario definir.
4.10. HERENCIA. 151
4.10. Herencia.
Herencia es el mecanismo mediante el cual es posible definir clases a partir de otras,
preservando parte de las propiedades de la primera y agregando o modificando otras.
Por ejemplo, si definimos la clase Persona, toda Persona tendrá una variable miembro
que sea su nombre. Si definimos una clase Hombre, también será Persona, y por tanto deberı́a
tener nombre. Pero además puede tener esposa. Y ciertamente no toda Persona tiene esposa.
Sólo un Hombre.
C++ provee mecanismos para implementar estas relaciones lógicas y poder definir una
clase Hombre a partir de Persona. Lo vemos en el siguiente ejemplo:
class Persona
{
private:
string nombre;
public:
Persona(string = "");
~Persona();
string getname();
}
Primero definimos una clase Persona que tiene nombre. Luego definimos una clase Hombre
a partir de Persona (con la lı́nea class Hombre : public Persona). Esto permite de modo
automático que Hombre tenga también una variable nombre. Y finalmente, dentro de la clase
Hombre, se definen todas aquellas caracterı́sticas adicionales que una Persona no tiene pero
un Hombre sı́: esposa, y funciones miembros para modificar y obtener el nombre de ella.
Un ejemplo de uso de estas dos clases:
Persona cocinera("Maria");
Hombre panadero("Claudio");
panadero.setwife("Estela");
Observemos que panadero también tiene una función getname(), a pesar de que la clase
Hombre no la define explı́citamente. Esta función se ha heredado de la clase de la cual Hombre
se ha derivado, Persona.
#ifndef _complejos_
#define _complejos_
#include <iostream>
#include <cmath>
class Complejo {
private:
double real, imaginaria;
public:
Complejo();
Complejo(double,double=0);
~Complejo();
void setreal(double);
void setimag(double);
double getreal();
double getimag();
double getmodule();
double getmodule2();
double getphase();
};
#endif
4.11. EJEMPLO: LA CLASE DE LOS COMPLEJOS. 153
y la implementación de lo anterior
#include "complejos.h"
Complejo::Complejo(double x, double y)
:real(x), imaginaria(y)
{}
Complejo::Complejo()
:real(0), imaginaria(0)
{}
Complejo::~Complejo()
{}
void Complejo::setreal(double x)
{
real = x;
}
void Complejo::setimag(double x)
{
imaginaria = x;
}
double Complejo::getreal()
{
return real;
}
double Complejo::getimag()
{
return imaginaria;
}
double Complejo::getmodule()
{
return sqrt(real*real+imaginaria*imaginaria);
}
double Complejo::getmodule2()
{
return real*real+imaginaria*imaginaria;
}
double Complejo::getphase()
154 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
{
return atan2(real,imaginaria);
}
Complejo conjugate(Complejo z)
{
return Complejo(z.getreal(), -z.getimag());
}
Complejo inverse(Complejo z)
{
return Complejo(z.getreal()/z.getmodule2(), -z.getimag()/z.getmodule2());
}
Complejo sqrt(Complejo z)
{
return Complejo(sqrt(z.getmodule())*cos(z.getphase()/2.0),
sqrt(z.getmodule())*sin(z.getphase()/2.0)) ;
}
Complejo log(Complejo z)
{
return Complejo(log(z.getmodule()), z.getphase());
}
4.12. COMPILACIÓN Y DEBUGGING. 155
-o exename define el nombre del ejecutable creado, en lugar del por defecto a.out.
g++ -o outputfile filename.cc
-On optimización de grado n que puede tomar valores de 1 (por defecto) a 3. El objetivo
inicial del compilador es reducir el tiempo de la compilación. Con -On, el compilador
trata de reducir el tamaño del ejecutable y el tiempo de ejecución, con n se aumenta el
grado de optimización.
156 CAPÍTULO 4. UNA BREVE INTRODUCCIÓN A C++.
-Wall notifica todos los posibles warnings en el código que está siendo compilado.
Gráfica.
versión 4.12, 24 de Octubre del 2003
En este capı́tulo quisiéramos mostrar algunas de las posibilidades gráficas presentes en Li-
nux. Nuestra intensión es cubrir temas como la visualización, conversión, captura y creación
de archivos gráficos. Sólo mencionaremos las aplicaciones principales en cada caso centrándo-
nos en sus posibilidades más que en su utilización especı́fica, ya que la mayorı́a posee una
interfase sencilla de manejar y una amplia documentación.
157
158 CAPÍTULO 5. GRÁFICA.
de entender la mayorı́a de los formatos más usados. Entre estos programas podemos mencio-
nar: Eye of Gnome (eog), Electric Eyes (eeyes), kview o display. Podemos mencionar que
aplicaciones como display entienden sobre ochenta formatos gráficos distintos entre los que
se encuentran la mayorı́a de los formatos conocidos más otros como ps, eps, pdf, fig, html,
entre muchos otros. Una mención especial merece el utilitario gthumb que nos permite hacer
un preview de un directorio con muchas imagenes de manera muy fácil.
correlativo, crea una secuencia animada con imágenes que persisten por 20 centésimas de
segundos en un formato conocido como mng.
permite exportar en formato que entienden los programas vectoriales (fig, por ejemplo)
luego podemos leer el archivo y editarlo. Además, xfig permite importar e incluir imágenes
del tipo bitmap, agregando riqueza a los diagramas que puede generar.
Una caracterı́stica importante de este tipo de programas es que trabajen por capas, las
cuales son tratadas independientemente, uno puede poner un objeto sobre otro o por debajo
de otro logrando diferentes efectos. Algunos programas de presentación gráficos basados en
LATEX y pdf están utilizando esta capacidad en xfig para lograr animaciones de imágenes.
Finalmente el programa xfig permite construir una biblioteca de objetos reutilizables
ahorrando mucho trabajo. Por ejemplo, si uno dibuja los elementos de un circuito eléctrico y
los almacena en el lugar de las bibliotecas de imágenes podrá incluir estos objetos en futuros
trabajos. El programa viene con varias bibliotecas de objetos listas para usar.
jrogan@huelen:~$ gnuplot
G N U P L O T
Version 3.7 patchlevel 2
last modified Sat Jan 19 15:23:37 GMT 2002
System: Linux 2.4.19
gnuplot> replot
gnuplot> set terminal postscript
Terminal type set to ’postscript’
Options are ’landscape noenhanced monochrome dashed defaultplex "Helvetica" 14’
gnuplot> set output "mygraph.ps"
gnuplot> replot
gnuplot> set terminal X
Terminal type set to ’X11’
Options are ’0’
gnuplot> set xrange[-2:2]
gnuplot> set yrange[-2:2]
gnuplot> splot exp(-x*x-y*y)
gnuplot> plot "myfile.dat" w l
gnuplot> exit
jrogan@huelen:~$
En el caso de xmgrace y SciGraphica mucho más directo manejarlo ya que está ba-
sado en menús. Además, existe abundante documentación de ambos softwares. El software
SciGraphica es una aplicación de visualización y análisis de data cientı́fica que permite el
despliegue de gráficos en 2 y 3 dimensiones, además, exporta los resultados a formato Pos-
tScript. Realmente esta aplicación nació como un intento de clonar el programa comercial
origen no disponible para Linux.
#!/bin/bash
g++ -Wall -O3 -o $1 $1.cc -I. -I$HOME/iglu/ \
-L/usr/X11R6/lib/ -L$HOME/iglu/ -liglu -lX11 -lm
/* Ejemplo: sen(x) */
#include ‘‘iglu.h’’
#include <cmath>
int main()
{
IgluDibujo v(‘‘Funcion Seno’’);
const int N=100;
double x[N], y[N];
162 CAPÍTULO 5. GRÁFICA.
v.map_coordinates(0,2*M_PI,-1.2,1.2);
double dx = 2*M_PI/(N-1);
for (int i=0;i<N;i++){
x[i] = i*dx;
y[i] = sin(x[i]);
}
v.plot_line(x,y,N);
v.wait();
return 0;
}
// Ejemplo sen(x-vt)
#include "iglu.h"
#include <cmath>
int main(){
IgluDibujo v(‘‘Pelicula’’);
const int N=100, Nt=100;
double x[N], y[N];
v.map_coordinates(0,2*M_PI,-1.2,1.2);
double dx = 2*M_PI/(N-1), dt = 1, t=0;
for (int j=0;j<Nt;j++){
v.clean();
t += dt;
for (int i=0;i<N;i++){
x[i] = i*dx;
y[i] = sin(x[i]-.1*t);
}
v.plot_line(x,y,N);
v.wait(.03);
v.flush();
}
v.wait();
return 0;
}
Capı́tulo 6
El sistema de preparación de
documentos TEX .
versión 5.0, 30 de Julio del 2003
6.1. Introducción.
TEX es un procesador de texto o, mejor dicho, un avanzado sistema de preparación de
documentos, creado por Donald Knuth, que permite el diseño de documentos de gran cali-
dad, conteniendo textos y fórmulas matemáticas. Años después, LATEX fue desarrollado por
Leslie Lamport, facilitando la preparación de documentos en TEX, gracias a la definición de
“macros” o conjuntos de comandos de fácil uso.
LATEX tuvo diversas versiones hasta la 2.09. Actualmente, LATEX ha recibido importantes
modificaciones, siendo la distribución actualmente en uso y desarrollo LATEX 2ε , una versión
transitoria en espera de que algún dı́a se llegue a la nueva versión definitiva de LATEX, LATEX3.
En estas páginas cuando digamos LATEX nos referiremos a la versión actual, LATEX 2ε . Cuan-
do queramos hacer referencia a la versión anterior, que deberı́a quedar progresivamente en
desuso, diremos explı́citamente LATEX 2.09.
6.2. Archivos.
El proceso de preparación de un documento LATEX consta de tres pasos:
a) dvi. Es el archivo procesado que podemos ver en pantalla o imprimir. Una vez
compilado, este archivo puede ser enviado a otro computador, para imprimir en
otra impresora, o verlo en otro monitor, independiente de la máquina (de donde
su extensión dvi, device independent).
163
164 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
\documentclass[12pt]{article}
\begin{document}
\end{document}
Haremos algunas precisiones respecto a la primera lı́nea más tarde. Lo importante es que
una lı́nea de esta forma debe ser la primera de nuestro archivo. Todo lo que se encuentra
antes de \begin{document} se denomina preámbulo. El texto que queramos escribir va entre
\begin{document} y \end{document}. Todo lo que se encuentre después de \end{document}
es ignorado.
6.3.2. Caracteres.
Pueden aparecer en nuestro texto todos los caracteres del código ASCII no extendido
(teclado inglés usual): letras, números y los signos de puntuación:
. : ; , ? ! ‘ ’ ( ) [ ] - / * @
# $ % & ~ _ ^ \ { }
tienen un significado especı́fico para LATEX. Algunos de ellos se pueden obtener anteponiéndo-
les un backslash:
# \# $ \$ % \% & \& { \{ } \}
Los caracteres
+ = | < >
6.3. INPUT BÁSICO. 165
6.3.3. Comandos.
Todos los comandos comienzan con un backslash, y se extienden hasta encontrar el primer
carácter que no sea una letra (es decir, un espacio, un número, un signo de puntuación o
matemático, etc.).
a) Observemos la siguiente palabra: fino. Esta palabra fue generada escribiendo simple-
mente fino, pero observemos que las letras ‘f’ e ‘i’ no están separadas, sino que unidas
artı́sticamente. Esto es una ligadura, y es considerada una práctica estéticamente pre-
ferible. LATEX sabe esto e inserta este pequeño efecto tipográfico sin que nos demos
cuenta.
b) Las comillas de apertura y de cierre son distintas. Por ejemplo: ‘insigne’ (comillas
simples) o “insigne” (comillas dobles). Las comillas de apertura se hacen con uno o con
dos acentos graves (‘), para comillas simples o dobles, respectivamente, y las de cierre
con acentos agudos (’): ‘insigne’, ‘‘insigne’’. No es correcto entonces utilizar las
comillas dobles del teclado e intentar escribir "insigne" (el resultado de esto es el poco
estético ”insigne”).
d) Énfasis de texto:
a) $x+y=3$
b) $$xy=8$$
c) \begin{equation}
x/y=5
\end{equation}
6.3.7. Comentarios.
Uno puede hacer que el compilador ignore parte del archivo usando %. Todo el texto desde
este carácter hasta el fin de la lı́nea correspondiente será ignorado (incluyendo el fin de lı́nea).
Un pequeño comentario. Un peque{\~n}o co% Texto ignorado
mentario.
6.3.10. Tı́tulo.
Un tı́tulo se genera con:
a) Sin tı́tulo:
\title{}
b) Sin autor:
\author{}
c) Sin fecha:
\date{}
e) Más de un autor:
Para artı́culos cortos, LATEX coloca el tı́tulo en la parte superior de la primera página
del texto. Para artı́culos largos, en una página separada.
170 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
6.3.11. Secciones.
Los tı́tulos de las distintas secciones y subsecciones de un documento (numerados ade-
cuadamente, en negrita, como en este texto) se generan con comandos de la forma:
\section{Una secci\’on}
\subsection{Una subsecci\’on}
6.3.12. Listas.
Los dos modos usuales de generar listas:
a) Listas numeradas (ambiente enumerate):
\begin{enumerate}
1. Nivel 1, ı́tem 1. \item Nivel 1, \’{\i}tem 1.
\item Nivel 1, \’{\i}tem 2.
2. Nivel 1, ı́tem 2.
\begin{enumerate}
a) Nivel 2, ı́tem 1. \item Nivel 2, \’{\i}tem 1.
\begin{enumerate}
1) Nivel 3, ı́tem 1. \item Nivel 3, \’{\i}tem 1.
3. Nivel 1, ı́tem 3. \end{enumerate}
\end{enumerate}
\item Nivel 1, \’{\i}tem 3.
\end{enumerate}
Es posible anidar hasta tres niveles de listas. Cada uno usa tipos distintos de rótulos,
según el ambiente usado: números arábes, letras y números romanos para enumerate, y
puntos, guiones y asteriscos para itemize. Los rótulos son generados automáticamente por
cada \item, pero es posible modificarlos agregando un parámetro opcional:
\rm es el default para texto normal; \it es el default para texto enfatizado; \bf es el
default para tı́tulos de capı́tulos, secciones, subsecciones, etc.
\textrm, \textbf, etc., sólo permiten cambiar porciones definidas del texto, contenido
entre los paréntesis cursivos. Con \rm, \bf, etc. podemos, omitiendo los paréntesis, cambiar
el font en todo el texto posterior:
Un cambio local de fonts y uno Un cambio {\sf local} de fonts
global, interminable e infini- \sl y uno global, interminable
to. . . e infinito...
También es posible tener combinaciones de estos fonts, por ejemplo, bold italic, pero no
sirven los comandos anteriores, sino versiones modificadas de \rm, \bf, etc.:
\rmfamily
\sffamily
\ttfamily
\mdseries
172 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
\bfseries
\upshape
\itshape
\slshape
\scshape
Por ejemplo:
Para entender el uso de estos comandos hay que considerar que un font tiene tres atributos:
family (que distingue entre rm, sf y tt), series (que distingue entre md y bf), y shape (que
distingue entre up, it, sl y sc). Cada uno de los comandos \rmfamily, \bfseries, etc.,
cambia sólo uno de estos atributos. Ello permite tener versiones mixtas de los fonts, como
un slanted sans serif, imposible de obtener usando los comandos \sl y \sf. Los defaults para
el texto usual son: \rmfamily, \mdseries y \upshape.
Tamaño.
Los tamaños de letras disponibles son:
texto \small
Se usan igual que los comandos de cambio de font \rm, \sf, etc., de la sección 6.3.13.
\normalsize es el default para texto normal; \scriptsize para sub o supraı́ndices;
\footnotesize para notas a pie de página.
ó \’o õ \~o ǒ \v o o̧ \c o
ò \‘o ō \=o ő \H o o. \d o
ô \^o ȯ \. o o⁀ o \t{oo} o \b o
¯
ö \"o ŏ \u o o̊ \r o
Cuadro 6.1: Acentos.
† \dag œ \oe l \l
‡ \ddag Œ \OE L \L
§ \S æ \ae ß \ss
¶ \P Æ \AE SS \SS
c \copyright å \aa ¿ ?‘
a \textcircled a Å \AA ¡ !‘
\textvisiblespace ø \o
£ \pounds Ø \O
Cuadro 6.2: Sı́mbolos especiales y caracteres no ingleses.
LATEX emplea sólo los caracteres ASCII básicos, que no contienen sı́mbolos castellanos
como ¿, ¡, ñ, etc. Ya hemos visto que existen comandos que permiten imprimir estos caracteres,
y por tanto es posible escribir cualquier texto en castellano (y otros idiomas, de hecho).
Sin embargo, esto no resuelve todo el problema, porque en inglés y castellano las palabras
se cortan en “sı́labas” de acuerdo a reglas distintas, y esto es relevante cuando se debe cortar el
texto en lı́neas. LATEX tiene incorporados algoritmos para cortar palabras en inglés y, si se ha
hecho una instalación especial de LATEX en nuestro computador, también en castellano u otros
idiomas (a través del programa babel, que es parte de la distribución standard de LATEX 2ε ).
En un computador con babel instalado y configurado para cortar en castellano basta incluir el
comando \usepackage[spanish]{babel} en el preámbulo para poder escribir en castellano
cortando las palabras en sı́labas correctamente.3
Sin embargo, ocasionalmente LATEX se encuentra con una palabra que no sabe cortar,
en cuyo caso no lo intenta y permite que ella se salga del margen derecho del texto, o bien
toma decisiones no óptimas. La solución es sugerirle a LATEX la silabación de la palabra. Por
ejemplo, si la palabra conflictiva es matem\’aticas (generalmente hay problemas con las
palabras acentuadas), entonces basta con reescribirla en la forma: ma\-te\-m\’a\-ti\-cas.
Con esto, le indicamos a LATEX en qué puntos es posible cortar la palabra. El comando \- no
tiene ningún otro efecto, de modo que si la palabra en cuestión no queda al final de la lı́nea,
LATEX por supuesto ignora nuestra sugerencia y no la corta.
Consideremos el siguiente ejemplo:
3
Esto resuelve también otro problema: los encabezados de capı́tulos o ı́ndices, por ejemplo, son escritos
“Capı́tulo” e “Índice”, en vez de “Chapter” e “Index”, y cuando se usa el comando \date, la fecha aparece
en castellano.
174 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
6.4.2. Fracciones.
a) Horizontales
n/2 n/2
b) Verticales
1
\frac{1}{2}, \frac 1{2}, \frac{1}2 ó \frac 12
2
y + z/2
x= x = \frac{y + z/2}{y^2+1}
y2 + 1
x+y
y \frac{x+y}{1 + \frac y{z+1}}
1 + z+1
6.4. FÓRMULAS MATEMÁTICAS. 175
La forma a) es más adecuada y la preferida para fracciones dentro del texto, y la se-
gunda para fórmulas separadas. \frac puede aparecer en fórmulas dentro del texto ( 12 con
$\frac 12$), pero esto es inusual y poco recomendable estéticamente, salvo estricta necesi-
dad.
6.4.3. Raı́ces.
√
n \sqrt{n} ó \sqrt n
√
a2 + b 2 \sqrt{a^2 + b^2}
√
n
2 \sqrt[n]{2}
b) ··· \cdots
Entre sı́mbolos como +, −, = :
..
c) . \vdots
x1
..
.
xn
..
d) . \ddots
1 0 ··· 0
0 1 0
In×n = .. . . ..
. . .
0 0 ... 1
No corresponde usar tres puntos seguidos (...), pues el espaciado entre puntos es incorrecto.
176 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Minúsculas
α \alpha θ \theta o o τ \tau
β \beta ϑ \vartheta π \pi υ \upsilon
γ \gamma ι \iota ̟ \varpi φ \phi
δ \delta κ \kappa ρ \rho ϕ \varphi
ǫ \epsilon λ \lambda ̺ \varrho χ \chi
ε \varepsilon µ \mu σ \sigma ψ \psi
ζ \zeta ν \nu ς \varsigma ω \omega
η \eta ξ \xi
Mayúsculas
Γ \Gamma Λ \Lambda Σ \Sigma Ψ \Psi
∆ \Delta Ξ \Xi Υ \Upsilon Ω \Omega
Θ \Theta Π \Pi Φ \Phi
Pn R1
i=1 xi = 0 f $\sum_{i=1}^n x_i = \int_0^1 f $
6.4.9. Matrices.
Ambiente array.
Se construyen con el ambiente array. Consideremos, por ejemplo:
a + b + c uv 27
a+b u+v 134
a 3u + vw 2.978
La primera columna está alineada al centro (c, center); la segunda, a la izquierda (l, left); la
tercera, a la derecha (r, right). array tiene un argumento obligatorio, que consta de tantas
letras como columnas tenga la matriz, letras que pueden ser c, l o r según la alineación
que queramos obtener. Elementos consecutivos de la misma lı́nea se separan con & y lı́neas
consecutivas se separan con \\. Ası́, el ejemplo anterior se obtiene con:
\begin{array}{clr}
a+b+c & uv & 27 \\
a+b & u + v & 134 \\
a & 3u+vw & 2.978
\end{array}
Delimitadores.
Un delimitador es cualquier sı́mbolo que actúe como un paréntesis, encerrando una ex-
presión, apareciendo a la izquierda y a la derecha de ella. La Tabla 6.10 muestra todos los
delimitadores posibles.
Para que los delimitadores tengan el tamaño correcto para encerrar la expresión correspon-
diente hay que anteponerles \left y \right. Podemos obtener ası́ expresiones matriciales:
\left(\begin{array}{cc}
a b a&b\\
c d c&d
\end{array}\right)
v = \left(\begin{array}{c}
1 1\\
v= 2 2\\
3 3
\end{array}\right)
180 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
( ( ) ) ↑ \uparrow
[ [ ] ] ↓ \downarrow
{ \{ } \} l \updownarrow
⌊ \lfloor ⌋ \rfloor ⇑ \Uparrow
⌈ \lceil ⌉ \rceil ⇓ \Downarrow
h \langle i \rangle m \Updownarrow
/ / \ \backslash
| | k \|
\Delta = \left|\begin{array}{cc}
a a
∆ = 11 12 a_{11} & a_{12}\\
a21 a22
a_{21} & a_{22}
\end{array}\right|
\left y \right deben ir de a pares, pero los delimitadores no tienen por qué ser los
mismos:
\left(\begin{array}{c}
a a\\
b b
\end{array}\right[
Tampoco es necesario que los delimitadores encierren matrices. Comparemos, por ejemplo:
~ + B)
~ =( dF~
(A )x=a (\vec A + \vec B) =
dx
( \frac{d \vec F}{dx} )_{x=a}
!
~+B
~ =
dF~
A \left(\vec A + \vec B\right) =
dx
x=a \left( \frac{d \vec F}{dx} \right)_{x=a}
b
b
df
Z
dx = f (x) \left. \int_a^b dx \frac{df}{dx} =
a dx a f(x) \right |_a^b
x = a+b+c+ \begin{eqnarray*}
d+e x& = & a + b + c +\\
&& d + e
\end{eqnarray*}
El asterisco impide que aparezcan números en las ecuaciones. Si deseamos que numere
cada lı́nea como una ecuación independiente, basta omitir el asterisco:
x = 5 (6.2) \begin{eqnarray}
a + b = 60 (6.3) x& = & 5 \\
a + b&= & 60
\end{eqnarray}
Si queremos que solamente algunas lı́neas aparezcan numeradas, usamos \nonumber:
x = a+b+c+ \begin{eqnarray}
d+e (6.4) x& = & a + b + c + \nonumber\\
&& d + e
\end{eqnarray}
El comando \eqnarray es suficiente para necesidades sencillas, pero cuando se requiere
escribir matemática de modo intensivo sus limitaciones comienzan a ser evidentes. Al agre-
gar al preámbulo de nuestro documento la lı́nea \usepackage{amsmath} quedan disponibles
muchos comandos mucho más útiles para textos matemáticos más serios, como el ambiente
equation*, \split, \multline o \intertext. En la sección 6.8.2 se encuentra una descrip-
ción de estos y otros comandos.
6.4.10. Acentos.
Dentro de una fórmula pueden aparecer una serie de “acentos”, análogos a los de texto
usual (Tabla 6.11).
Las letras i y j deben perder el punto cuando son acentuadas: ~i es incorrecto. Debe ser ~ı.
\imath y \jmath generan las versiones sin punto de estas letras:
~ı + ̂ \vec \imath + \hat \jmath
182 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Bastante más óptimo es utilizar el comando \text, disponible a través de amsmath (sección
6.8.2).
6.4.13. Fonts.
Análogamente a los comandos para texto usual (Sec. 6.3.13), es posible cambiar los fonts dentro
del modo matemático:
(A, x) \mathrm{(A,x)}
(A, x) \mathnormal{(A,x)}
(A, B) \mathcal{(A,B)}
(A, x) \mathbf{(A,x)}
(A, x) \mathsf{(A,x)}
(A, x) \mathtt{(A,x)}
(A, x ) \mathit{(A,x)}
Ã × 1 \mathbf{\tilde A \times 1}
Como en todo ambiente matemático, los espacios entre caracteres son ignorados:
Hola \mathrm{H o l a}
Finalmente, observemos que \mathit corresponde al font itálico, en tanto que \mathnormal al
font matemático usual, que es también itálico. . . o casi:
6.5. TABLAS. 183
6.5. Tablas.
array nos permitió construir matrices en modo matemático. Para tablas de texto existe tabular,
que funciona de la misma manera. Puede ser usado tanto en modo matemático como fuera de él.
\begin{tabular}{lcl}
Nombre : Juan Pérez Nombre&:&Juan P\’erez\\
Edad : 26 Edad&:&26\\
Profesión : Estudiante Profesi\’on&:&Estudiante
\end{tabular}
Si deseamos agregar lı́neas verticales y horizontales para ayudar a la lectura, lo hacemos inser-
tando | en los puntos apropiados del argumento de tabular, y \hline al final de cada lı́nea de la
tabla:
\begin{tabular}{|l|r|}\hline
Item Gastos Item&Gastos\\ \hline
Vasos $ 500 Vasos& \$ 500 \\
Botellas $ 1300 Botellas & \$ 1300 \\
Platos $ 500 Platos & \$ 500 \\ \hline
Total $ 2300 Total& \$ 2300 \\ \hline
\end{tabular}
diferentes. Ası́, euler, eq:euler, euler_1, euler1, Euler, etc., son etiquetas válidas y distintas.
Podemos usar \label dentro de equation, eqnarray y enumerate.
También podemos referenciar páginas con \pageref:
LATEX puede dar cuenta de las referencias cruzadas gracias al archivo aux (auxiliar) generado
durante la compilación.
Al compilar por primera vez el archivo, en el archivo aux es escrita la información de los \label
encontrados. Al compilar por segunda vez, LATEX lee el archivo aux e incorpora esa información al
dvi. (En realidad, también lo hizo la primera vez que se compiló el archivo, pero el aux no existı́a
entonces o no tenı́a información útil.)
Por tanto, para obtener las referencias correctas hay que compilar dos veces, una para generar
el aux correcto, otra para poner la información en el dvi. Toda modificación en la numeración
tendrá efecto sólo después de compilar dos veces más. Por cierto, no es necesario preocuparse de
estos detalles a cada momento. Seguramente compilaremos muchas veces el archivo antes de tener
la versión final. En todo caso, LATEX avisa, tras cada compilación, si hay referencias inexistentes
u otras que pudieron haber cambiado, y sugiere compilar de nuevo para obtener las referencias
correctas. (Ver Sec. 6.14.2.)
\usepackage{<package>}
\usepackage{<package1>,<package2>}
\usepackage{<package1>}
\usepackage{<package2>}
Algunos paquetes aceptan opciones adicionales (del mismo modo que la clase article acepta
la opción 12pt):
\usepackage[option1,option2]{<package1>}
6.8.1. babel
Permite el procesamiento de textos en idiomas distintos del inglés. Esto significa, entre otras
cosas, que se incorporan los patrones de silabación correctos para dicho idioma, para cortar adecua-
damente las palabras al final de cada lı́nea. Además, palabras claves como “Chapter”, “Index”, “List
of Figures”, etc., y la fecha dada por \date, son cambiadas a sus equivalentes en el idioma escogido.
La variedad de idiomas disponibles es enorme, pero cada instalación de LATEX tiene sólo algunos de
ellos incorporados. (Ésta es una decisión que toma el administrador del sistema, de acuerdo a las
necesidades de los usuarios. Una configuración usual puede ser habilitar la compilación en inglés,
castellano, alemán y francés.)
Ya sabemos como usar babel para escribir en castellano: basta incluir en el preámbulo la lı́nea
\usepackage[spanish]{babel}
186 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
6.8.2. AMS-LATEX
El paquete amsmath permite agregar comandos para escritura de textos matemáticos profe-
sionales, desarrollados originalmente por la American Mathematical Society. Si un texto contiene
abundante matemática, entonces seguramente incluir la lı́nea correspondiente en el preámbulo:
\usepackage{amsmath}
aliviará mucho la tarea. He aquı́ algunas de las caracterı́sticas adicionales disponibles con AMS-
LATEX.
x = 2y − 3 \begin{equation*}
x = 2y - 3
\end{equation*}
multline permite dividir una ecuación muy larga en varias lı́neas, de modo que la primera lı́nea
quede alineada con el margen izquierdo, y la última con el margen derecho:
15
X \begin{multline}
= 1 + 2 + 3 + 4 + 5+ \sum_{i=1}^{15} = 1 +2+3+4+5+\\
i=1 6+7+8+9+10+\\
6 + 7 + 8 + 9 + 10+ 11+12+13+14+15
11 + 12 + 13 + 14 + 15 (6.6) \end{multline}
align permite reunir un grupo de ecuaciones consecutivas alineándolas (usando &, igual que la
alineación vertical de tabular y array). gather hace lo mismo, pero centrando cada ecuación en
la página independientemente.
a1 = b1 + c1 (6.7) \begin{align}
a2 = b2 + c2 − d2 + e2 (6.8) a_1 &= b_1 + c_1 \\
a_2 &= b_2 + c_2 - d_2 + e_2
\end{align}
a1 = b1 + c1 (6.9) \begin{gather}
a2 = b2 + c2 − d2 + e2 (6.10) a_1 = b_1 + c_1 \\
a_2 = b_2 + c_2 - d_2 + e_2
\end{gather}
Con multline*, align* y gather* se obtienen los mismos resultados, pero con ecuaciones no
numeradas.
split permite escribir una sola ecuación separada en lı́neas (como multline), pero permite
alinear las lı́neas con & (como align). split debe ser usado dentro de un ambiente como equation,
align o gather (o sus equivalentes con asterisco):
6.8. ALGUNAS HERRAMIENTAS IMPORTANTES 187
a1 = b1 + c1 \begin{equation}
(6.11)
= b2 + c2 − d2 + e2 \begin{split}
a_1& = b_1 + c_1 \\
& = b_2 + c_2 - d_2 + e_2
\end{split}
\end{equation}
Espacio horizontal
\quad y \qquad insertan espacio horizontal en ecuaciones:
x>y , ∀x ∈ A \begin{gather*}
x≤z , ∀z ∈ B x > y \ , \quad \forall\, x \in A \\
x \leq z \ , \qquad \forall\, z \in B
\end{gather*}
Texto en ecuaciones
Para agregar texto a una ecuación, usamos \text:
\begin{equation*}
x = 2n − 1 , con n entero x = 2^n - 1 \ , \quad \text{con $n$ entero}
\end{equation*}
\text se comporta como un buen objeto matemático, y por tanto se pueden agregar subı́ndices
textuales más fácilmente que con \mbox (ver sección 6.4.11):
Vcrı́tico $V_{\text{cr\’{\i}tico}}$
Referencia a ecuaciones
\eqref es equivalente a \ref, salvo que agrega los paréntesis automáticamente:
La ecuación (6.5) era la de Eu- La ecuaci\’on \eqref{euler} era la de Euler.
ler.
x1 = a + b + c , \begin{align*}
x2 = d + e , x_1 &= a + b + c \ , \\
x_2 &= d + e \ , \\
y por otra parte \intertext{y por otra parte}
x_3 &= f + g + h \ .
x3 = f + g + h . \end{align*}
\begin{pmatrix}
a b a&b\\
c d c&d
\end{pmatrix}
\Delta = \begin{vmatrix}
a11 a12
∆ = a_{11} & a_{12}\\
a21 a22 a_{21} & a_{22}
\end{vmatrix}
k
v= v = \binom{k}{2}
2
Podemos observar que el espaciado entre los paréntesis y el resto de la fórmula es más adecuado
que el de los ejemplos en la sección 6.4.9.
Flechas extensibles
Las flechas en la tabla 6.6 vienen en ciertos tamaños predefinidos. amsmath proporciona fle-
chas extensibles \xleftarrow y \xrightarrow, para ajustar sub o superı́ndices demasiado anchos.
Además, tienen un argumento opcional y uno obligatorio, para colocar material sobre o bajo ellas:
n+µ−1 n±i−1
A ←−−−− B −−−−→ C −
→D
T U
C \mathbb{C} para los números complejos I \mathbb{I} para los números imaginarios
R \mathbb{R} para los números reales Q \mathbb{Q} para los números racionales
Z \mathbb{Z} para los números enteros N \mathbb{N} para los números naturales
6.8. ALGUNAS HERRAMIENTAS IMPORTANTES 189
6.8.3. fontenc
Ocasionalmente, LATEX tiene problemas al separar una palabra en sı́labas. Tı́picamente, eso
ocurre con palabras acentuadas, pues, debido a la estructura interna del programa, un carácter
como la “á” en “matemáticas” no es tratado igual que los otros. Para solucionar el problema, y
poder cortar en sı́labas palabras que contengan letras acentuadas (además de acceder a algunos
caracteres adicionales), basta incluir el paquete fontenc:
\usepackage[T1]{fontenc}
Técnicamente, lo que ocurre es que la codificación antigua para fonts es la OT1, que no contiene
fonts acentuados, y que por lo tanto es útil sólo para textos en inglés. La codificación T1 aumenta los
fonts disponibles, permitiendo que los caracteres acentuados sean tratados en igual pie que cualquier
otro.
6.8.4. enumerate
enumerate.sty define una muy conveniente extensión al ambiente enumerate de LATEX. El
comando se usa igual que siempre (ver sección 6.3.12), con un argumento opcional que determina
el tipo de etiqueta que se usará para la lista. Por ejemplo, si queremos que en vez de números se
usen letras mayúsculas, basta usar \begin{enumerate}[A]:
A Primer ı́tem.
B Segundo ı́tem.
Si deseamos insertar un texto que no cambie de una etiqueta a otra, hay que encerrarlo entre
paréntesis cursivos (\begin{enumerate}[{Caso} A:]):
6.8.5. Color.
A través de PostScript es posible introducir color en documentos LATEX. Para ello, incluimos en
el preámbulo el paquete color.sty:
\usepackage{color}
190 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
De este modo, está disponible el comando \color, que permite especificar un color, ya sea por
nombre (en el caso de algunos colores predefinidos), por su código rgb (red-green-blue) o código
cmyk (cian-magenta-yellow-black). Por ejemplo:
Un texto en un
{\color[cmyk]{.3,.5,.75,0} tercer color}
Los colores más frecuentes (azul, amarillo, rojo, etc.) se pueden dar por nombre, como en este
ejemplo. Si se da el código rgb, se deben especificar tres números entre 0 y 1, que indican la cantidad
de rojo, verde y azul que constituyen el color deseado. En el ejemplo, le dimos máxima cantidad de
rojo y azul, y nada de verde, con lo cual conseguimos un color violeta. Si se trata del código cmyk los
números a especificar son cuatro, indicando la cantidad de cian, magenta, amarillo y negro. En el
ejemplo anterior pusimos una cantidad arbitraria de cada color, y resultó un color café. Es evidente
que el uso de los códigos rgb y cmyk permite explorar infinidad de colores.
Observar que \color funciona de modo análogo a los comandos de cambio de font de la sección
6.3.13, de modo que si se desea restringir el efecto a una porción del texto, hay que encerrar dicho
texto entre paréntesis cursivos. Análogamente al caso de los fonts, existe el comando \textcolor,
que permite dar el texto a colorear como argumento:
Un texto en un
\textcolor[cmyk]{.3,.5,.75,0}{tercer color}
\pagenumbering{arabic}
\pagenumbering{roman}
6.9. MODIFICANDO EL ESTILO DE LA PÁGINA. 191
arabic es el default.
b) Estilo de página.
El comando \pagestyle determina dónde queremos que vayan los números de página:
Corte de lı́neas.
En la página 173 ya vimos un ejemplo de inducción de un corte de lı́nea en un punto deseado
del texto, al dividir una palabra en sı́labas.
Cuando el problema no tiene relación con sı́labas disponemos de dos comandos:
Observemos cómo en el segundo caso, en que se usa \linebreak, la separación entre palabras
es alterada para permitir que el texto respete los márgenes establecidos.
192 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Corte de páginas.
\clearpage, sin embargo, no siempre tiene efectos visibles. Dependiendo de la cantidad y tipo
de texto que quede en la página, los espacios verticales pueden o no ser ajustados, y si no lo son,
el resultado termina siendo equivalente a un \newpage. TEX decide en última instancia qué es lo
óptimo.
Adicionalmente, tenemos el comando:
a) Unidades.
cm centı́metro
mm milı́metro
in pulgada
pt punto (1/72 pulgadas)
em ancho de una “M” en el font actual
ex altura de una “x” en el font actual
Las cuatro primeras unidades son absolutas; las últimas dos, relativas, dependiendo del ta-
maño del font actualmente en uso.
b) Cambio de longitudes.
TEX almacena los valores de las longitudes relevantes al texto en comandos especiales:
6.9. MODIFICANDO EL ESTILO DE LA PÁGINA. 193
\parindent Sangrı́a.
Todas estas variables son modificables con los comandos \setlength, que le da a una variable
un valor dado, y \addtolength, que le suma a una variable la longitud especificada. Por
ejemplo:
Por default, el ancho y altura del texto, y los márgenes izquierdo y superior, están definidos
de modo que quede un espacio de una pulgada (≃ 2.56 cm) entre el borde del texto y el borde
de la página.
Un problema tı́pico es querer que el texto llene un mayor porcentaje de la página. Por ejem-
plo, para que el margen del texto en los cuatro costados sea la mitad del default, debemos
introducir los comandos:
\addtolength{\textwidth}{1in}
\addtolength{\textheight}{1in}
\addtolength{\oddsidemargin}{-.5in}
\addtolength{\topmargin}{-.5in}
Las dos primeras lı́neas aumentan el tamaño horizontal y vertical del texto en 1 pulgada.
Si luego restamos media pulgada del margen izquierdo y el margen superior, es claro que la
distancia entre el texto y los bordes de la página sera de media pulgada, como deseábamos.
c) Espacios verticales y horizontales.
Se insertan con \vspace y \hspace:
Algunos ejemplos:
Un primer párrafo de un pe- Un primer p\’arrafo de un
queño texto. peque\~no texto.
\vspace{1cm}
Y un segundo párrafo separa- Y un segundo p\’arrafo
do del otro. separado del otro.
194 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Si por casualidad el espacio vertical impuesto por \vspace debiese ser colocado al comienzo
de una página, TEX lo ignora. Serı́a molesto visualmente que en algunas páginas el texto
comenzara algunos centı́metros más abajo que en el resto. Lo mismo puede ocurrir si el
espacio horizontal de un \hspace queda al comienzo de una lı́nea.
Los comandos \vspace*{<longitud>} y \hspace*{<longitud>} permiten que el espacio en
blanco de la <longitud> especificada no sea ignorado. Ello es útil cuando invariablemente
queremos ese espacio vertical u horizontal, aunque sea al comienzo de una página o una lı́nea
—por ejemplo, para insertar una figura.
6.10. Figuras.
Lo primero que hay que decir en esta sección es que LATEX es un excelente procesador de texto,
tanto convencional como matemático. Las figuras, sin embargo, son un problema aparte.
LATEX provee un ambiente picture que permite realizar dibujos simples. Dentro de la estruc-
tura \begin{picture} y \end{picture} se pueden colocar una serie de comandos para dibujar
lı́neas, cı́rculos, óvalos y flechas, ası́ como para posicionar texto. Infortunadamente, el proceso de
ejecutar dibujos sobre un cierto umbral de complejidad puede ser muy tedioso para generarlo di-
rectamente. Existe software (por ejemplo, xfig) que permite superar este problema, pudiéndose
dibujar con el mouse, exportando el resultado al formato picture de LATEX. Sin embargo, picture
tiene limitaciones (no se pueden hacer lı́neas de pendiente arbitraria), y por tanto no es una solución
óptima.
Para obtener figuras de buena calidad es imprescindible recurrir a lenguajes gráficos externos, y
LATEX da la posibilidad de incluir esos formatos gráficos en un documento. De este modo, tanto el
texto como las figuras serán de la más alta calidad. Las dos mejores soluciones son utilizar Metafont
o PostScript. Metafont es un programa con un lenguaje de programación gráfico propio. De hecho,
los propios fonts de LATEX fueron creados usando Metafont, y sus capacidades permiten hacer dibujos
de complejidad arbitraria. Sin embargo, los dibujos resultantes no son trivialmente reescalables, y
exige aprender un lenguaje de programación especı́fico.
Una solución mucho más versátil, y adoptada como el estándar en la comunidad de usuarios
de LATEX, es el uso de PostScript. Como se mencionó brevemente en la sección 1.13, al imprimir,
una máquina unix convierte el archivo a formato PostScript, y luego lo envı́a a la impresora. Pero
PostScript sirve más que para imprimir, siendo un lenguaje de programación gráfico completo, con
el cual podemos generar imágenes de gran calidad, y reescalables sin pérdida de resolución. Además,
muchos programas gráficos permiten exportar sus resultados en formato PostScript. Por lo tanto,
podemos generar nuestras figuras en alguno de estos programas (xfig es un excelente software, que
satisface la mayor parte de nuestras necesidades de dibujos simples; octave o gnuplot pueden ser
usados para generar figuras provenientes de cálculos cientı́ficos, etc.), lo cual creará un archivo con
extensión .ps (PostScript) o .eps (PostScript encapsulado).5 Luego introducimos la figura en el
documento LATEX, a través del paquete graphicx.
5
eps es el formato preferido, pues contiene información sobre las dimensiones de la figura, información
que es utilizada por LATEX para insertar ésta adecuadamente en el texto.
6.10. FIGURAS. 195
6.10.1. graphicx.sty
Si nuestra figura está en un archivo figura.eps, la instrucción a utilizar es:
\documentclass[12pt]{article}
\usepackage{graphicx}
\begin{document}
... Texto ...
\includegraphics[width=w, height=h]{figura.eps}
...
\end{document}
Los parámetros width y height son opcionales y puede omitirse uno para que el sistema escale
de acuerdo al parámetro dado. Es posible variar la escala completa de la figura o rotarla usando
comandos disponibles en graphicx.
\begin{center}
\includegraphics[height=3cm]{figura.eps}
\end{center}
\begin{figure}[h]
\begin{center}
\includegraphics[height=3cm]{figura.eps}
\end{center}
\caption{Un sujeto caminando.}
\label{caminando}
\end{figure}
196 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
da como resultado:
figure delimita lo que en TEX se denomina un objeto flotante, es decir, un objeto cuya posición
no está determinada a priori, y se ajusta para obtener los mejores resultados posibles. TEX considera
(de acuerdo con la tradición), que la mejor posición para colocar una figura es al principio o al final
de la página. Además, lo ideal es que cada página tenga un cierto número máximo de figuras,
que ninguna figura aparezca en el texto antes de que sea mencionada por primera vez, y que, por
supuesto, las figuras aparezcan en el orden en que son mencionadas. Éstas y otras condiciones
determinan la posición que un objeto flotante tenga al final de la compilación. Uno puede forzar la
decisión de LATEX con el argumento opcional de figure:
El argumento adicional ! suprime, para ese objeto flotante especı́fico, cualquier restricción que
exista sobre el número máximo de objetos flotantes en una página y el porcentaje de texto mı́nimo
que debe haber en una página.
Varios de estos argumentos se pueden colocar simultánemente, su orden dictando la prioridad.
Por ejemplo,
\begin{figure}[htbp]
...
\end{figure}
indica que la figura se debe colocar como primera prioridad aquı́ mismo; si ello no es posible, al
comienzo de página (ésta o la siguiente, dependiendo de los detalles de la compilación), y ası́ suce-
sivamente.
Además, figure numera automáticamente la figura, colocando el texto “Figura N :”, y \caption
permite colocar una leyenda, centrada en el texto, a la figura. Puesto que la numeración es automáti-
ca, las figuras pueden ser referidas simbólicamente con \label y \ref (sección 6.6). Para que la
referencia sea correcta, \label debe estar dentro del argumento de \caption, o después, como
aparece en el ejemplo de la Figura 6.1 (\ref{caminando}!).
Finalmente, notemos que la figura debió ser centrada explı́citamente con center. figure no
hace nada más que tratar la figura como un objeto flotante, proporcionar numeración y leyenda. El
resto es responsabilidad del autor.
6.11. CARTAS. 197
6.11. Cartas.
Para escribir cartas debemos emplear el estilo letter en vez del que hemos utilizado hasta
ahora, article. Comandos especiales permiten escribir una carta, poniendo en lugares adecuados
la dirección del remitente, la fecha, la firma, etc.
A modo de ejemplo, consideremos el siguiente input:
\documentclass[12pt]{letter}
\usepackage[spanish]{babel}
\begin{document}
\closing{Saludos,}
\cc{Arturo Prat \\ Luis Barrios}
\end{letter}
\end{document}
9 de Julio de 1998
Estimado Juan
Aún no tenemos novedades.
Parece increı́ble, pero los recientes acontecimientos nos han superado, a pesar
de nuestros esfuerzos. Esperamos que mejores tiempos nos aguarden.
Saludos,
Pedro Pérez
Secretario
Copia a: Arturo Prat
Luis Barrios
6.12. LATEX Y EL FORMATO PDF. 199
Observemos que el texto de la carta está dentro de un ambiente letter, el cual tiene un argu-
mento obligatorio, donde aparece el destinatario de la carta (con su dirección opcionalmente).
Los comandos disponibles son:
\address{<direccion>} <direccion> del remitente.
\begin{letter}{<destinatario 1>}
\opening<apertura 1>
...
\end{letter}
\begin{letter}{<destinatario 2>}
\address{<direccion remitente 2>}
\signature{<firma 2>}
\opening<apertura 2>
...
\end{letter}
\begin{letter}{<destinatario 3>}
\opening<apertura 3>
...
\end{letter}
\end{document}
dará origen a tres cartas con la misma dirección de remitente y firma, salvo la segunda.
En todos estos comandos, lı́neas sucesivas son indicadas con \\.
archivo pdf con LATEX es necesario compilarlo con pdflatex. Ası́, pdflatex <archivo> generará un
archivo <archivo>.pdf en vez del <archivo>.dvi generado por el compilador usual.
Si nuestro documento tiene figuras, sólo es posible incluirlas en el documento si están también
en formato pdf. Por tanto, si tenemos un documento con figuras en PostScript, debemos introducir
dos modificaciones antes de compilar con pdflatex:
b) Convertir las figuras PostScript a pdf (con epstopdf, por ejemplo). Si tenemos una figura en el
archivo <archivo_figura>.eps, entonces epstopdf <archivo_figura>.eps genera el archivo
correspondiente <archivo_figura>.pdf.
Observar que el mismo paquete graphicx descrito en la sección 6.10 para incluir figuras
PostScript permite, sin modificaciones, incluir figuras en pdf.
\newcommand{<comando>}{<accion>}
El caso más sencillo es cuando una estructura se repite frecuentemente en nuestro documento.
Por ejemplo, digamos que un sujeto llamado Cristóbal no quiere escribir su nombre cada vez que
aparece en su documento:
con el primer carácter que no es letra. Por tanto, \nombre Loyola ignora el espacio al final de
\nombre, y el output serı́a “CristóbalLoyola”.
También es posible definir comandos que funcionen en modo matemático:
Como \vel contiene un comando matemático (\dot), \vel sólo puede aparecer en modo ma-
temático.
Podemos también incluir la apertura de modo matemático en la definición de \vel:
\newcommand{\vel}{$\dot x$}. De este modo, \vel (no $\vel$) da como output directamen-
te ẋ. Sin embargo, esta solución no es óptima, porque la siguiente ocurrencia de \vel da un error.
En efecto, si \vel = $\dot x$, entonces $ \vel(t)>0$ = $ $\dot x$> 0$. En tal caso, LATEX
ve que un modo matemático se ha abierto y cerrado inmediatamente, conteniendo sólo un espacio
entremedio, y luego, en modo texto, viene el comando \dot, que es matemático: LATEX acusa un
error y la compilación se detiene.
La solución a este problema es utilizar el comando \ensuremath, que asegura que haya modo
matemático, pero si ya hay uno abierto, no intenta volverlo a abrir:
Un caso especial de comando matemático es el de operadores tipo logaritmo (ver Tabla 6.9). Si
queremos definir una traducción al castellano de \sin, debemos usar el comando \DeclareMathOperator
disponible via amsmath:
\newcommand{\be}{\begin{enumerate}}
1. El primer caso. \newcommand{\ee}{\end{enumerate}}
2. Ahora el segundo.
\be
3. Y el tercero. \item El primer caso.
\item Ahora el segundo.
\item Y el tercero.
\ee
202 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Podemos también definir comandos que acepten argumentos. Si el sujeto anterior, Cristóbal,
desea escribir cualquier nombre precedido de “Nombre:” en itálica, entonces puede crear el siguiente
comando:
\nombre{Violeta}
Observemos que \newcommand tiene un argumento opcional, que indica el número de argumentos
que el nuevo comando va a aceptar. Esos argumentos se indican, dentro de la definición del comando,
con #1, #2, etc. Por ejemplo, consideremos un comando que acepta dos argumentos:
\newcommand{\fn}[2]{f(#1,#2)}
\newcommand{\fn}[2][x]{f(#1,#2)}
Redefinición de comandos
Ocasionalmente no nos interesa definir un nuevo comando, sino redefinir la acción de un comando
preexistente. Esto se hace con \renewcommand:
Un ejemplo más útil ocurre cuando queremos asegurar un cambio de párrafo, por ejemplo, para
colocar un tı́tulo de sección:
\underline{\texto},
\underline{\textodos},
y
\underline{\textotres}.
\texto conserva espacios en blanco antes y después del texto, \textodos sólo el espacio en
204 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
blanco después del texto, y \textotres no tiene espacios en blanco alrededor del texto.
Nuevos ambientes
Nuevos ambientes en LATEX se definen con \newenvironment:
Entonces, con
\begin{na}
Hola a todos. Es un placer saludarlos en este d\’{\i}a tan especial.
Los nuevos ambientes también pueden ser definidos de modo que acepten argumentos. Como con
\newcommand, basta agregar como argumento opcional a \newenvironment un número que indique
cuántos argumentos se van a aceptar:
Dentro de <comienzo ambiente>, se alude a cada argumento como #1, #2, etc. Los argumentos
no pueden ser usados en los comandos de cierre del ambiente (<final ambiente>). Por ejemplo,
modifiquemos el ambiente na anterior, de modo que en vez de colocar una lı́nea horizontal al
comienzo, coloque lo que le indiquemos en el argumento:
Estructura básica.
La estructura básica de un paquete o una clase es:
a) Identificación: Información general (nombre del paquete, fecha de creación, etc.). (Obligatoria.)
c) Opciones: Comandos relacionados con el manejo de las opciones con las cuales el paquete o clase
pueden ser invocados. (Opcional.)
206 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
d) Más declaraciones: Aquı́ van los comandos que constituyen el cuerpo de la clase o paquete.
(Obligatoria: si no hay ninguna declaración, el paquete o clase no hace nada, naturalmente.)
La identificación está consituida por las siguientes dos lı́neas, que deben ir al comienzo del
archivo:
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{<paquete>}[<fecha> <otra informacion>]
La primera lı́nea indica a LATEX que éste es un archivo para LATEX 2ε . La segunda lı́nea especifica
que se trata de un paquete, indicando el nombre del mismo (es decir, el nombre del archivo sin
extensión) y, opcionalmente, la fecha (en formato YYYY/MM/DD) y otra información relevante. Por
ejemplo, nuestro paquete addmath.sty comienza con las lı́neas:
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{addmath}[1998/09/30 Macros matematicos adicionales (VM)]
Si lo que estamos definiendo es una clase, usamos el comando \ProvidesClass. Para nuestra
clase mfm2.cls:
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mfm2}[2002/03/25 Estilo para apuntes MFM II (VM)]
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{addmath}[1998/09/30 Macros matematicos adicionales (VM)]
(x|y) \prodInt{x}{y}
hxi \promedio{x}
Z ∞
dz f (z) \intii dz\, f(z)
−∞
∠ ABC = 90◦ \angle\, ABC = 90\grados
2 F1 (a, b, c ; d) \Hipergeometrica{a}{b}{c}{d}
6.13. MODIFICANDO LATEX. 207
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{addmath}[1998/09/30 Macros matematicos adicionales (VM)]
\RequirePackage{amsmath}
\RequirePackage{amssymb}
\RequirePackage{euscript}
...
\newcommand{\norma}[1]{\ensuremath \left\lVert\, #1 \,\right\rVert}
\newcommand{\intC}{{\sideset{^*}{}\int}}
\DeclareMathOperator{\senh}{senh}
...
Por ejemplo:
kxk \norma{x}
∗Z
dz f (z) \intC dz \, f(z)
senh(2y) \senh (2y)
La posibilidad de basar un archivo .sty o .cls en otro es particularmente importante para
una clase, ya que contiene una gran cantidad de comandos y definiciones necesarias para compilar
el documento exitosamente. Sin embargo, un usuario normal, aun cuando desee definir una nueva
clase, estará interesado en modificar sólo parte del comportamiento. Con \LoadClass, dicho usuario
puede cargar la clase sobre la cual se desea basar, y luego introducir las modificaciones necesarias,
facilitando enormemente la tarea.
Por ejemplo, al preparar este documento fue claro desde el comienzo que se necesitaba esencial-
mente la clase book, ya que serı́a un texto muy extenso, pero también era claro que se requerı́an
ciertas modificaciones. Entonces, en nuestra clase mfm2.cls lo primero que hacemos es cargar la
clase book, más algunos paquetes necesarios (incluyendo nuestro addmath), y luego procedemos a
modificar o añadir comandos:
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mfm2}[2002/03/25 Estilo para apuntes MFM II (VM)]
\LoadClass[12pt]{book}
\RequirePackage[spanish]{babel}
6
Estos comandos sólo se pueden usar en un archivo .sty o .cls Para documentos normales, la manera
de cargar un paquete es \usepackage, y para cargar una clase es \documentclass.
208 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
\RequirePackage{enumerate}
\RequirePackage{addmath}
En un archivo .sty o un .cls se pueden cargar varios paquetes con \RequirePackage. \LoadClass,
en cambio, sólo puede aparecer en un .cls, y sólo es posible usarlo una vez (ya que normalmente
clases distintas son incompatibles entre sı́).
Manejo de opciones
En el último ejemplo anterior, la clase mfm2 carga la clase book con la opción 12pt. Esto significa
que si nuestro documento comienza con \documentclass{mfm2}, será compilado de acuerdo a la
clase book, en 12 puntos. No es posible cambiar esto desde nuestro documento. Serı́a mejor que
pudiéramos especificar el tamaño de letra fuera de la clase, de modo que \documentclass{mfm2}
dé un documento en 10 puntos, y \documentclass[12pt]{mfm2} uno en 12 puntos. Para lograr
esto hay que poder pasar opciones desde la clase mfm2 a book.
El modo más simple de hacerlo es con \LoadClassWithOptions. Si mfm2.cls ha sido llamada
con opciones <opcion1>,<opcion2>, etc., entonces book será llamada con las mismas opciones.
Por tanto, basta modificar en mfm2.cls la lı́nea \LoadClass[12pt]{book} por:
\LoadClassWithOptions{book}
\RequirePackageWithOptions{<paquete_base>}
El ejemplo anterior puede ser suficiente en muchas ocasiones, pero en general uno podrı́a llamar
a nuestra nueva clase, mfm2, con opciones que no tienen nada que ver con book. Por ejemplo,
podrı́amos llamarla con opciones spanish,12pt. En tal caso, deberı́a pasarle spanish a babel, y
12pt a book. Más aún, podrı́amos necesitar definir una nueva opción, que no existe en ninguna de
las clases o paquetes cargados por book, para modificar el comportamiento de mfm2.cls de cierta
manera especı́fica no prevista. Estas dos tareas, discriminar entre opciones antes de pasarla a algún
paquete determinado, y crear nuevas opciones, constituyen un manejo más avanzado de opciones.
A continuación revisaremos un ejemplo combinado de ambas tareas, extraido de la clase con la cual
compilamos este texto, mfm2.cls.
La idea es poder llamar a mfm2 con una opción adicional keys, que permita agregar al dvi
información sobre las etiquetas (dadas con \label) de ecuaciones, figuras, etc., que aparezcan en
el documento (veremos la utilidad y un ejemplo de esto más adelante). Lo primero es declarar una
nueva opción, con:
\DeclareOption{<opcion>}{<comando>}
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mfm2}[2002/03/25 Estilo para apuntes MFM II (VM)]
...
\DeclareOption{keys}{...}
6.13. MODIFICANDO LATEX. 209
...
\ProcessOptions\relax
...
Observamos que después de declarar la o las opciones (en este caso keys), hay que procesarlas,
con \ProcessOptions.7
Las lı́neas anteriores permiten que \documentclass{mfm2} y \documentclass[keys]{mfm2}
sean ambas válidas, ejecutándose o no ciertos comandos dependiendo de la forma utilizada.
Si ahora queremos que \documentclass[keys,12pt]{mfm2} sea una lı́nea válida, de-
bemos procesar keys dentro de mfm2.cls, y pasarle a book.cls las opciones restantes. El
siguiente es el código definitivo:
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mfm2}[2002/03/25 Estilo para apuntes MFM II (VM)]
\newif\ifkeys\keysfalse
\DeclareOption{keys}{\keystrue}
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{book}}
\ProcessOptions\relax
\LoadClass{book}
\RequirePackage[spanish]{babel}
\RequirePackage{amsmath}
\RequirePackage{theorem}
\RequirePackage{epsfig}
\RequirePackage{ifthen}
\RequirePackage{enumerate}
\RequirePackage{addmath}
\ifkeys\RequirePackage[notref,notcite]{showkeys}\fi
Sin entrar en demasiados detalles, digamos que la opción keys tiene el efecto de hacer
que una cierta variable lógica \ifkeys, sea verdadera (cuarta lı́nea del código). La siguien-
te lı́nea (\DeclareOption*...) hace que todas las opciones que no han sido procesadas
(12pt, por ejemplo) se pasen a la clase book. A continuación se procesan las opciones con
\ProcessOptions, y finalmente se carga la clase book.
Las lı́neas siguientes cargan todos los paquetes necesarios, y finalmente se encuentran
todos los nuevos comandos y definiciones que queremos incluir en mfm2.cls.
Observemos que la forma particular en que se carga el paquete showkeys. Ésa es pre-
cisamente la función de la opción keys que definimos: showkeys.sty se carga con ciertas
opciones sólo si se da la opción keys.
¿Cuál es su efecto? Consideremos el siguiente texto de ejemplo, en que mfm2 ha sido
llamada sin la opción keys:
7
\relax es un comando de TEX que, esencialmente, no hace nada, ni siquiera introduce un espacio en
blanco, y es útil incluirlo en puntos crı́ticos de un documento, como en este ejemplo.
210 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
\documentclass[12pt]{mfm2}
\begin{document}
La opci\’on \verb+keys+ resulta muy \’util cuando tengo objetos numerados
autom\’aticamente, como una ecuaci\’on:
\begin{equation}
\label{newton}
\vec F = m \vec a \ .
\end{equation}
y luego quiero referirme a ella: Ec.\ \eqref{newton}.
En el primer caso, se ha compilado sin la opción keys, y en el segundo con ella. El efecto
es que, si se usa un \label en cualquier parte del documento, aparece en el margen derecho
una caja con el nombre de dicha etiqueta (en este caso, newton). Esto es útil para cualquier
tipo de documentos, pero lo es especialmente en textos como estos apuntes, muy extensos y
con abundantes referencias. En tal caso, tener un modo visual, rápido, de saber los nombres
de las ecuaciones sin tener que revisar trabajosamente el archivo fuente es una gran ayuda.
Ası́, versiones preliminares pueden ser compiladas con la opción keys, y la versión final sin
ella, para no confesar al lector nuestra mala memoria o nuestra comodidad.
6.13. MODIFICANDO LATEX. 211
Caso 1: \documentclass[12pt]{mfm2}
F~ = m~a . (1)
Caso 2: \documentclass[keys,12pt]{mfm2}
La primera lı́nea nos comunica que LATEX ha encontrado un error. A veces los errores
tienen que ver con procesos más internos, y son encontrados por TEX. Esta lı́nea nos informa
quién encontró el error.
La tercera lı́nea comienza con un signo de exclamación. Éste es el indicador del error. Nos
dice de qué error se trata.
Las dos lı́neas siguientes describen el error en términos de comandos de bajo nivel.
La lı́nea 6 nos dice dónde ocurrió el error: la lı́nea 140 en este caso. Además nos informa
del texto conflictivo: \begin{itemie}.
En realidad, el mensaje nos indica dónde LATEX advirtió el error por primera vez, que no
es necesariamente el punto donde el error se cometió. Pero la gran mayorı́a de las veces la
indicación es precisa. De hecho, es fácil darse cuenta, con la tercera lı́nea
(Environment itemie undefined)
y la sexta (\begin{itemie}) que el error consistió en escribir itemie en vez de itemize. La
información de LATEX es clara en este caso y nos dice correctamente qué ocurrió y dónde.
Luego viene un ?. LATEX está esperando una respuesta de nosotros. Tenemos varias alter-
nativas. Comentaremos sólo cuatro, tı́picamente usadas:
(a) h <Enter>
Solicitamos ayuda. TEX nos explica brevemente en qué cree él que consiste el error y/o
nos da alguna recomendación.
(b) x <Enter>
Abortamos la compilación. Deberemos volver al editor y corregir el texto. Es la opción
más tı́pica cuando uno tiene ya cierta experiencia, pues el mensaje basta para reconocer
el error.
(c) <Enter>
Ignoramos el error y continuamos la compilación. TEX hace lo que puede. En algunos
casos esto no tiene consecuencias graves y podremos llegar hasta el final del archivo
sin mayores problemas. En otros casos, ignorar el error puede provocar que ulteriores
comandos —perfectamente válidos en principio— no sean reconocidos y, ası́, acumular
6.14. ERRORES Y ADVERTENCIAS. 213
muchos errores más. Podemos continuar con <Enter> sucesivos hasta llegar al final de
la compilación.
(d) q <Enter>
La acción descrita en el punto anterior puede llegar a ser tediosa o infinita. q hace
ingresar a TEX en batchmode, modo en el cual la compilación prosigue ignorando todos
los errores hasta el final del archivo, sin enviar mensajes a pantalla y por ende sin que
debamos darle infinitos <Enter>.
Las opciones (c) y (d) son útiles cuando no entendemos los mensajes de error. Como
TEX seguirá compilando haciendo lo mejor posible, al mirar el dvi puede que veamos más
claramente dónde comenzaron a ir mal las cosas y, por tanto, por qué.
Como dijimos, LATEX indica exactamente dónde encontró el error, de modo que hemos de
ponerle atención. Por ejemplo, si tenemos en nuestro documento la lı́nea:
... un error inesperado\fotnote{En cualquier punto.}
puede decidir...
generarı́a el mensaje de error:
! Undefined control sequence.
*
Falta \end{document}. (Dar Ctrl-C o escribir \end{document} para salir de la compilación.)
! Missing $ inserted.
Errores c), d), f), h) de la Sec. 6.14.1.
! Missing { (o }) inserted.
Paréntesis cursivos no apareados.
! Missing \begin{document}.
Falta \begin{document} o hay algo incorrecto en el preámbulo.
6.14.2. Advertencias.
La estructura de una advertencia de LATEX es:
LaTeX warning. <mensaje>.
Algunos ejemplos:
Label ‘...’ multiply defined.
Dos \label tienen el mismo argumento.
Label(s) may have changed. Rerun to get cross-references right.
Los números impresos por \ref y \pageref pueden ser incorrectos, pues los valores corres-
pondientes cambiaron respecto al contenido del aux generado en la compilación anterior.
Reference ‘...’ on page ... undefined.
El argumento de un \ref o un \pageref no fue definido por un \label.
TEX también envı́a advertencias. Se reconocen porque no comienzan con TeX warning.
Algunos ejemplos.
Overfull \hbox ...
TEX no encontró un buen lugar para cortar una lı́nea, y puso más texto en ella que lo
conveniente.
Overfull \vbox ...
TEX no encontró un buen lugar para cortar una página, y puso más texto en ella que lo
conveniente.
Underfull \hbox ...
TEX construyó una lı́nea con muy poco material, de modo que el espacio entre palabras puede
ser excesivo.
Underfull \vbox ...
TEX construyó una página con muy poco material, de modo que los espacios verticales (entre
párrafos) pueden ser excesivos.
Las advertencias de LATEX siempre deben ser atendidas. Una referencia doblemente defini-
da, o no compilar por segunda vez cuando LATEX lo sugiere, generará un resultado incorrecto
en el dvi. Una referencia no definida, por su parte, hace aparecer un signo ?? en el texto
final. Todos resultados no deseados, por cierto.
Las advertencias de TEX son menos decisivas. Un overfull o underfull puede redundar en
que alguna palabra se salga del margen derecho del texto, que el espaciado entre palabras en
una lı́nea sea excesivo, o que el espacio vertical entre párrafos sea demasiado. Los estándares
de calidad de TEX son altos, y por eso envı́a advertencias frecuentemente. Pero generalmente
los defectos en el resultado final son imperceptibles a simple vista, o por lo menos no son
suficientes para molestarnos realmente. A veces sı́, por supuesto, y hay que estar atentos.
Siempre conviene revisar el texto y prestar atención a estos detalles, aunque ello sólo tiene
sentido al preparar la versión definitiva del documento.
216 CAPÍTULO 6. EL SISTEMA DE PREPARACIÓN DE DOCUMENTOS TEX .
Parte II
Métodos Numéricos.
217
Capı́tulo 7
Preliminares.
versión final 4.3, 22 de Noviembre del 2007 1 .
class Vec3:
def __mul__(self,new):
return self.x*new.x+self.y*new.y+self.z*new.z
def __repr__(self):
return ‘‘(%1.5f,%1.5f,%1.5f)’’ % (self.x,self.y,self.z)
1
Este capı́tulo está basado en el primer capı́tulo del libro: Numerical Methods for Physics, second edition
de Alejandro L. Garcia, editorial Prentice Hall
219
220 CAPÍTULO 7. PRELIMINARES.
def main():
c=Vec3(x1,x2,x3)
d=Vec3(x1,x2,x3)
if c*d==0:
print ‘‘Los vectores ingresados son ortogonales’’
else:
print ‘‘Los vectores ingresados no son ortogonales’’
if __name__ == ‘__main__’:
main()
Primero cambiamos los permisos del archivo orthog.py para que sea ejecutable y luego
usamos el programa
jrogan@huelen:~/programas_metodos_numericos/pyhton$ chmod 744 orthog.py%$
jrogan@huelen:~/programas_metodos_numericos/pyhton$ orthog.py%$
Ingrese la primera coordenada del 1er vector : 1
Ingrese la segunda coordenada del 1er vector : 0
Ingrese la tercera coordenada del 1er vector : 0
Ingrese la primera coordenada del 2do vector : 0
Ingrese la segunda coordenada del 2do vector : 1
Ingrese la tercera coordenada del 2do vector : 0
Los vectores ingresados son ortogonales
class Vector{
private:
double c_x;
double c_y;
double c_z;
public:
Vector():c_x(0),c_y(0),c_z(0) {} ;
Vector(double x, double y, double z):c_x(x),c_y(y),c_z(z) {} ;
~Vector() {} ;
double x() const {return c_x;};
double y() const {return c_y;};
double z() const {return c_z;};
};
#endif
Ahora estamos en condiciones de escribir el programa propiamente tal. Las primeras lı́neas
son
222 CAPÍTULO 7. PRELIMINARES.
Las primeras lı́neas son comentarios que nos recuerdan lo que el programa hace. La lı́nea
siguiente incluye las definiciones de nuestra recién creada clase. Luego incluı́mos una lı́nea
dice que vamos a usar el namespace std. A continuación comienza el programa
#include "vector3d.h"
int main()
{
Vector a, b;
cout << "Ingrese el primer vector : ";
cin >> a ;
cout << "Ingrese el segundo vector : ";
cin >> b ;
if(a*b==0) {
cout <<"Son ortogonales" << endl;
} else {
cout << "No son ortogonales"<< endl ;
}
return 0;
}
Declaramos dos objetos tipo Vector, a y b para almacenar los vectores que entran. La
instrucción de salida despliega sobre la pantalla:
if(a*b==0) {
cout <<"Son ortogonales" << endl;
} else {
cout << "No son ortogonales"<< endl ;
}
y despliega el resultados. Aquı́ los comandos de compilación y una salida tı́pica del programa.
7.1. PROGRAMAS Y FUNCIONES. 223
De acuerdo al valor de adotb el programa despliega una de las dos posibles respuestas. A
continuación la salida al ejecutar el help del programa
octave> help orthog
orthog is the file: /home/jrogan/orthog.m
Interpolación.
Es bien sabido que dados tres pares (x, y), se puede encontrar una cuadrática que pasa
por los puntos deseados. Hay varias maneras de encontrar el polinomio y varias maneras de
escribirlo. La forma de Lagrange del polinomio es
(x − x2 )(x − x3 ) (x − x1 )(x − x3 ) (x − x1 )(x − x2 )
p(x) = y1 + y2 + y3 , (7.1)
(x1 − x2 )(x1 − x3 ) (x2 − x1 )(x2 − x3 ) (x3 − x1 )(x3 − x2 )
donde (x1 , y1 ), (x2 , y2 ), (x3 , y3 ), son los tres puntos por los que queremos pasar. Comúnmente
tales polinomios son usados para interpolar entre los puntos dados. A continuación el bosquejo
de un programa simple de interpolación, que llamaremos interp
Inicializa los puntos (x1 , y1 ), (x2 , y2 ) y (x3 , y3 ) para ser ajustados por el polinomio.
Establece el intervalo de la interpolación (desde xmı́n hasta xmáx )
Encuentra y ∗ para los valores deseados de x∗ , usando la función intrpf.
Finalmente, podemos graficar la curva dada por (x∗ , y ∗ ), y marcar los puntos originales
para comparar.
7.1. PROGRAMAS Y FUNCIONES. 225
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# interp - Programa para interpolar datos usando
# el polinomio de Lagrange cuadratico para tres puntos dados.
# Importa un modulo que permite graficar.
from pylab import *
# Definicion de la funcion que evalua el polinomio.
def intrpf(x,x_i,y_i):
t0=y_i[0]*(x-x_i[1])*(x-x_i[2])/((x_i[0]-x_i[1])*(x_i[0]-x_i[2]))
t1=y_i[1]*(x-x_i[0])*(x-x_i[2])/((x_i[1]-x_i[0])*(x_i[1]-x_i[2]))
t2=y_i[2]*(x-x_i[0])*(x-x_i[1])/((x_i[2]-x_i[0])*(x_i[2]-x_i[1]))
return t0+t1+t2
def main():
# Se ingresan los tres puntos y se almacenan en dos listas x_i e y_i
x_i=[]
y_i=[]
print "Ingrese tres puntos (x,y)"
x0=input("Coordenada x, primer punto : ")
y0=input("Coordenada y, primer punto : ")
x1=input("Coordenada x, segundo punto : ")
y1=input("Coordenada y, segundo punto : ")
x2=input("Coordenada x, tercer punto : ")
y2=input("Coordenada y, tercer punto : ")
x_i.append(x0)
x_i.append(x1)
x_i.append(x2)
y_i.append(y0)
y_i.append(y1)
y_i.append(y2)
# Se ingresa el intervalo donde se evaluara el polinomio
print "Ingrese intervalo para hacer la interpolacion"
x_min=input("Coordenada x minimo : ")
x_max=input("Coordenada x maximo : ")
# Grafica
t=arange(x_min,x_max,(x_max-x_min)/100.0)
plot(x_i, y_i,’ro’,t, intrpf(t,x_i,y_i))
title("Polinomio de interpolacion de Lagrange")
grid(True)
show()
if __name__==’__main__’:
main()
226 CAPÍTULO 7. PRELIMINARES.
Usando el programa
jrogan@huelen:~/programas_metodos_numericos/pyhton$ interp.py
Ingrese tres puntos (x,y)
Coordenada x, primer punto : 0.5
Coordenada y, primer punto : 0.25
Coordenada x, segundo punto : 1
Coordenada y, segundo punto : 1
Coordenada x, tercer punto : 2
Coordenada y, tercer punto : 4
Ingrese intervalo para hacer la interpolacion
Coordenada x minimo : -3
Coordenada x maximo : 3
El uso de las instrucción plot en el programa levanta una ventana independiente con el
siguiente gráfico:
En ambos casos hay asignamiento dinámico de memoria, nplot podrı́a ser una entrada del
programa.
Los valores interpolados son calculados en un for
Notemos que xi[1]=xmı́n , xi[nplot]=xmáx , con valores equiespaciados entre ellos. Los va-
lores de yi (y ∗ = p(x∗ )) son evaluados usando la ecuación (7.1) en la función intrpf. La
salida del programa
#!/usr/bin/octave
load x.txt; load y.txt; load xi.txt; load yi.txt;
%* Grafica la curva dada por (xi,yi) y marca los puntos originales
xlabel(’x’);
ylabel(’y’);
title(’Interpolacion de tres puntos’);
gset nokey;
plot(x,y, ’*’,xi,yi,’-’);
pause
system( "grafica.m" ) ;
7.1. PROGRAMAS Y FUNCIONES. 229
5
y
0
-3 -2 -1 0 1 2 3
x
Esta lı́nea no es absolutamente necesaria, por que al salir el programa liberará la memoria
de todas maneras. Sin embargo, se considera como buen estilo de programación, limpiar uno
la memoria que requirió durante la ejecución del programa.
La función intrpf, la cual evalúa el polinomio de Lagrange, comienza por las siguientes
lı́neas
Especifica los argumentos de llamada y lo que devuelve. Todas las variables dentro de la
función son locales. El C++ pasa las variables por valor, por defecto, la función recibe una
copia que se destruye cuando termina la función, si se desea pasar una variable double a
por referencia debemos anteponerle el signo &, es decir, pasarla como double &a. De esta
manera la función puede modificar el valor que tenı́a la variable en el programa principal.
El resto de la función
230 CAPÍTULO 7. PRELIMINARES.
double yi = (xi-x[2])*(xi-x[3])/((x[1]-x[2])*(x[1]-x[3]))*y[1]
+ (xi-x[1])*(xi-x[3])/((x[2]-x[1])*(x[2]-x[3]))*y[2]
+ (xi-x[1])*(xi-x[2])/((x[3]-x[1])*(x[3]-x[2]))*y[3];
return yi ;
Estas lı́neas evalúan el polinomio. Inicialmente pondremos esta función en el mismo archivo,
luego la podemos separar en otro archivo y escribir un Makefile que genere el ejecutable.
Los puntos de la interpolación (x∗ , y ∗ ) son graficados con lı́nea segmentada y los datos
originales con cı́rculos, ver figura (7.2). El trabajo real es hecho por la función intrpf. Un
bosquejo de lo que queremos que haga esta función a continuación.
Salida: y ∗ .
Las funciones en Octave están implementadas como en la mayorı́a de los lenguajes, excepto
que aquı́ cada función tiene que ir en un archivo separado. El nombre del archivo debe
coincidir con el nombre de la función (la función intrpf está en el archivo intrpf.m).
function yi=intrpf(xi,x,y)
% Funcion para interpolar entre puntos
% usando polinomio de Lagrange (cuadratico)
% Entradas
% x Vector de las coordenadas x de los puntos dados (3 valores)
% y Vector de las coordenadas y de los puntos dados (3 valores)
% Salida
% yi El polinomio de interpolacion evaluado en xi
La función intrpf tiene tres argumentos de entrada y uno de salida. El resto de la función
es directa, sólo evalúa el polinomio definido en (7.1). El cuerpo de la función a continuación
yi = (xi-x(2))*(xi-x(3))/((x(1)-x(2))*(x(1)-x(3)))*y(1) ...
+ (xi-x(1))*(xi-x(3))/((x(2)-x(1))*(x(2)-x(3)))*y(2) ...
+ (xi-x(1))*(xi-x(2))/((x(3)-x(1))*(x(3)-x(2)))*y(3);
return;
tı́pico es 2±127 ≈ 10±38 ; para precisión doble es tı́picamente 2±1024 ≈ 10±308 . Exceder el
intervalo de la precisión simple no es difı́cil. Consideremos, por ejemplo, la evaluación del
radio de Bohr en unidades SI,
4πε0 ~2
a0 = 2
≈ 5.3 × 10−11 [m] . (7.2)
me e
Mientras sus valores caen dentro del intervalo de un número de precisión simple, el intervalo
es excedido en el cálculo del numerador (4πε0 ~2 ≈ 1.24 × 10−78 [kg C2 m]) y del denominador
(me e2 ≈ 2.34 × 10−68 [kg C2 ]). La mejor solución para lidiar con este tipo de dificultades de
intervalo es trabajar en un conjunto de unidades naturales al problema (e.g. para problemas
atómicos se trabaja en las distancias en Ångstroms y la carga en unidades de la carga del
electrón).
Algunas veces los problemas de intervalo no son causados por la elección de las unida-
des, sino porque los números en el problema son inherentemente grandes. Consideremos un
importante ejemplo, la función factorial. Usando la definición
n! = n × (n − 1) × (n − 2) × . . . × 3 × 2 × 1 , (7.3)
es fácil evaluar n! en Python,
nFactorial=1;
for i in range(1,n+1):
nFactorial *=i
o en C++,
double nFactorial=1;
for(int i=1; i <=n; i++) nFactorial *=i ;
donde n es un número dado.
En Octave, usando el operador dos puntos este cálculo puede ser realizado como
nFactorial = prod(1:n);
donde prod(x) es el producto de los elementos del vector x y 1:n=[1,2. . . , n]. Infortunada-
mente, debido a problemas de intervalo, no podemos calcular n! para n > 170 usando estos
métodos directos de evaluación (7.3).
Una solución común para trabajar con números grandes es usar su logaritmo. Para el
factorial
log(n!) = log(n) + log(n − 1) + . . . + log(3) + log(2) + log(1) . (7.4)
En Octave, esto puede ser evaluado como
log_nFactorial = sum( log(1:n) ) ;
donde sum(x) es la suma de los elementos del vector x. Sin embargo, este esquema es compu-
tacionalmente pesado si n es grande. Una mejor estrategia es combinar el uso de logaritmos
con la fórmula de Stirling2
√
n −n 1 1
n! = 2nπn e 1+ + + ··· (7.5)
12n 288n2
2
M. Abramowitz and I. Stegun, Handbook of Mathematical Functions ( New York: Dover 1972).
7.2. ERRORES NUMÉRICOS. 233
o
1 1 1
log(n!) = log(2nπ) + n log(n) − n + log 1 + + + ··· . (7.6)
2 12n 288n2
Esta aproximación puede ser usada cuando n es grande (n > 30), de otra manera es preferible
la definición original.
Finalmente, si el valor de n! necesita ser impreso, podemos expresarlo como
donde el exponente es la parte entera de log10 (n!), y la mantisa es 10a donde a es la parte
fraccionaria de log10 (n!). Recordemos que la conversión entre logaritmo natural y logaritmo
en base 10 es log10 (x) = log10 (e) log(x).
f (x + h) − f (x)
f ′ (x) = , (7.8)
h
en el lı́mite en que h → 0. ¿Qué sucede si evaluamos el lado derecho de esta expresión,
poniendo h = 0?. Como el computador no entiende que la expresión es válida sólo como un
lı́mite, la división por cero tiene varias posibles salidas. El computador puede asignar el valor,
Inf, el cual es un número de punto flotante especial reservado para representar el infinito. Ya
que el numerador es también cero el computador podrı́a evaluar el cociente siendo indefinido
(Not-a-Number), NaN, otro valor reservado. O el cálculo podrı́a parar y arrojar un mensaje
de error.
Claramente, poniendo h = 0 para evaluar (7.8) no nos dará nada útil, pero ¿si le ponemos
a h un valor muy pequeño, digamos h = 10−300 , usamos doble precisión? La respuesta aún
será incorrecta debido a la segunda limitación sobre la representación de números con punto
flotante: el número de dı́gitos en la mantisa. Para precisión simple, el número de dı́gitos
significantes es tı́picamente 6 o 7 dı́gitos decimales; para doble precisión es sobre 16 dı́gitos.
Ası́, en doble precisión, la operación 3 + 10−20 retorna una respuesta 3 por el redondeo;
usando h = 10−300 en la ecuación (7.8) casi con seguridad regresará 0 cuando evaluemos el
numerador.
La figura 7.3 ilustra la magnitud del error de redondeo en un cálculo tı́pico de derivada.
Definimos el error absoluto
′ f (x + h) − f (x)
∆(h) = f (x) −
. (7.9)
h
Notemos que ∆(h) decrece cuando h se hace más pequeño, lo cual es esperado, dado que
la ecuación (7.8) es exacta cuando h → 0. Por debajo de h = 10−10 , el error comienza a
incrementarse debido a efectos de redondeo. En valores menores de h el error es tan grande
234 CAPÍTULO 7. PRELIMINARES.
0
10
−2
10
−4
∆(h)
10
−6
10
−8
10
−10
10 −20 −16 −12 −8 −4 0
10 10 10 10 10 10
h
Figura 7.3: Error absoluto ∆(h), ecuación (7.9), versus h para f (x) = x2 y x = 1.
En este capı́tulo resolveremos uno de los primeros problemas abordados por un estudiante
de fı́sica: el vuelo de un proyectil y, en particular, el de una pelota de baseball. Sin la resistencia
del aire el problema es fácil de resolver. Sin embargo, incluyendo un arrastre realista, nosotros
necesitamos calcular la solución numéricamente. Para analizar este problema definiremos
primero la diferenciación numérica. De hecho antes de aprender fı́sica uno aprende cálculo
ası́ que no debemos sorprendernos si este es nuestro punto de partida. En la segunda mitad del
capı́tulo nos ocuparemos de otro viejo conocido, el péndulo simple, pero sin la aproximación a
ángulos pequeños. Los problemas oscilatorios, tales como el péndulo, nos revelarán una falla
fatal en algunos de los métodos numéricos de resolver ecuaciones diferenciales ordinarias.
d~v 1 d~r
= F~a (~v ) − g ŷ , = ~v , (8.1)
dt m dt
donde m es la masa del proyectil. La fuerza debido a la resistencia del aire es F~a (~v ), la
aceleración gravitacional es g, e ŷ es un vector unitario en la dirección y. El movimiento es
bidimensional, tal que podemos ignorar la componente z y trabajar en el plano xy.
La resistencia del aire se incrementa con la velocidad del objeto, y la forma precisa para
F~a (~v ) depende del flujo alrededor del proyectil. Comúnmente, esta fuerza es aproximada por
1
F~a (~v ) = − Cd ρA | v | ~v , (8.2)
2
1
Este capı́tulo está basado en el segundo capı́tulo del libro: Numerical Methods for Physics, second edition
de Alejandro L. Garcia, editorial Prentice Hall
235
236 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
~v (t + τ ) − ~v (t)
+ O(τ ) = ~a(~r(t), ~v (t)) , (8.13)
τ
~r(t + τ ) − ~r(t)
+ O(τ ) = ~v (t) , (8.14)
τ
o bien
notemos que τ O(τ ) = O(τ 2 ). Este esquema numérico es llamado el método de Euler. Antes de
discutir los méritos relativos de este acercamiento, veamos cómo serı́a usado en la práctica.
Primero, introducimos la notación
fn = f (tn ) , tn = nτ , n = 0, 1, 2, . . . (8.17)
tal que f0 = f (t = 0). Nuestras ecuaciones para el método de Euler (despreciando el término
del error) ahora toma la forma
5. Vaya al paso 3 hasta que suficientes puntos de trayectoria hayan sido calculados.
El método calcula un conjunto de valores para ~rn y ~vn que nos da la trayectoria, al menos
en un conjunto discreto de valores. La figura 8.1 ilustra el cálculo de la trayectoria para un
único paso de tiempo.
8.1. MOVIMIENTO DE UN PROYECTIL. 239
vn x τ vn vn
an
τ an
vn+1
rn rn+1
Figura 8.1: Trayectoria de una partı́cula después de un único paso de tiempo con el método
de Euler. Sólo para efectos ilustrativos τ es grande.
sido el error local, el error cometido en un único paso de tiempo. En un problema tı́pico
nosotros deseamos evaluar la trayectoria desde t = 0 a t = T . El número de pasos de tiempo
es NT = T /τ ; notemos que si reducimos τ , debemos tomar más pasos. Si el error local es
O(τ n ), entonces estimamos el error global como
Iterar hasta que la bola golpée en el piso o el máximo número de pasos sea completado.
Cuadro 8.1: Bosquejo del programa balle, el cual calcula la trayectoria de una pelota de
baseball usando el método de Euler.
Movimiento de Proyectil
Método de Euler
Teoría (sin aire)
6
4
Altura [m]
0 5 10 15 20 25
Alcance [m]
Figura 8.2: Salida del programa balle para una altura inicial de 0 [m], una velocidad inicial
de 15 [m/s], y un paso de tiempo τ =0.1 [s]. No hay resistencia del aire. La lı́nea continua es
la teórica y los puntos son los calculados, la diferencia se debe a errores de truncamiento.
jrogan@huelen:~/programas$ balle
Ingrese la altura inicial [m] : 0
Ingrese la velocidad inicial [m/s]: 15
Ingrese angulo inicial (grados): 45
242 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
Movimiento de Proyectil
Método de Euler
Teoría (sin aire)
60
40
Altura [m]
20
0
0 50 100 150 200 250
Alcance [m]
Figura 8.3: Salida del programa balle para una altura inicial de 1 [m], una velocidad inicial
de 50 [m/s], y un paso de tiempo τ =0.1 [s]. Con resistencia del aire.
básicos. Para un péndulo simple es más conveniente describir la posición en términos del
desplazamiento angular, θ(t). La ecuación de movimiento es
d2 θ g
2
= − sen θ , (8.26)
dt L
donde L es la longitud del brazo y g es la aceleración de gravedad. En la aproximación para
ángulo pequeño, sen θ ≈ θ, la ecuación (8.26) se simplifica a
d2 θ g
2
=− θ. (8.27)
dt L
Esta ecuación diferencial ordinaria es fácilmente resuelta para obtener
2πt
θ(t) = C1 cos + C2 , (8.28)
Ts
donde las constantes C1 y C2 están determinadas por los valores iniciales de θ y ω = dθ/dt.
El perı́odo para ángulos pequeños, Ts es
s
L
Ts = 2π . (8.29)
g
1
mL2 ω 2 − mgL cos θ = −mgL cos θm , (8.31)
2
o
2g
ω2 = (cos θ − cos θm ) . (8.32)
L
Ya que ω = dθ/dt,
dθ
dt = r . (8.33)
2g
(cos θ − cos θm )
L
En un perı́odo el péndulo se balancea de θ = θm a θ = −θm y regresa a θ = θm . Ası́, en medio
perı́odo el péndulo se balancea desde θ = θm a θ = −θm . Por último, por el mismo argumento,
244 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
usando el cambio de variable sen z = sen(θ/2)/ sen(θm /2). Para valores pequeños de θm ,
podrı́amos expandir K(x) para obtener
s
L 1 2
T = 2π 1+ θ + ... . (8.38)
g 16 m
donde f (3) es la tercera derivada de f (t) y ζ± es un valor ente t y t ± τ . Restando las dos
ecuaciones anteriores y reordenando tenemos,
f (t + τ ) − f (t − τ ) 1 2 (3)
f ′ (t) = − τ f (ζ) , (8.42)
2τ 6
donde f (3) (ζ) = (f (3) (ζ+ ) + f (3) (ζ− ))/2 y t − τ ≤ ζ ≤ t + τ . Esta es la aproximación en la
primera derivada centrada. El punto clave es que el error de truncamiento es ahora cuadrático
en τ , lo cual es un gran progreso sobre la aproximación de las derivadas adelantadas que tiene
un error de truncamiento O(τ ).
Usando las expansiones de Taylor para f (t + τ ) y f (t − τ ) podemos construir una fórmula
centrada para la segunda derivada. La que tiene la forma
f (t + τ ) + f (t − τ ) − 2f (t) 1
f ′′ (t) = 2
− τ 2 f (4) (ζ) , (8.43)
τ 12
donde t − τ ≤ ζ ≤ t + τ . De nuevo, el error de truncamiento es cuadrático en τ . La mejor
manera de entender esta fórmula es pensar que la segunda derivada está compuesta de una
derivada derecha y de una derivada izquierda, cada una con incrementos de τ /2.
Usted podrı́a pensar que el próximo paso serı́a preparar fórmulas más complicadas que
tengan errores de truncamiento aún más pequeños, quizás usando ambas f (t ± τ ) y f (t ± 2τ ).
Aunque tales fórmulas existen y son ocasionalmente usadas, las ecuaciones (8.10), (8.42) y
(8.43) sirven como el “caballo de trabajo” para calcular las derivadas primera y segunda.
~v (t + τ ) − ~v (t − τ )
+ O(τ 2 ) = ~a(~r(t)) , (8.46)
2τ
para la ecuación de la velocidad. Note que aunque los valores de velocidad son evaluados en
t + τ y t − τ , la aceleración es evaluada en el tiempo t.
Por razones que pronto serán claras, la discretización de la ecuación de posición estará cen-
trada entre t + 2τ y t,
~r(t + 2τ ) − ~r(t)
+ O(τ 2 ) = ~v (t + τ ) . (8.47)
2τ
246 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
Este es el método del “salto de la rana” (leap frog). Naturalmente, cuando el método es usado
en un programa, el término O(τ 3 ) no va y por lo tanto constituye el error de truncamiento
para el método.
El nombre “salto de la rana” es usado ya que la solución avanza en pasos de 2τ , con la
posición evaluada en valores pares (~r0 , ~r2 , ~r4 , . . . ), mientras que la velocidad está calculada en
los valores impares (~v1 , ~v3 , ~v5 , . . . ). Este entrelazamiento es necesario ya que la aceleración,
la cual es una función de la posición, necesita ser evaluada en un tiempo que está centrado
entre la velocidad nueva y la antigua. Algunas veces el esquema del “salto de la rana” es
formulado como
Estas ecuaciones, conocidas como el método de Verlet 5 , podrı́an parecer extrañas a pri-
mera vista, pero ellas son fáciles de usar. Suponga que conocemos ~r0 y ~r1 ; usando la ecuación
(8.59), obtenemos ~r2 . Conociendo ~r1 y ~r2 podrı́amos ahora calcular ~r3 , luego usando la ecua-
ción (8.58) obtenemos ~v2 , y ası́ sucesivamente.
Los métodos del “salto de la rana” y de Verlet tienen la desventaja que no son “autoini-
ciados”. Usualmente tenemos las condiciones iniciales ~r0 = ~r(t = 0) y ~v0 = ~v (t = 0), pero no
~v−1 = ~v (t = −τ ) [necesitado por el “salto de la rana” en la ecuación (8.50)] o ~r−1 = ~r(t = −τ )
[necesitado por Verlet en la ecuación (8.59)]. Este es el precio que hay que pagar para los
esquemas centrados en el tiempo.
Para lograr que estos métodos partan, tenemos una variedad de opciones. El método de
Euler-Cromer, usando la ecuación (8.53), toma ~v1/2 = ~v1 , lo cual es simple pero no muy
preciso. Una alternativa es usar otro esquema para lograr que las cosas partan, por ejemplo,
en el “salto de la rana” uno podrı́a tomar un paso tipo Euler para atrás, ~v−1 = ~v0 − τ~a0 .
Algunas precauciones deberı́an ser tomadas en este primer paso para preservar la precisión
del método; usando
τ2
~r−1 = ~r0 − τ~v0 + ~a(~r0 ) , (8.60)
2
es una buena manera de comenzar el método de Verlet.
Además de su simplicidad, el método del “salto de la rana” tiene propiedades favorables
(e.g. conservación de la energı́a). El método de Verlet tiene muchas ventajas. Primero, la
ecuación de posición tiene un error de truncamiento menor que otros métodos. Segundo, si la
fuerza es solamente una función de la posición y si nos preocupamos sólo de la trayectoria de
la partı́cula y no de su velocidad (como en muchos problemas de mecánica celeste), podemos
saltarnos completamente el cálculo de velocidad. El método es popular para el cálculo de las
trayectorias en sistemas con muchas partı́culas, e.g., el estudio de fluidos a nivel microscópico.
El algoritmo de Verlet, recien visto, no incorpora la velocidad en forma explı́cita. Una
variante, conocida como velocity Verlet, lo hace explı́citamente
1
~rn+1 = ~rn + τ~vn + τ 2~a(~rn ) (8.61)
2
τ
~vn+1 = ~vn + [~a(~rn ) + ~a(~rn+1 )] . (8.62)
2
Sin embargo, en ambas versiones del algoritmo sólo se consideran aceleraciones que de-
penden de las posiciones. Para enfretar un problema con aceleraciones que dependen tanto
de las posiciones como de las velocidades hay que usar la variante desarrollada por Martys y
Mountain de velocity Verlet 6 :
1
~rn+1 = ~rn + τ~vn + τ 2~a(~rn , ~vn ) (8.63)
2
~rn+1 − ~rn
~un+1/2 = (8.64)
τ
τ
~vn+1 = ~vn + ~a(~rn , ~vn ) + ~a(~rn+1 , ~un+1/2 ) , (8.65)
2
5
L. Verlet, “Computer experiments on classical fluid I. Thermodynamical properties of Lennard-Jones
molecules”, Phys. Rev. 159, 98-103 (1967).
6
N.S. Martys & R.D. Mountain, “Velocity Verlet algorithm for dissipative-particle-dynamics-based models
of suspensions”, Phys. Rev. E 59, 3733 (1999).
248 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
Tomar un paso para atrás para partir Verlet; ver ecuación (8.60).
Iterar sobre el número deseado de pasos con el paso de tiempo y método numérico dado.
Cuadro 8.2: Bosquejo del programa péndulo, el cual calcula el tiempo de evolución de un
péndulo simple usando el método de Euler o Verlet.
dω dθ
= α(θ) =ω , (8.66)
dt dt
donde la aceleración angular α(θ) = −g sen θ/L. El método de Euler para resolver estas
ecuaciones diferenciales ordinarias es iterar las ecuaciones:
θn+1 = θn + τ ωn , (8.67)
ωn+1 = ωn + τ αn . (8.68)
perı́odo por registrar cuando el ángulo cambia de signo; esto es verificar si θn y θn+1 tienen
signos opuestos probando si θn ∗ θn+1 < 0. Cada cambio de signo da una estimación para el
perı́odo, T̃k = 2τ (nk+1 − nk ), donde nk es el paso de tiempo en el cual el k-ésimo cambio de
signo ocurre. El perı́odo estimado de cada inversión de signo es registrado, y el valor medio
calculado como
M
D E 1 X
T̃ = T̃k , (8.70)
M k=1
40
30
20
10
Angulo [grados]
−10
−20
−30
−40
−50
0 5 10 15 20 25 30
Tiempo
Figura 8.4: Salida del programa péndulo usando el método de Euler. El ángulo inicial es
θm = 10◦ , el paso en el tiempo es τ = 0.1, y 300 iteraciones fueron calculadas.
20
15
10
5
Angulo [grados]
−5
−10
−15
−20
−25
0 5 10 15 20 25 30
Tiempo
Figura 8.5: Salida del programa péndulo usando el método de Euler. El ángulo inicial es
θm = 10◦ , el paso en el tiempo es τ = 0.05 y 600 iteraciones fueron calculadas.
T = 9.740, indicando que esta aproximación para (8.37) deja de ser válida para este ángulo
tan grande.
8.2. PÉNDULO SIMPLE. 251
15
10
Angulo [grados]
−5
−10
−15
0 5 10 15 20 25 30
Tiempo
Figura 8.6: Salida del programa péndulo usando el método de Verlet. El ángulo inicial es
θm = 10◦ , el paso en el tiempo es τ = 0.1 y 300 iteraciones fueron calculadas.
200
150
100
Angulo [grados]
50
−50
−100
−150
−200
0 5 10 15 20 25 30
Tiempo
Figura 8.7: Salida del programa péndulo usando el método de Verlet. El ángulo inicial es
θm = 170◦ , el paso en el tiempo es τ = 0.1 y 300 iteraciones fueron calculadas.
252 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
8.3.1. balle.py
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import math
from pylab import *
def main():
Cd=0.35
rho=1.293 # [kg/m^3]
radio=0.037 # [m]
A=math.pi*radio*radio
m=0.145 # [kg]
g=9.8 # [m/s^2]
a=-Cd*rho*A/(2.0*m)
salida=open("salida.txt", "w")
salidaT=open("salidaT.txt", "w")
x0=0
y0=input("Ingrese la altura inicial [m] :")
v0=input("Ingrese la velocidad inicial [m/s]:")
theta0=input("Ingrese el angulo inicial (grados):")
flagRA=2
vxn=v0*math.cos(math.pi*theta0/180.0)
vyn=v0*math.sin(math.pi*theta0/180.0)
xn=x0
yn=y0
tiempo = -tau
l_xT=[]
l_yT=[]
l_xn=[]
l_yn=[]
while yn >= y0:
8.3. LISTADO DE LOS PROGRAMAS EN PYTHON. 253
tiempo = tiempo+tau
aux_c=math.cos(math.pi*theta0/180.0)
aux_s=math.sin(math.pi*theta0/180.0)
l_xT.append(x0+v0*aux_c*tiempo)
l_yT.append(y0+v0*aux_s*tiempo-g*tiempo*tiempo/2.0)
l_xn.append(xn)
l_yn.append(yn)
if flagRA==0:
a=0.0
v=math.sqrt(vxn*vxn + vyn*vyn)
axn=a*v*vxn
ayn=a*v*vyn - g
xnp1 = xn + tau * vxn
ynp1 = yn + tau * vyn
vxnp1 = vxn + tau*axn
vynp1 = vyn + tau*ayn
vxn=vxnp1
vyn=vynp1
xn=xnp1
yn=ynp1
salida.close()
salidaT.close()
#
if __name__==’__main__’:
main()
254 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
8.3.2. pendulo.py
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import math
from matplotlib import rc
from pylab import *
rc(’text’, usetex=True)
def main():
respuesta = 2
while respuesta !=0 and respuesta != 1:
respuesta = input("Elija el metodo Euler=0 o Verlet=1 : ")
omega1=0
theta1=input("Ingrese el angulo inicial (en grados) : ")
theta1*=math.pi/180.0
tau = input("Ingrese el paso del tiempo : ")
pasos = input("Ingrese el numero de pasos : ")
periodo = []
theta0=theta1-tau*omega1-tau*tau*math.sin(theta1)
thetaNm1=theta0
thetaN=theta1
omegaN=omega1
nK=1
M=0
l_t=[]
l_theta=[]
for i in range(1,pasos):
alphaN=-math.sin(thetaN)
if respuesta==0: # Euler
thetaNp1=thetaN+tau*omegaN
omegaNp1=omegaN+tau*alphaN
else: # Verlet
thetaNp1=2.0*thetaN-thetaNm1+tau*tau*alphaN
omegaNp1=0.0 #solo por completitud
8.3. LISTADO DE LOS PROGRAMAS EN PYTHON. 255
l_t.append((i-1)*tau)
l_theta.append(thetaNp1*180/math.pi)
print >> salida, (i-1)*tau,thetaNp1*180/math.pi
if thetaNp1*thetaN<0:
if M==0:
M+=1
periodo.append(0.0)
nK=i
else:
M+=1
periodo.append(2.0*tau*(i-nK))
nK=i
thetaNm1=thetaN
thetaN=thetaNp1
omegaN=omegaNp1
Tprom=0
for i in range(1,M):
Tprom += periodo[i]
Tprom /= float(M-1)
ssr=0
for i in range(1,M):
ssr+=(periodo[i]-Tprom)*(periodo[i]-Tprom)
ssr/=float(M-2)
sigma=math.sqrt(ssr/float(M-1))
print "Periodo = ", Tprom, "+/-", sigma
salida.close()
plot(l_t, l_theta,’ro’)
xlabel(r’\textbf{\Large Tiempo (s)}’)
ylabel(r’\textbf{\Large\’Angulo (grados)}’)
title(r’\textbf{\Huge P\’endulo no lineal}’)
grid(True)
show()
#
if __name__==’__main__’:
main()
256 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
8.4.1. balle.cc
#include "NumMeth.h"
int main()
{
const double Cd=0.35;
const double rho=1.293; // [kg/m^3]
const double radio=0.037; // [m]
double A= M_PI*radio*radio ;
double m=0.145; // [kg]
double g=9.8; // [m/s^2]
double a = -Cd*rho*A/(2.0e0*m) ;
int flagRA = 2 ;
while (flagRA!=0 && flagRA !=1) {
cout <<"Con resistencia del aire, Si= 1, No= 0: ";
cin >> flagRA;
}
cout <<"Ingrese el paso en el tiempo, tau en [s]: ";
cin >> tau ;
double vxn=v0*cos(M_PI*theta0/180.0) ;
double vyn=v0*sin(M_PI*theta0/180.0) ;
double xn=x0 ;
double yn=y0 ;
double tiempo = -tau;
while( yn >= y0) {
tiempo +=tau ;
salidaT << x0+v0*cos(M_PI*theta0/180.0) *tiempo <<" " ;
salidaT << y0+v0*sin(M_PI*theta0/180.0) *tiempo -g*tiempo*tiempo/2.0e0<< endl;
8.4. LISTADO DE LOS PROGRAMAS EN C++. 257
8.4.2. pendulo.cc
#include "NumMeth.h"
int main()
{
int respuesta=2 ;
while(respuesta != 0 && respuesta !=1 ) {
cout << "Elija el metodo: Euler=0 y Verlet=1 : " ;
cin >> respuesta ;
}
double theta1 ;
double omega1 = 0.0e0;
cout << "Ingrese el angulo inicial (grados): ";
cin >> theta1 ;
theta1*=M_PI/180.0e0 ;
double tau ;
cout << "Ingrese el paso de tiempo: ";
cin >> tau ;
int pasos ;
cout << "Ingrese el numero de pasos: ";
cin >> pasos ;
double thetaNm1=theta0 ;
double thetaN=theta1 ;
double omegaN=omega1;
int nK=1;
int M=0 ;
thetaNm1=thetaN ;
thetaN=thetaNp1 ;
omegaN=omegaNp1 ;
}
double Tprom=0.0e0;
for (int i=1; i < M; i++) Tprom+=periodo[i] ;
Tprom/=double(M-1) ;
double ssr=0.0 ;
for (int i=1; i < M; i++) ssr+=(periodo[i]-Tprom)* (periodo[i]-Tprom);
ssr/=double(M-2);
double sigma =sqrt(ssr/double(M-1)) ;
cout <<" Periodo = "<< Tprom << "+/-"<< sigma << endl ;
8.4. LISTADO DE LOS PROGRAMAS EN C++. 259
salida.close() ;
delete [] periodo;
return 0;
}
260 CAPÍTULO 8. EDO: MÉTODOS BÁSICOS.
Capı́tulo 9
Ecuaciones Diferenciales
Ordinarias II: Métodos Avanzados.
versión final 4.0, 22 Noviembre 20071
261
262 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
a algunos textos de mecánica estándar, tales como Symon2 o Landau y Lifshitz3 . La energı́a
total del satélite es
1 GM m
E = mv 2 − , (9.2)
2 r
donde r = | ~r | y v = | ~v |. Esta energı́a total es conservada, tal como el momento angular,
~ = ~r × (m~v ) .
L (9.3)
q
2b
r
x
Q
2a
La excentricidad de la Tierra es e = 0.017, por lo tanto esta órbita está muy cercana de ser
circular. La distancia del Sol al perihelio (punto de mayor aproximación) es q = (1 − e)a; la
distancia del Sol al afelio es Q = (1 + e)a.
La ecuación (9.6) también se mantiene para una órbita elı́ptica, si reemplazamos el radio
con el semieje mayor; por lo tanto la energı́a total es
GM m
E=− . (9.8)
2a
Note que E ≤ 0. De las ecuaciones (9.2) y (9.8), encontramos que la velocidad orbital como
función de la distancia radial es
s
2 1
v = GM − . (9.9)
r a
Cuadro 9.2: Bosquejo del programa orbita, el cual calcula la trayectoria de un cometa usando
varios métodos numéricos.
90 o
30
Energía Cinética
Energía Potencial
Energía [M AU*AU/año*año]
Energía Total
20
10
180o 0o
−10
−20
−30
−40
0 0.5 1 1.5 2 2.5 3 3.5 4
3 2 1 0 1 2 3
Figura 9.2: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Euler. La distancia radial inicial es 1 [AU] y la velocidad tangencial inicial es 2π [AU/año].
El paso en el tiempo es τ = 0.02 [años]; y 200 pasos son calculados. Los resultados están en
desacuerdo con la predicción teórica de una órbita circular con energı́a total constante.
energı́a llega a ser positiva; el satélite alcanza la velocidad de escape. Si bajamos el paso de
tiempo desde τ = 0.02 [años] a τ = 0.005 [años] obtenemos mejores resultados, como los
mostrados en la figura 9.5. Estos resultados no son del todo perfectos; la órbita puede ser
una elipse cerrada, pero todavı́a tiene una notable deriva espúria.
En este punto usted se podrı́a estar preguntando, “¿Por qué estamos estudiando este
problema?, si la solución analı́tica es bien conocida”. Es verdad que hay problemas mecánicos
celestes más interesantes (e.g., el efecto de perturbaciones sobre la órbita, problema de tres
cuerpos). Sin embargo, antes de hacer los casos complicados podrı́amos, siempre, chequear
los algoritmos de problemas conocidos. Suponga que introducimos una pequeña fuerza de
arrastre sobre el cometa. Podrı́amos pecar de inocentes creyendo que la precisión de la figura
9.5 fue un fenómeno fı́sico más que un artefacto numérico.
90 o
30
Energía [M AU*AU/año*año]
20
Energía Cinética
10
Energía Potencial
Energía Total
0
180o 0o −10
−20
−30
−40
−50
1.5 1 0.5 0 0.5 1 1.5 0 0.5 1 1.5 2 2.5 3 3.5 4
Figura 9.3: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Euler-Cromer. Los parámetros son los mismos que en la figura 9.2. Los resultados están en
un acuerdo cualitativo al menos con la predicción teórica de una órbita circular con energı́a
total constante.
90o
Energía Potencial
Energía Total
100
180 o 0o
0
−100
−200
270 o 0 1 2 3 4
30 20 10 0 10 20 30
Distancia [AU] Tiempo [años]
Figura 9.4: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Euler-Cromer. La distancia radial inicial es 1 [AU] y la velocidad tangencial inicial es
π [AU/año]. El paso en el tiempo es τ = 0.02 [años]; y 200 pasos son calculados. Debido al
error numérico el cometa alcanza la velocidad de escape, la posición final es 35 [AU] y la
energı́a total es positiva.
9.2. MÉTODOS DE RUNGE-KUTTA. 267
90 o
300
Energía [M AU*AU/años*años]
Energía Cinética
200 Energía Potencial
Energía Total
100
180o 0o 0
−100
−200
−300
270o
1 0.5 0 0.5 1 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
Distancia [AU] Tiempo [años]
Figura 9.5: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Euler-Cromer. Los parámetros son los mismos que en la figura 9.4 excepto que el paso de
tiempo es más pequeño τ = 0.005 [años]. Los resultados son mejores, pero aún presenta una
precesión espúria.
d~x
= f~(~x(t), t) , (9.15)
dt
donde el vector de estado x(t) = [x1 (t), x2 (t), . . . xN (t)] es la solución deseada. En el problema
de Kepler tenemos
~x(t) = [rx (t), ry (t), vx (t), vy (t)] , (9.16)
y
~ drx dry dvx dvy
f (~x(t), t) = , , , ,
dt dt dt dt (9.17)
= [vx (t), vy (t), Fx (t)/m, Fy (t)/m] ,
Nuestro punto de partida es el método simple de Euler; en forma vectorial podrı́a ser
escrito como
~x(t + τ ) = ~x(t) + τ f~(~x, t) . (9.18)
Consideremos la primera fórmula de Runge-Kutta:
1 1
~x(t + τ ) = ~x(t) + τ f~ ~x t + τ , t + τ ,
∗
(9.19)
2 2
donde
1 1
~x t + τ ≡ ~x(t) + τ f~(~x, t) .
∗
(9.20)
2 2
Para ver de dónde viene esta fórmula, consideremos por el momento el caso de una
variable. Sabemos que la expansión de Taylor
dx(ζ)
x(t + τ ) = x(t) + τ ,
dt (9.21)
= x(t) + τ f (x(ζ), ζ) ,
es exacta para algún valor de ς entre t y t + τ , como se vio en la ecuación (8.10). La fórmula
de Euler toma ς = t; Euler-Cromer usa ς = t en la ecuación de velocidad y ς = t + τ en la
ecuación de posición. Runge-Kutta usa ς = t+ 21 τ , lo cual pareciera ser una mejor estimación.
1
Sin embargo, x t + 2 τ no es conocida, podemos aproximarla de la manera simple: usando
1
un paso de Euler calculamos x t + 2 τ y usando esta como nuestra estimación de x t + 12 τ .
∗
1
~x(t + τ ) = ~x(t) + τ [f~(~x(t), t) + f~(~x ∗ (t + τ ), t + τ )] , (9.23)
2
donde
~x ∗ (t + τ ) ≡ ~x(t) + τ f~(~x(t), t) . (9.24)
Para entender este esquema, consideremos nuevamante el caso en una variable. En nuestra
fórmula original, estimamos que f (x(ς), ς) como 21 [f (x, t) + f (x ∗ (t + τ ), t + τ )].
Estas expresiones pueden ser deducidas usando la expansión de Taylor con dos variables,
∞ n
X 1 ∂ ∂
f (x + h, t + τ ) = h +τ f (x, t) , (9.25)
n=0
n! ∂x ∂t
donde todas las derivadas son evaluadas en (x, t). Para una fórmula general de Runge-Kutta
de segundo orden queremos obtener una expresión de la siguiente forma
donde
x∗ ≡ x(t) + βτ f (x(t), t) . (9.27)
1 1
w1 = 0 , w2 = 1 α= , β= , (9.28)
2 2
1 1
w1 = , w2 = , α=1, β=1. (9.29)
2 2
Deseamos seleccionar cuatro coeficientes tal que tengamos una precisión de segundo orden;
esto es deseamos calzar la serie de Taylor a través de los términos de la segunda derivada.
Los detalles del cálculo se proponen como un ejercicio, pero cualquier grupo de coeficientes
satisfacen las relaciones siguientes w1 + w2 = 1, αw2 = 1/2 y α = β darán un esquema
Runge-Kutta de segundo orden. El error de truncamiento local es O(τ 3 ), pero la expresión
explı́cita no tiene una forma simple. No está claro que un esquema sea superior al otro ya
que el error de truncamiento, siendo una función complicada de f (x, t), variará de problema
a problema.
270 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
F~1 = f~(~x, t) ,
~ ~ 1 ~ 1
F2 = f ~x + τ F1 , t + τ ,
2 2
(9.31)
~ ~ 1 ~ 1
F3 = f ~x + τ F2 , t + τ ,
2 2
F~4 = f~(~x + τ F~3 , t + τ ) .
El siguiente extracto del Numerical Recipes4 resume mejor el estado que las fórmulas de
arriba tienen en el mundo del análisis numérico:
Usted se preguntará, ¿por qué fórmulas de cuarto orden y no de orden superior? Bien, los
métodos de orden superior tienen un error de truncamiento mejor, pero también requieren
más cálculo, esto es, más evaluaciones de f (x, t). Hay dos opciones, hacer más pasos con un τ
pequeño usando un método de orden inferior o hacer pocos pasos con un τ más grande usando
un método de orden superior. Ya que los métodos de Runge-Kutta de órdenes superiores son
muy complicados, el esquema de cuarto orden dado anteriormente es muy conveniente. Entre
paréntesis, el error de truncamiento local para Runge-Kutta de cuarto orden es O(τ 5 ).
Para implementar métodos de cuarto orden para nuestro problema de la órbita, usaremos
la función rk4 (tabla 9.3). Esta función toma como datos: el estado actual del sistema, ~x(t); el
paso de tiempo para ser usado, τ ; el tiempo actual, t; la función f~(~x(t), t; λ); donde λ es una
lista de parámetros usados por f~. La salida es el nuevo estado del sistema, ~x(t + τ ), calculado
por el método de Runge-Kutta. Usando Runge-Kutta de cuarto orden da los resultados
mostrados en la figura 9.6, la cual es mucho mejor que las obtenidas usando el método de
Euler-Cromer (figura 9.5).
4
W. Press, B. Flannery, S. Tukolsky and W. Vetterling, Numerical Recipes in fortran, 2nd ed. (Cam-
bridge: Cambridge University Press 1992).
9.2. MÉTODOS DE RUNGE-KUTTA. 271
Salidas: ~x(t + τ ).
Cuadro 9.3: Bosquejo de la función rk4, la cual evalúa un paso simple usando el método
Runge-Kutta de cuarto orden.
Salidas: d~x(t)/dt.
Cuadro 9.4: Bosquejo de la función gravrk, la cual es usada por la función Runge-Kutta para
evaluar las ecuaciones de movimiento para el problema de Kepler.
90 o
300
Energía Cinética
Energía [M AU*AU/años*años]
Energía Potencial
Energía Total
200
100
o
180 0o 0
−100
−200
−300
1 0.5 0 0.5 1 0 0.2 0.4 0.6 0.8 1.0
Distancia [AU] o Tiempo [años]
270
Figura 9.6: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Runge-Kutta. La distancia radial inicial es 1 [AU] y la velocidad tangencial inicial es
π [AU/año]. El paso en el tiempo es τ = 0.005 [años]; y 200 pasos son calculados. Comparemos
con la figura 9.5.
Cuando es llamado por orbita, esta función recibe un puntero a gravrk en la variable
derivsRK. Dentro de rk4, la sentencia
(*derivsRK)( x, t, param, F1 ) ;
es equivalente a
gravrk( x, t, param, F1 ) ;
tenemos sólo una idea apróximada de lo que τ pudiera ser; ahora tenemos que seleccionar un
τmı́n y un τmáx y una manera de intercambiar entre ellos. Si tenemos que hacer esto por prueba
y error manual, podrı́a ser peor que haciéndolo por la fuerza bruta calculando con un paso
de tiempo pequeño toda la trayectoria. Idealmente, deseamos estar completamente liberados
de tener que especificar un paso de tiempo. Deseamos tener una trayectoria calculada de la
misma posición inicial hasta algún tiempo final con la seguridad de que la solución es correcta
a una precisión especificada
Los programas adaptativos continuamente monitorean la solución y modifican el paso de
tiempo para asegurar que se mantenga la precisión especificada por el usuario. Esos programas
pueden hacer algunos cálculos extras para optimizar la elección de τ , en muchos casos este
trabajo extra vale la pena. Aquı́ está una manera para implementar esta idea: dado el estado
actual ~x(t), el programa calcula ~x(t + τ ) como siempre, y luego repite el cálculo haciendolo
en dos pasos, cada uno con paso de tiempo τ2 . Visualmente, esto es
paso grande
x b(t+ τ)
x (t)
x (t+τ/2) x s (t+ τ)
paso pequeño paso pequeño
La diferencia entre las dos respuestas, ~xb (t+τ ) y ~xs (t+τ ), estima el error de truncamiento
local. Si el error es tolerable, el valor calculado es aceptado y un valor mayor de τ es usado
en la próxima iteración. Por otra parte, si el error es muy grande, la respuesta es rebotada, el
paso de tiempo es reducido y el procedimiento es repetido hasta que se obtenga una respuesta
aceptable. El error de truncamiento estimado para el actual paso de tiempo puede guiarnos
en seleccionar en nuevo paso de tiempo para la próxima iteración.
Ya que esto es sólo una estimación, el nuevo paso de tiempo es τnuevo = S1 τest , donde S1 < 1.
Esto nos hace sobreestimar el cambio cuando disminuimos τ y subestimar el cambio cuan-
do lo aumentamos. Malogramos los esfuerzos computacionales cada vez que rebotamos una
respuesta y necesitamos reducir el paso de tiempo, por lo tanto es mejor ajustar τnuevo < τest .
Podrı́amos poner un segundo factor de seguridad, S2 < 1, para asegurarse que el programa
no sea demasiado entusiasta en aumentar o disminuir precipitadamente el paso de tiempo.
274 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
Cuadro 9.5: Bosquejo de la función rka, la cual evalúa un único paso usando un método
adaptativo de Runge-Kutta de cuarto orden.
Esto obliga a asegurar que nuestro nueva estimación para τ nunca aumente o decrezca
por más que un factor S2 . Por supuesto, este nuevo τ podrı́a ser insuficientemente pequeño,
y tendrı́amos que continuar reduciendo el paso de tiempo; pero al menos sabrı́amos que no
ocurrirá de un modo incontrolado.
Este procedimiento no es “a prueba de balas”, los errores de redondeo llegan a ser signi-
ficativos en pasos de tiempos muy pequeños. Por esta razón la iteración adaptativa podrı́a
fallar para encontrar un paso de tiempo que de la precisión deseada. Debemos mantener esta
limitación en mente cuando especifiquemos el error aceptable. Una función de Runge-Kutta
adaptativa, llamada rka, es esbozada en la tabla 9.5. Note que los datos de entrada en la
secuencia de llamada son los mismos que para rk4, excepto por la suma de ∆i , el error ideal
especificado. Las salidas del rka son el nuevo estado del sistema, ~x(t′ ); el tiempo nuevo, t′ y
el nuevo paso de tiempo, τ nuevo, el cual podrı́a ser usado la próxima vez que sea llamada
la rka.
Usando el método de Runge-Kutta adaptativo, el programa orbita da los resultados en
la figura 9.7 para una órbita altamente elı́ptica. Notemos que el programa toma muchos más
pasos en el perihelio que en el afelio. Podemos comparar con los resultados usando el método
de Runge-Kutta no adaptativo (figura 9.6) en el cual los pasos en el perihelio son ampliamente
9.3. MÉTODOS ADAPTATIVOS 275
90 o 1500
Energía [M AU*AU/años*años]
Energía Cinética
Energía Potencial
Energía Total
1000
500
180o 0o 0
−500
−1000
−1500
o 0 0.05 0.1 0.15 0.2 0.25
270
1 0.5 0 0.5 1
Figura 9.7: Gráfico de la trayectoria y la energı́a desde el programa orbita usando el método
de Runge-Kutta adaptativo. La distancia radial inicial es 1 [AU] y la velocidad tangencial
inicial es π/2 [AU/año]. El paso inicial en el tiempo es τ = 0.1 [años]; y 40 pasos son
calculados.
0.1
0.01
Tau [aæos]
0.001
0.0001
0.01 0.1 1
Distancia [Au]
Figura 9.8: Paso de tiempo como función de la distancia radial desde el programa orbita
usando el método de Runge-Kutta adaptativo. Los paramétros son los mismos de la figura
9.7.
espaciados. Una gráfica de los pasos de tiempo versus la distancia radial (figura 9.8) muestra
que τ varı́a casi tres órdenes de magnitud.√Interesantemente esta gráfica revela una relación
exponencial aproximada de la forma τ ∝ r3 . Por supuesto esta dependencia nos recuerda
la tercera ley de Kepler, ecuación (9.10). Esperamos alguna dispersión en los puntos, ya que
nuestra rutina adaptada solamente estima el paso de tiempo óptimo.
276 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
9.4.1. orbita.py
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import sys
import math
from matplotlib import rc
from pylab import *
import numpy
rc(’text’, usetex=True)
GM=4.0e0*math.pi*math.pi
masaCometa = 1.0e0
adaptErr = 1.0e-3
def gravrk(x,t):
global GM
vX=x[2]
vY=x[3]
mod_r= math.sqrt(x[0]*x[0]+x[1]*x[1])
aX= -GM*x[0]/(mod_r*mod_r*mod_r)
aY= -GM*x[1]/(mod_r*mod_r*mod_r)
## Retorna la derivada
return numpy.array( [vX,vY,aX,aY] )
## Evaluemos F1=f(x,t)
F1 = gravrk(x,tiempo)
half_tau = tau/2.0e0
t_half = tiempo+half_tau
t_full = tiempo+tau
xtemp = x + half_tau*F2
F3 = gravrk(xtemp, t_half)
## Retornamos x(t+tau)
return x + tau*(F1+F4+2.0*(F2+F3))/6.0
def rka( x ):
global adaptErr
tiempo = x[4]
tSave = tiempo
tau=x[5]
## factores de seguridad
safe1 = 0.9
safe2 = 4.0
maxTray=100
for i in range(0,4):
scale = adaptErr * (math.fabs(xSmall[i]) + fabs(xBig[i]))/2.0e0
xDiff = xSmall[i] - xBig[i]
ratio = fabs(xDiff)/(scale+eps)
if erroRatio <= ratio :
erroRatio=ratio
278 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
def main():
salidaO = open("Orbita.txt","w")
salidaE = open("Energia.txt","w")
salidaT = open("Tau.txt","w")
xN= x0
yN= y0
vxN=vx0
vyN=vy0
tiempo = 0.0e0
l_r=[]
l_theta=[]
l_tiempo=[]
l_Ek=[]
l_Ep=[]
l_ET=[]
l_distancia=[]
l_tau=[]
Ek = masaCometa*v2/2.0e0
Ep = -GM*masaCometa/r
ET= Ep+Ek
l_tiempo.append(tiempo)
l_Ek.append(Ek)
l_Ep.append(Ep)
l_ET.append(ET)
print >> salidaE, tiempo, Ek, Ep, ET
modr3=math.pow(xN*xN+yN*yN, 3.0/2.0)
axN= -GM*xN/modr3
ayN= -GM*yN/modr3
if metodo==1: ## Euler
vxNp1=vxN+tau*axN
vyNp1=vyN+tau*ayN
xNp1= xN+tau* vxN
yNp1= yN+tau* vyN
tiempo += tau
elif metodo==2: ## Euler-Cromer
280 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
vxNp1=vxN+tau*axN
vyNp1=vyN+tau*ayN
xNp1= xN+tau* vxNp1
yNp1= yN+tau* vyNp1
tiempo += tau
elif metodo==3: ## Runge-Kutta 4to Orden
x= numpy.array( [xN,yN,vxN,vyN] )
x_new = rk4( x, tiempo, tau)
xNp1=x_new[0]
yNp1=x_new[1]
vxNp1=x_new[2]
vyNp1=x_new[3]
tiempo += tau
xN=xNp1
yN=yNp1
vxN=vxNp1
vyN=vyNp1
salidaO.close()
salidaE.close()
salidaT.close()
figure(1)
ax = axes([0.1, 0.1, 0.8, 0.8], polar=True, axisbg=’#d5de9c’)
polar(l_theta, l_r, color=’#ee8d18’, lw=2)
title(r’\textbf{\Huge \’Orbita}’)
9.4. LISTADOS DEL PROGRAMA. 281
figure(2)
linea_Ek = plot(l_tiempo, l_Ek)
setp(linea_Ek, color=’b’, linewidth=1.0)
linea_Ep = plot(l_tiempo, l_Ep)
setp(linea_Ep, color=’g’, linewidth=1.0)
linea_ET = plot(l_tiempo, l_ET)
setp(linea_ET, color=’r’, linewidth=1.0)
legend((r’Energ\’ia Cin\’etica’, r’Energ\’ia Potencial’, r’Energ\’ia Total’),
’upper right’, shadow=True)
xlabel(r’\textbf{\Large Tiempo (a\~nos)}’)
ylabel(r’\textbf{\Large Energ\’ia (M*AU$^2$/a\~no$^2$)}’)
title(r’\textbf{\Huge Energ\’ia}’)
grid(True)
if metodo == 4:
figure(3)
loglog(l_distancia,l_tau,"b+")
xlabel(r’\textbf{\Large Distancia (AU)}’)
ylabel(r’\textbf{\Large $\tau$ (a\~no)}’)
title(r’\textbf{\Huge Variaci\’on del paso de tiempo}’)
grid(True)
show()
#
if __name__==’__main__’:
main()
9.4.2. orbita.cc
#include "NumMeth.h"
// Evaluemos F1=f(x,t)
(*derivsRK) (x, t, param, F1) ;
// Retornamos x(t+tau)
void rka ( double * x, int nX, double & t, double & tau, double erro,
void(*derivsRK)(double *, double, double, double *),
double param)
{
double tSave = t ;
double safe1 = 0.9, safe2 = 4.0 ; //factores de seguridad
// Retorna la derivada
deriv[0] = vX;
284 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
deriv[1] = vY;
deriv[2] = aX;
deriv[3] = aY;
}
int main()
{
ofstream salidaO ("Orbita.txt") ;
ofstream salidaE ("Energia.txt") ;
ofstream salidaT ("Tau.txt") ;
double r0 ;
cout << "Ingrese la distancia radial inicial [AU]: " ;
cin >> r0 ;
double vT ;
cout << "Ingrese la velocidad tangencial inicial [AU/annos]: " ;
cin >> vT ;
double x0=r0 ;
double y0=0.0e0;
double vx0=0.0e0 ;
double vy0=vT;
//
// Suponemos angulo inicial nulo
//
int metodo = 0 ;
while( metodo < 1|| metodo > 4 ) {
cout << "Ingrese el metodo a usar :" << endl ;
cout << "\t Metodo de Euler \t\t\t[1]" << endl;
cout << "\t Metodo de Euler-Cromer \t\t[2]" << endl;
cout << "\t Metodo de Runge-Kutta 4 orden \t\t[3]" << endl;
cout << "\t Metodo de Runge-Kutta adaptativo \t[4]" << endl;
cout << "elija: " ;
cin >> metodo ;
}
double tau ;
cout << "Ingrese paso en el tiempo: " ;
cin >> tau ;
int numPasos ;
cout << "Ingrese el numero de pasos: " ;
cin >> numPasos ;
double param=GM ;
const int dimX= 4;
double * x = new double[dimX] ;
9.4. LISTADOS DEL PROGRAMA. 285
double Ep = -GM*masaCometa/r ;
double Ek = masaCometa*v2/2.0e0 ;
double ET= Ep+Ek ;
salidaE<< tiempo << " " << Ek<< " " << Ep<<" " << ET << endl ;
xNp1=x[0] ;
yNp1=x[1];
vxNp1=x[2];
vyNp1=x[3];
tiempo += tau ;
}
break ;
case 4: {
x[0] = xN;
x[1] = yN;
x[2] = vxN;
x[3] = vyN;
rka( x, dimX, tiempo, tau, adaptErr, gravrk, param);
double distancia = sqrt( x[0]*x[0]+x[1]*x[1]) ;
salidaT<< distancia << " " <<tau << endl ;
xNp1=x[0] ;
yNp1=x[1];
vxNp1=x[2];
vyNp1=x[3];
}
}
xN=xNp1 ;
yN=yNp1 ;
vxN=vxNp1 ;
vyN=vyNp1 ;
}
salidaO.close() ;
salidaE.close() ;
salidaT.close() ;
delete [] x;
return 0;
}
9.4.3. vector4d.h
#ifndef _vector_4d_h
#define _vector_4d_h
//
// Clase basica de vectores 4d para Runge-Kutta
//
#include <iostream>
class Vector{
9.4. LISTADOS DEL PROGRAMA. 287
private:
double c_t;
double c_x;
double c_y;
double c_z;
public:
Vector():c_t(0),c_x(0),c_y(0),c_z(0) {} ;
Vector(double t,double x, double y, double z):c_t(t),c_x(x),c_y(y),c_z(z) {} ;
void set(double,double, double, double);
double componente(int);
~Vector() {} ;
double t() const {return c_t;};
double x() const {return c_x;};
double y() const {return c_y;};
double z() const {return c_z;};
};
9.4.4. vector4d.cc
#include "vector4d.h"
double Vector::componente(int i) {
double d=0;
if (i==0) {
d=c_t;
} else if(i==1) {
d=c_x;
} else if(i==2) {
d=c_y;
} else if(i==3) {
288 CAPÍTULO 9. EDO II: MÉTODOS AVANZADOS.
d=c_z;
} else {
std::cout << "Error en componente"<< std::endl;
exit(-1);
}
return d;
}
9.4.5. orbita2.cc
#include "NumMeth.h"
#include "vector4d.h"
int main()
{
ofstream salidaO ("Orbita.txt") ;
ofstream salidaE ("Energia.txt") ;
9.4. LISTADOS DEL PROGRAMA. 289
double r0 ;
cout << "Ingrese la distancia radial inicial [AU]: " ;
cin >> r0 ;
double vT ;
cout << "Ingrese la velocidad tangencial inicial [AU/a\~nos]: " ;
cin >> vT ;
double x0=r0 ;
double y0=0.0e0;
double vx0=0.0e0 ;
double vy0=vT;
//
// Suponemos angulo inicial nulo
//
int metodo = 0 ;
while( metodo < 1|| metodo > 4 ) {
cout << "Ingrese el m\’etodo num\’erico a usar :" << endl ;
cout << "\t M\’etodo de Euler \t\t\t[1]" << endl;
cout << "\t M\’etodo de Euler-Cromer \t\t[2]" << endl;
cout << "\t M\’etodo de Runge-Kutta 4 orden \t\t[3]" << endl;
cout << "\t M\’etodo de Runge-Kutta adaptativo \t[4]" << endl;
cout << "elija: " ;
cin >> metodo ;
}
double tau ;
cout << "Ingrese paso en el tiempo: " ;
cin >> tau ;
int numPasos ;
cout << "Ingrese el n\’umero de pasos: " ;
cin >> numPasos ;
Vector x;
double Ep = -GM*masaCometa/r ;
double Ek = masaCometa*v2/2.0e0 ;
double ET= Ep+Ek ;
salidaE<< tiempo << " " << Ek<< " " << Ep<<" " << ET << endl ;
}
}
xN=xNp1 ;
yN=yNp1 ;
vxN=vxNp1 ;
vyN=vyNp1 ;
}
salidaO.close() ;
salidaE.close() ;
salidaT.close() ;
return 0;
}
// Retornamos x(t+tau)
int maxTray=100 ;
for (int iTray=0; iTray<maxTray; iTray++) {
// Tomemos dos peque\~nos pasos en el tiempo
double half_tau = 0.5*tau ;
Vector xSmall = x ;
xSmall=rk4( xSmall, tSave, half_tau) ;
t= tSave + half_tau ;
xSmall=rk4( xSmall, t, half_tau) ;
// Tomemos un solo tiempo grande
Vector xBig = x;
xBig=rk4( xBig, tSave, tau) ;
f~(~x ∗ ) = 0 , (10.2)
o
fi (x∗1 , x∗2 , . . . , x∗N ) = 0 , para todo i, (10.3)
1
Este capı́tulo está basado en el cuarto capı́tulo del libro: Numerical Methods for Physics, second edition
de Alejandro L. Garcia, editorial Prentice Hall.
2
R. Seydel, From Equilibrium to Chaos, Practical Bifurcation and Stability Analysis (New York: Elsevier,
1988).
293
294 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
ya que esto implica que d~x ∗ /dt = 0. Localizar los estados estacionarios se reduce al problema
de resolver N ecuaciones con N incógnitas xi ∗ . Este problema es también llamado “encontrar
las raices de f (x)”.
Como un ejemplo, consideremos el péndulo simple; el estado está descrito por el ángulo
θ y la velocidad angular ω. Los estados estacionarios se encuentran al resolver las ecuaciones
no lineales
g
− sen θ∗ = 0 , ω ∗ = 0 . (10.4)
L
Las raices son θ∗ = 0, ±π, ±2π, . . . y ω ∗ = 0. Por supuesto que no todos los sistemas son tan
fáciles de resolver.
Deberı́a ser claro que encontrar raices tiene muchas más aplicaciones que calcular los
estados estacionario. En este capı́tulo consideraremos una variedad de maneras para resolver
ambos sistemas lineales y sistemas no lineales. En esta sección y en la próxima consideraremos
sistemas lineales, dejando los sistemas no lineales para la parte final del capı́tulo.
x1 + x2 + x3 = 6,
3x2 + x3 = 9, (10.10)
− 13 x3 = −1 .
10.1.3. Pivoteando.
La eliminación Gaussiana es un procedimiento simple, sin embargo, se debe estar conciente
de sus riesgos. Para ilustrar la primera fuente de problemas, consideremos el conjunto de
ecuaciones
ǫx1 + x2 + x3 = 5 ,
x1 + x2 = 3, (10.11)
x1 + x3 = 4 .
En el lı́mite ǫ → 0 la solución es x1 = 1, x2 = 2, x3 = 3. Para estas ecuaciones, el primer
paso de la eliminación hacia adelante podrı́a partir por multiplicar la primera ecuación por
(1/ǫ) y sustrayéndola de la segunda y tercera ecuaciones, lo que da
ǫx1 + x2 + x3 = 5,
(1 − 1/ǫ)x2 − (1/ǫ)x3 = 3 − 5/ǫ , (10.12)
−(1/ǫ)x2 + (1 − 1/ǫ)x3 = 4 − 5/ǫ .
Por supuesto, si ǫ = 0 tenemos grandes problemas, ya que el factor 1/ǫ estalla. Aún si ǫ 6= 0,
pero es pequeño, vamos a tener serios problemas de redondeo. Supongamos que 1/ǫ es tan
3
G.E. Forsythe and C.B. Moler, Computer Solution of Linear Algebraic System (Upper Saddle River, N.J.:
Prentice-Hall, 1967).
4
sparse
296 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
grande que (C − 1/ǫ) → −1/ǫ, donde C es del orden de la unidad. Nuestras ecuaciones,
después del redondeo, llegan a ser
ǫx1 + x2 + x3 = 5,
−(1/ǫ)x2 − (1/ǫ)x3 = −5/ǫ , (10.13)
−(1/ǫ)x2 − (1/ǫ)x3 = −5/ǫ .
En este punto está claro que no podemos proceder ya que la segunda y la tercera ecuación en
(10.13) son ahora idénticas; no tenemos más que tres ecuaciones independientes. El próximo
paso de eliminación serı́a transformar la tercera ecuación en (4.13) en la tautologı́a 0 = 0.
Afortunadamente, hay una solución simple: intercambiar el orden de las ecuaciones antes
de realizar la eliminación. Cambiando las ecuaciones primera y segunda en (10.11),
x1 + x2 = 3,
ǫx1 + x2 + x3 = 5 , (10.14)
x1 + x3 = 4 .
x1 + x2 = 3,
(1 − ǫ)x2 + x3 = 5 − 3ǫ , (10.15)
−x2 + x3 = 4 − 3 .
x1 + x2 = 3,
x2 + x3 = 5 , (10.16)
−x2 + x3 = 1 .
x1 + x2 = 3,
x2 + x3 = 5 , (10.17)
2x3 = 6 .
10.1.4. Determinantes.
Es fácil obtener el determinante de una matriz usando la eliminación Gaussiana. Después
de completar la eliminación hacia adelante, uno simplemente calcula el producto de los coe-
ficientes de los elementos diagonales. Tomamos nuestro ejemplo original, ecuación (10.8). La
matriz es
1 1 1
Ǎ = −1 2 0 . (10.18)
2 0 1
Completada la eliminación hacia adelante, ecuación (10.10), el producto de los coeficientes de
los elementos diagonales es (1)(3)(− 13 ) = −1, el cual, usted puede comprobar, es el determi-
nante de Ǎ. Este método es sutı́lmente más complicado cuando se usa el pivoteo. Si el número
de pivoteos es impar, el determinante es el producto negativo de los coeficientes de los elemen-
tos de la diagonal. Ahora deberı́a ser obvio que la regla de Cramer es computacionalmente
una manera ineficiente para resolver el conjunto de ecuaciones lineales.
int M, N
. . . // Se fijan valores a M y N
double * * A = new double * [M] ; // asignacion de un vector de punteros
for(int i=0;i<M;i++) {
A[i] = new double [N] ; // asignacion de memoria para cada fila
}
para un reserva dinámica (no olvide desasignar cada fila con un delete[]). Una asignación
estática es simple pero rı́gida, ya que las dimensiones son constantes fijas. Cuando el arreglo
estático es pasado a una función, esta función deberı́a declarar algún arreglo con el mismo
número de columnas de acuerdo al arreglo a ser indexado apropiadamente. Una asignación
dinámica es flexible pero es difı́cil de manejar, aunque los detalles pueden ser escondidos
dentro de funciones separadas. Accesar elementos fuera de los contornos del arreglo es un
error de programación común (bug) el cual es difı́cil de rastrear con arreglos dinámicos.
C++ nos permite corregir estas deficiencias creando nuestros propios tipos de variables.
Estas variables definidas por el usuario son llamadas clases de objetos. De aquı́ en adelante
usaremos la clase Matrix para declarar arreglos de una o dos dimensiones de números de punto
flotante. Esta clase está enteramente declarada dentro del archivo de cabecera Matrix.h e
implementada dentro de Matrix.cc. Algunos ejemplos de declaración de objetos de Matrix
son
Aunque esto se parece a una asignación de arreglo estático, note que las dimensiones no son
constantes fijas. Tanto vectores unidimensionales como matrices bidimensionales pueden ser
declarados; las anteriores son tratadas como matrices de una sola columna. Los valores en
estas variables pueden ser fijados por la declaración de asignamiento
El formato para el objeto Matrix es A(i, j) en vez de A[i][j], para distinguirlos de los arreglos
C++ convencionales.
Un objeto Matrix conoce sus dimensiones, y usted puede obtenerlas usando las funciones
miembros nRow() y nCol(). Por ejemplo:
10.1. SISTEMAS DE ECUACIONES LINEALES. 299
Cuadro 10.1: Bosquejo de la función ge, la cual resuelve un sistema de ecuaciones lineales
Ǎ ~x = ~b por eliminación Gaussiana. La función también devuelve el determinante de Ǎ.
Ǎ ~x = ~b , (10.20)
como
~x = Ǎ−1 ~b , (10.21)
donde Ǎ−1 es la matriz inversa de Ǎ. No nos deberı́a sorprender que el cálculo de la matriz
inversa esté relacionada al algoritmo para resolver un conjunto de ecuaciones lineales. Usual-
mente la inversa de una matriz es calculada por aplicaciones repetidas de eliminaciones de
Gauss (o una variante de descomposición llamada LU).
La inversa de una matriz está definida por la ecuación
Ǎ Ǎ−1 = Iˇ , (10.22)
podrı́amos escribir la matriz identidad como una vector fila de los vectores columna
El vector solución ~x1 es la primera columna de la inversa Ǎ−1 . Si procedemos de esta manera
con los otros êi calcularemos todas las columnas de Ǎ−1 . En otras palabras, nuestra ecuación
para la matriz inversa ǍǍ−1 = Iˇ es resuelta escribiéndola como
Ǎ ~x1 ~x2 . . . ~xN = ê1 ê2 . . . êN . (10.27)
10.2. MATRIZ INVERSA. 301
Entradas: Ǎ.
Cuadro 10.2: Bosquejo de la función inv, la cual calcula el inverso de una matriz y su
determinante.
(~x1 )1 (~x2 )1 . . . (~xN )1
x1 )2
(~ (~x2 )2 . . . (~xN )2
Ǎ−1
= ~x1 ~x2 . . . ~xN = .. .. .. (10.28)
. ...
. .
(~x1 )N (~x2 )N . . . (~xN )N
−1 1 a22 −a12
Ǎ = . (10.29)
a11 a22 − a12 a21 −a21 a11
Para matrices mayores las fórmulas rápidamente llegan a ser muy desordenadas.
302 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
Note que realmente no tenemos dos ecuaciones independientes, ya que la segunda es sólo el
doble de la primera. Estas ecuaciones no tienen una solución única. Si tratamos de hacer una
eliminación hacia adelante, obtenemos
x1 + x2 = 1
, (10.31)
0 = 0
no tiene inversa. Una matriz sin inversa se dice que es singular. Una matriz singular tam-
bién tiene un determinante nulo. Las matrices singulares no siempre son evidentes. ¿Podrı́a
adivinar si esta matriz
1 2 3
4 5 6
7 8 9
es singular?
Aquı́ está lo que pasa en Octave cuando tratamos de resolver (10.30)
donde ǫ es un número muy pequeño. A una matriz se le dice patológica cuando está muy
cerca de ser singular.
Si usted sospecha que está tratrando con ~
una matriz patológica cuando resuelve Ǎ~x = b,
entonces calcule el error absoluto, Ǎ~x − ~b / ~b para comprobar si ~x es una solución precisa.
10.2. MATRIZ INVERSA. 303
x2
x1
Figura 10.1: Sistema de bloques acoplados por resortes anclados entre paredes.
Por conveniencia, abreviaremos la ecuación de arriba como F~ = Ǩ~x − ~b. En forma matricial
las simetrı́as son claras, y vemos que no serı́a difı́cil extender el problema a un sistema más
grande de osciladores acoplados. En el estado de equilibrio estático las fuerzas netas son
iguales a cero, ası́ obtenemos el resto de las posiciones de las masas resolviendo Ǩ~x − ~b.
f(x)
x2 x* x
x1 x3
la función. Donde esta lı́nea tangente intersecta el eje x es nuestra siguiente estimación de la
raı́z. Efectivamente, estamos linealizando f (x) y resolviendo el problema lineal. Si la función
es bien comportada, pronto será aproximadamente lineal en alguna vecindad cerca de la raı́z
x∗ .
Unas pocas notas acerca del método de Newton: primero, cuando converge, encuentra una
raı́z muy rápidamente; pero, desafortunadamente, algunas veces diverge (e.g., f ′ (xn ) ≈ 0) y
falla. Para funciones bien comportadas, este método es garantizado que converger si estamos
suficientemente cerca de la raı́z. Por esta razón algunas veces está combinada con un algoritmo
más lento pero que asegura encontrar la raı́z, tal como la bisección. Segundo, si hay varias
raices, la raı́z a la cual el método converge depende de la estimación inicial (que podrı́a no
ser la raı́z deseada). Hay procedimientos (e.g., “deflation”) para encontrar múltiples raices
usando el método de Newton. Finalmente, el método es más lento cuando se encuentran
raices tangentes, tales como para f (x) = x2 .
∂fj (~x)
Dij (~x) = . (10.44)
∂xi
306 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
Ya que ~x ∗ es la raı́z, f~(~x ∗ ) = 0, como antes podrı́amos resolverla para δ~x. Despreciando el
término de error, podemos escribir la ecuación (10.43) como
o
δ~x = f~(~x1 ) Ď−1 (~x1 ) . (10.46)
Nuestra nueva estimación es
Este procedimiento puede ser iterado para mejorar nuestra estimación hasta que sea obtenida
la precisión deseada. Ya que Ď cambia en cada iteración, podrı́a ser desgastador calcular su
inversa. En cambio, usamos la eliminación Gaussiana sobre la ecuación (10.45) para resolver
para δ~x, y calcular la nueva estimación como ~x2 = ~x1 − δ~x.
Cuadro 10.3: Descripción del programa newtn, el cual encuentra una raı́z para un conjunto
de ecuaciones.
√ √
√ σ = 10 y b = 8/3, las tres raı́ces son [x, y, z] = [0, 0, 0], [6 2, 6 2, 27] y
Para√ r = 28,
[−6 2, −6 2, 27].
Un√ejemplo
√ de la salida de newtn está dada más abajo; note que el programa obtiene la
raı́z [6 2, 6 2, 27].
octave:1> newtn
newtn is the file: ~/cursos/MFM1.apuntes2/programas/newtn.m
10.3.4. Continuación.
La primera dificultad con el método de Newton es la necesidad de dar una buena estima-
ción inicial para la raı́z. A menudo nuestro problema es de la forma
f~(~x ∗ ; λ) = 0 , (10.50)
donde λ es algún parámetro en el problema. Supongamos que conocemos una raı́z ~x0∗ para el
valor λ0 pero necesitamos encontrar una raı́z para un valor diferente de λa . Intuitivamente,
si λa ≈ λ0 , entonces ~x0∗ deberı́a ser una buena estimación inicial para encontrar ~xa∗ usando
308 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
Cuadro 10.4: Descripción de la función fnewt, la cual es usada por newtn para encontrar los
estados estables de la ecuación de Lorenz.
el método de Newton. Pero si λa 6≈ λ0 , ¿hay alguna manera para hacer uso de nuestra raı́z
conocida?
La respuesta es sı́ y la técnica es conocida como continuación. La idea es acercarse poco
a poco a λa definiendo la siguiente secuencia de λ :
i
λi = λ0 + (λa − λ0 ) , (10.51)
N
para i = 1, . . . . . . ., N . Usando el método de Newton, resolvemos f (~x1∗ ; λ1 ) = 0 con ~x0∗
como la estimación inicial. Si N es suficientemente grande, entonces λ1 ≈ λ0 y el método
podrı́a converger rápidamente. Luego usamos ~x1∗ como una estimación inicial para resolver
f (~x2∗ ; λ2 ) = 0; la secuencia continúa hasta alcanzar nuestro valor deseado en λa . La técnica
de continuación tiene un beneficio adicional que podemos usar cuando estamos interesados
en conocer no sólo una raı́z simple, sino conocer como ~x ∗ varı́a con λ.
10.4. LISTADOS DEL PROGRAMA. 309
print A
print b
print c
def fnewt(x,a):
f = array( [a[1]*(x[1]-x[0]),
a[0]*x[0]-x[1]-x[0]*x[2],
x[0]*x[1]-a[2]*x[2]])
D = array( [[-a[1], a[1], 0],
[a[0]-x[2], -1, -x[0]],
[x[1], x[0],-a[2]]])
return (f,D)
def main():
x = input("Ingrese la estimacion inicial x_1, x_2, x_3 : ")
x0=array(x)
t = input("Ingrese los parametros r, sigma, b : ")
a=array(t)
nStep=10
for i in range(0,nStep):
f, D = fnewt(x,a)
310 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
dx = linalg.solve(D,f)
x=x-dx
# end main
if __name__==’__main__’:
main()
#include <iostream>
#include <string>
class Matrix {
private:
int nFilP ;
int nColP ;
int dimP ;
double * dataP ;
void copy(const Matrix &) ;
public:
Matrix() ;
Matrix( int, int = 1, double = 0.0e0) ;
Matrix( const Matrix &) ;
~Matrix() ;
Matrix & operator = (const Matrix &) ;
double & operator () (int, int =0) ;
int nRow () const;
int nCol () const ;
void set(double) ;
double maximoF(int) const ;
void pivot(int,int) ;
void multi(double, int, int ) ;
};
#endif
#include "Matrix.h"
#include <cassert>
#include <cstdlib>
// Constructor de copia
312 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
// Destructor
Matrix::~Matrix() {
delete [] dataP ; // Libera la memoria
}
// operador Parentesis
// Permite accesar los valores de una matriz via el par (i,j)
// Ejemplo a(1,1)=2*b(2,3)
// Si no se especifica la columna la toma igual a 0
double & Matrix::operator() (int indF, int indC) {
error(indF>-1 && indF< nFilP, "Indice de fila fuera de los limites") ;
error(indC>-1 && indC<nColP, "Indice de columna fuera de los limites" ) ;
return dataP[indC+nColP*indF] ;
}
if(indF1==indF2) return ;
double paso ;
int ind1, ind2 ;
for (int i = 0; i < nColP; i++ ) {
ind1 = nColP*indF1+i ;
ind2 = nColP*indF2+i ;
paso = dataP[ind1] ;
dataP[ind1] = dataP[ind2] ;
dataP[ind2] = paso ;
}
}
//
// IOSTREAM <<
//
ostream & operator << (ostream & os, Matrix mat ) {
for(int indF=0; indF < mat.nRow(); indF++) {
os << "| " ;
for(int indC=0; indC<mat.nCol(); indC++) {
os << mat(indF,indC) << " ";
}
os << "|"<<endl ;
}
return os ;
}
if(i) return ;
cout << s<<"\a" <<endl ;
exit(-1);
}
#include "NumMeth.h"
#include "Matrix.h"
}
for (int indF= nFil-1; indF >=0; indF--) {
double sum =0.0e0 ;
for (int i = indF+1; i < nCol; i++) sum += A(indF, i)*x(i) ;
x(indF) = b(indF)/A(indF, indF) - sum /A(indF, indF);
}
double determ = 1.0e0 ;
for (int i = 0; i < nCol; i++) determ *= A(i,i) ;
return determinante*determ ;
}
#include "NumMeth.h"
#include "Matrix.h"
nStep =10;
for iStep=1:nStep
[f D] = fnewt(x,a) ;
dx=D\f ;
x=x-dx;
end
fprintf(’Despues de %g iteraciones la raiz es \n’, nStep);
disp(x’);
f(1)=a(2)*(x(2)-x(1));
f(2)= a(1)*x(1)-x(2)-x(1)*x(3);
f(3)=x(1)*x(2)-a(3)*x(3) ;
D(1,1)=-a(2);
D(2,1)=a(1)-x(3);
D(3,1)=x(2);
D(1,2)=a(2);
D(2,2)=-1;
D(3,2)=x(1);
D(1,3)=0;
D(2,3)=-x(1);
D(3,3)=-a(3);
return
318 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
#include <iostream>
#include "NumMeth.h"
#include "Matrix.h"
void fnewt( Matrix & f, Matrix & D, Matrix xN, Matrix lambda )
{
double r= lambda(0) ;
double s= lambda(1) ;
double b=lambda(2) ;
double x=xN(0) ;
double y=xN(1) ;
double z=xN(2) ;
f(0) = s*(y-x) ;
f(1) = r*x-y-x*z ;
f(2) = x*y-b*z ;
D(0,0) = -s ;
D(1,0) = r-z ;
D(2,0) = y ;
D(0,1) = s ;
D(1,1)= -1.0e0 ;
D(2,1) = x ;
D(0,2)=0.0e0 ;
D(1,2)= -x ;
D(2,2) =-b ;
}
A.pivot(indC, indicePivot) ;
b.pivot(indC, indicePivot) ;
determinante *= -1.0e0 ;
}
double Ad= A(indC,indC) ;
for (int indF=indC+1; indF< nFil; indF++ ) {
double factor = - A(indF, indC)/Ad ;
A.multi(factor, indC, indF);
b.multi(factor, indC, indF);
}
}
for (int indF= nFil-1; indF >=0; indF--) {
double sum =0.0e0 ;
for (int i = indF+1; i < nCol; i++) sum += A(indF, i)*x(i) ;
x(indF) = b(indF)/A(indF, indF) - sum /A(indF, indF);
}
double determ = 1.0e0 ;
for (int i = 0; i < nCol; i++) determ *= A(i,i) ;
return determinante*determ ;
}
int main()
{
Matrix D(3,3) ;
Matrix f(3) ;
Matrix lambda(3), xN(3), xNp1(3), dx(3) ;
cout <<"Ingrese r , Sigma, b : " ;
cin >> lambda(0) >> lambda(1) >> lambda(2) ;
cout << "Ingrese estimacion inicial : " ;
cin>> xN(0) >> xN(1) >> xN(2) ;
int iteraciones ;
cout << "Ingrese numero de iteraciones : " ;
cin >> iteraciones ;
for (int itera = 0; itera < iteraciones; itera ++ ) {
f.set(0.0e0) ;
D.set(0.0e0) ;
dx.set(0.0e0) ;
fnewt(f, D, xN, lambda ) ;
ge(D, f, dx) ;
xNp1=xN-dx ;
xN=xNp1 ;
cout << xN(0)<< " " << xN(1) << " " << xN(2) << endl ;
}
cout << xN <<endl ;
return 0;
}
320 CAPÍTULO 10. RESOLVIENDO SISTEMAS DE ECUACIONES.
Capı́tulo 11
Análisis de datos.
versión 3.0, 02 de Diciembre 20031
A menudo se destaca que los sistemas fı́sicos simulados sobre un computador son similares
al trabajo experimental. La razón de esta analogı́a es hecha ya que la simulación computacio-
nal produce datos en muchas maneras similares a los experimentos de laboratorio. Sabemos
que en el trabajo experimental uno a menudo necesita analizar los resultados y esto es igual
en las simulaciones numéricas. Este capı́tulo cubre parcialmente algunos tópicos en el análisis
de datos.
321
322 CAPÍTULO 11. ANÁLISIS DE DATOS.
355
350
CO2 (ppm)
345
340
335
1982 1984 1986 1988 1990
Año
Figura 11.1: Dióxido de carbono (en partes por millón) medida en Mauna Loa, Hawai desde
1981 a 1990. Barra de error estimada es σ0 = 0.16 [p.p.m.] .
tración de CO2 por año?. Esta es la primera pregunta que motiva nuestro estudio de ajustes
de curvas.
Nuestro criterio de ajuste para la curva será que la suma de los cuadrados de los errores
sea un mı́nimo; esto es, necesitamos encontrar {aj } que minimice la función
N
X N
X
D({aj }) = ∆2i = [Y (xi ; {aj}) − yi ]2 . (11.2)
i=1 i=1
11.1. AJUSTE DE CURVAS. 323
σi Y(x;{aj })
yi
∆i
xi
Figura 11.2: Ajuste de datos a una curva.
Esta técnica nos dará el ajuste de los cuadrados mı́nimos; esta no es la única manera de
obtener un ajuste de la curva, pero es la más común. El método de los mı́nimos cuadrados
fue el primero usado por Gauss para determinar las órbitas de los cometas a partir de los
datos observados.
A menudo, nuestros datos tienen una barra de error estimada (o intervalo de seguridad),
el cual escribimos como yi ± σi . En este caso podrı́amos modificar nuestro criterio de ajuste
tanto como para dar un peso menor a los puntos con más error. En este espı́ritu definimos
N 2 N
2
X ∆i X [Y (xi ; {aj }) − yi ]2
χ ({aj }) = = . (11.3)
i=1
σi i=1
σi2
La función chi-cuadrado es la función de ajuste más comúnmente usada, porque si los errores
son distribuidos gaussianamente, podemos hacer sentencias estadı́sticas concernientes a la
virtud del ajuste.
Antes de continuar, debemos destacar que discutiremos brevemente la validación de la
curva de ajuste. Usted puede ajustar cualquier curva para cualquier conjunto de datos, pero
esto no significa que los resultados sean significativos. Para establecer un significado tene-
mos que preguntarnos lo siguiente: ¿cuál es la probabilidad de que los datos, dado el error
experimental asociado con cada uno de los puntos, sean descritos por la curva?. Desafortu-
nadamente, las hipótesis de prueba ocupan una porción significativa de un curso estadı́stico
y están fuera de los objetivos de estos apuntes.2
Este tipo de ajuste de curva es también conocido como regresión lineal. Deseamos determinar
a1 y a2 tal que
N
X 1
χ2 (a1 , a2 ) = 2
(a1 + a2 xi − yi )2 , (11.5)
i=1
σi
a1 S + a2 Σx − Σy = 0 (11.8)
a1 Σx + a2 Σx2 − Σxy = 0 , (11.9)
donde
N N N
X 1 X xi X yi
S≡ , Σx ≡ , Σy ≡ ,
σ2
i=1 i
σ2
i=1 i
σ2
i=1 i
N N N
(11.10)
X x2 i
X yi2 X xi yi
Σx2 ≡ , 2
Σy ≡ , Σxy ≡ .
i=1
σi2 σ2
i=1 i i=1
σi2
Puesto que las sumas pueden ser calculadas directamente de los datos, ellas son constantes
conocidas. De este modo tenemos un conjunto lineal de dos ecuaciones simultáneas en las
incógnitas a1 y a2 . Estas ecuaciones son fáciles de resolver:
Note que si σi es una constante, esto es, si el error es el mismo para todos los datos, los σ se
cancelan en las ecuaciones (11.11). En este caso, los parámetros, a1 y a2 , son independientes
de la barra de error. Es común que un conjunto de datos no incluya las barras de error
asociadas. Todavı́a podrı́amos usar las ecuaciones (11.11) para ajustar los datos si tenemos
que los σi son constantes (σi =1 siendo la elección más simple).
Lo siguiente es obtener una barra de error asociado, σaj , para el parámetro aj de la curva
de ajuste. Usando la ley de la propagación de errores,3
N 2
X ∂aj
σa2j = σi2 , (11.12)
i=1
∂yi
3
P. Bevington, Data Reduction an Error Analysis for the Physical Sciences 2d ed. (New York: McGraw-
Hill, 1992).
11.1. AJUSTE DE CURVAS. 325
Note que σaj es independiente de yi . Si las barras de error de los datos son constantes
(σi = σ0 ), el error en los parámetros es
s s
σ0 hx2 i σ0 1
σa1 =√ , σa2 =√ , (11.14)
N hx2 i − hxi2 N hx2 i − hxi2
donde
N N
1 X 2 1 X 2
hxi = xi , hx i = x . (11.15)
N i=1 N i=1 i
N
1 X
σ02 ≈s = 2
[yi − (a1 + a2 xi )]2 , (11.16)
N − 2 i=1
donde s es la desviación estándar de la muestra. Note que esta varianza muestral está nor-
malizada por N − 2, puesto que hemos extraı́do dos parámetros a1 y a2 , de los datos.
Muchos problemas de ajuste de curva no lineales, pueden ser transformados en problemas
lineales por un simple cambio de variable. Por ejemplo,
podrı́a ser reescrita como las ecuaciones (11.4) usando el cambio de variable
ln Z = Y , ln α = a1 , β = a2 . (11.18)
ln Z = Y , ln t = x , ln α = a1 , β = a2 . (11.20)
Estas transformaciones deberı́an ser familiares debido a que se usan para graficar datos en
escala semi logarı́tmica o escalas log-log.
326 CAPÍTULO 11. ANÁLISIS DE DATOS.
Para encontrar los óptimos parámetros procedemos como antes encontrando el mı́nimo de
χ2 ,
N
(M )2
∂χ2 ∂ X 1 X
= ak Yk (x) − yi =0, (11.22)
∂aj ∂aj i=1 σi2 k=1
o (M )
N
X 1 X
Yj (xi ) ak Yk (x) − yi = 0 , (11.23)
σ2
i=1 i k=1
por lo tanto
N X
M N
X Yj (xi )Yk (xi ) X Yj (xi )yi
ak = , (11.24)
i=1 k=1
σi2 i=1
σi2
para j = 1, . . . , M . Este conjunto de ecuaciones es conocida como las ecuaciones normales
del problema de los mı́nimos cuadrados.
Las ecuaciones normales son más fáciles de trabajar en forma matricial. Primero, defina-
mos la matriz de diseño Ǎ, como
Y1 (xi )/σ1 Y2 (xi )/σ1 . . .
Yj (xi )
Ǎ = Y1 (xi )/σ2 Y2 (xi )/σ2 . . . , Aij = . (11.25)
.. .. .. σi
. . .
Note que la matriz de diseño no depende de yi , los valores de datos, pero depende de xi , esto
es, sobre el diseño del experimento (donde las mediciones son tomadas). Usando la matriz de
diseño, podemos escribir (11.24) como
N X
M N
X X yi
Aij Aik ak = Aij , (11.26)
i=1 k=1 i=1
σi
o en forma matricial,
(ǍT Ǎ)~a = ǍT~b , (11.27)
donde el vector ~b está definido como bi = yi /σi y ǍT es la traspuesta de la matriz de diseño.
Esta ecuación es fácil de resolver para el parámetro vector ~a,
| yi − Y (xi ) | ≈ σi . (11.31)
En este capı́tulo veremos algunos métodos básicos para realizar cuadraturas (evaluar
integrales numéricamente).
12.1. Definiciones
Consideremos la integral
Z b
I= f (x) dx . (12.1)
a
Nuestra estrategia para estimar I es evaluar f (x) en unos cuantos puntos, y ajustar una curva
simple a través de estos puntos. Primero, subdividamos el intervalo [a, b] en N subintervalos.
Definamos los puntos xi como
La función f (x) sólo se evalúa en estos puntos, de modo que usamos la notación fi ≡ f (xi ),
ver figura 12.1.
f(x)
x0=a x1 x2 xi xN=b
Figura 12.1: Subintervalos.
329
330 CAPÍTULO 12. INTEGRACIÓN NUMÉRICA BÁSICA
fi
f(x)
fi+1
Ti
xi xi+1 x
1
Ti = (xi+1 − xi )(fi+1 + fi ) .
2
I ≃ IT = T0 + T1 + · · · + TN −1 .
Las expresiones son más simples si tomamos puntos equidistantes. En este caso el espa-
ciado entre cada par de puntos es
b−a
h= ,
N
de modo que xi = a + ih. El área de un trapecio es entonces
1
Ti = h(fi+1 + fi ) ,
2
1 1
IT (h) = hf0 + hf1 + hf2 + · · · + hfN −1 + hfN
2 2
N −1
1 X
= h(f0 + fN ) + h fi (12.2)
2 i=1
12.3. INTERPOLACIÓN CON DATOS EQUIDISTANTES. 331
f (N +1) (ξ) N +1
RN = h α(α − 1)(α − 2) · · · (α − N ) ,
(N + 1)!
usando que ∆f (xi ) = f (xi+1 ) − f (xi ) podemos dar una expresión para la integral
f (xi+1 ) + f (xi )
Ii (h) ≈ h. (12.5)
2
integrando obtenemos
usando la expresión para ∆f (xi ) = f (xi+1 )−f (xi ) y para ∆2 f (xi ) = f (xi+2 )−2f (xi+1 )+f (xi )
tenemos
h
Ii (h) ≈ (f (xi ) + 4f (xi+1 ) + f (xi+2 )) . (12.6)
3
Se puede continuar encontrando expresiones usando aproximaciones de orden mayor, sin
embargo, las fórmulas (12.5) y (12.6) son, sin duda, las más usadas.
A continuación, para evaluar la integral finita (12.1) debemos sumar las integrales par-
ciales para ambos casos. Partamos sumando (12.5)
b N −1 Z xi+1 N −1
f (xi+1 ) − f (xi )
Z X X
I= f (x) dx = f (x) dx ≈ h ≡ IT (h) ,
a i=0 xi i=0
2
b N −2 xi+2 N −2
h
Z X′ Z X′
I= f (x) dx = f (x) dx ≈ [f (xi ) + 4f (xi+1 ) + f (xi+2 )] ≡ Is (h) ,
a i=0 xi i=0
3
X′
donde significa que el ı́ndice i de la suma se incrementa en dos en cada oportunidad. Si
hacemos la suma tenemos
2h f (a) f (b)
Is (h) = + 2f (x1 ) + f (x2 ) + 2f (x3 ) + f (x4 ) + 2f (x5 ) + . . . + . (12.8)
3 2 2
La cual es conocida como la regla de Simpson. Notemos que se necesita un número impar de
puntos equiespaciados para aplicar esta regla.
Se puede estimar el error cometido en cada caso integrando el término (12.3) que corres-
ponde al error de truncamiento de la expansión. Evaluemos el error para el caso trapezoidal
en que cortamos la expansión a primer orden, por lo tanto el error corresponde
Z xi+1 2
d f (ξ) h2 (x − xi ) (x − xi − h)
ǫT (h) = dx ,
xi dx2 2! h h
1 d2 f (ξ) h
3
h3 1 d2 f (ξ) h3 d2 f (ξ)
h
Z
ǫT (h) = u(u − h) du = − = −
2 dx2 0 3 2 2 dx2 12 dx2
334 CAPÍTULO 12. INTEGRACIÓN NUMÉRICA BÁSICA
este es el error en cada integral entre xi y xi+1 , para obtener el error total en la integral entre
a y b tenemos que multiplicar por el número de intervalos (N ),
El segundo término del lado derecho da la contribución de los puntos interiores que se han
agregado cuando el tamaño del intervalo es reducido a la mitad.
Usando el método recursivo descrito, podemos agregar paneles hasta que la respuesta
parezca converger. Sin embargo, podemos mejorar notablemente este proceso usando un
método llamado integración de Romberg. Primero veamos la mecánica, y después explicaremos
por qué funciona. El método calcula los elementos de una matriz triangular:
R1,1 – – –
R2,1 R2,2 – –
R=R (12.13)
3,1 R3,2 R3,3 –
.. .. .. ..
. . . .
0.771743 0 0
0.825263 0.843103 0
0.838368 0.842736 0.842712
R3,3 da el resultado exacto con 4 decimales, usando los mismos 4 paneles que antes usamos
con la regla trapezoidal (y que ahora reobtenemos en el elemento R3,1 ).
Es útil que el programa entregue toda la tabla y no sólo el último término, para tener
una estimación del error. Como en otros contextos, usar una tabla demasiado grande puede
no ser conveniente pues errores de redondeo pueden comenzar a degradar la respuesta.
Para entender por qué el esquema de Romberg funciona, consideremos el error para la
regla trapezoidal, ET (hn ) = I − IT (hn ). Usando (12.9),
1 2 ′
ET (hn ) = − hn [f (b) − f ′ (a)] + O(h4n ) .
12
Como hn+1 = hn /2,
1 2 ′
ET (hn+1 ) = − h [f (b) − f ′ (a)] + O(h4n ) .
48 n
336 CAPÍTULO 12. INTEGRACIÓN NUMÉRICA BÁSICA
Notemos cómo el término h2n se cancela, dejando un error de truncamiento de orden h4n . La
siguiente columna (la tercera) de la tabla de Romberg cancela este término, y ası́ sucesiva-
mente.
aquı́ los xi son un conjunto de puntos elegidos de manera inteligente tal que disminuyan el
error y los wi son sus pesos respectivos, no necesariamente iguales unos con otros.
Figura 12.4: (a) Ilustración de la regla trapezoidal que une los dos puntos extremos por una
recta. (B) Estimación mejorada al elegir inteligentemente los puntos.
El nombre que reciben esta clase de técnicas es Cuadratura de Gauss, la más común de
ellas es la conocida como de Gauss-Legendre, y es útil para un intervalo finito, el cual es
mapeado mediante un cambio de variables al intervalo [−1, 1]. R ∞Existen otras cuadraturas
como la de Gauss-Laguerre óptima para integrales de la forma 0 e−x f (x) dx.
12.6. CUADRATURA DE GAUSS. 337
±xi wi ±xi wi
N=2 N=8
0.57735 02692 1.00000 00000 0.18343 46425 0.36268 37834
N=3 0.52553 24099 0.31370 66459
0.00000 00000 0.88888 88889 0.79666 64774 0.22238 10345
0.77459 66692 0.55555 55556 0.96028 98565 0.10122 85363
N=4 N=12
0.33998 10436 0.65214 51549 0.12523 34085 0.24914 70458
0.86113 63116 0.34785 48451 0.36783 14990 0.23349 25365
N=5 0.58731 79543 0.20316 74267
0.00000 00000 0.56888 88889 0.76990 26742 0.16007 83285
0.53846 93101 0.47862 86705 0.90411 72564 0.10693 93260
0.90617 98459 0.23692 68850 0.98156 06342 0.04717 53364
Para evaluar (12.1), debemos mapear el intervalo [a, b] en el intervalo [−1, 1] mediante el
cambio de variable x = 21 (b + a) + 21 (b − a)z, quedándonos
b 1
b−a
Z Z
f (x) dx = f (z) dz , (12.17)
a 2 −1
esta última integral puede ser evaluada usando diferentes conjuntos de puntos y pesos,
Z 1
f (z) dz ≈ w1 f (x1 ) + . . . + wN f (xn ) . (12.18)
−1
12.7. Bibliografı́a
Numerical Methods for Physics, second edition de Alejandro L. Garcia, editorial Pren-
tice Hall.
import sys
import math
from matplotlib import rc
from matplotlib.patches import Polygon
from pylab import *
import numpy
rc(’text’, usetex=True)
def integrando(x):
return (2.0/math.sqrt(math.pi))*math.exp(-x*x)
def main():
a=0.0
b=1.0
I=h*((integrando(a)+integrando(b))/2.0 +suma)
print "Valor aproximado: erf(1) : ",I
print "Valor exacto : ertf(1)= 0.842701 "
ax = subplot(111)
l_t=[]
l_fdt=[]
# Graficando
for t in arange(a,b,0.01):
l_t.append(t)
l_fdt.append(integrando(t))
xlabel(r’\textbf{\Large $t$}’)
ylabel(r’\textbf{\Large erf($t$)}’)
title(r’\textbf{\Huge Integrado con Trapecio}’)
grid(True)
show()
#
if __name__==’__main__’:
main()
def integrando(x):
return (2.0/sqrt(pi))*exp(-x*x)
a = 0.0
b = 1.0
suma = 0.0
R=[]
for i in range(N):
R.append( [0.0] * N )
h=b-a
np=1
12.8. LISTADOS DEL PROGRAMA. 341
for i in range(N):
if i==0:
R[0][0]=h*(integrando(a)+integrando(b))/2.0
else:
suma=0
j=1
for j in range(1,np,2):
x=a+j*h
suma+=integrando(x)
R[i][0]=R[i-1][0]/2.0 + h*suma
h/=2
np*=2
m=1.0
for j in range(1,N):
m*=4.0
for i in range(j,N):
R[i][j] = R[i][j-1] + (R[i][j-1]-R[i-1][j-1])/(m-1.0)
for i in range(N):
for j in range(N):
print R[i][j],
print
print
double integrando(double);
int main(){
double a=0, b=1,x;
int N;
double h;
double I, suma=0;
cout << "Regla trapezoidal para erf(1)" << endl;
342 CAPÍTULO 12. INTEGRACIÓN NUMÉRICA BÁSICA
h = (b-a)/(N-1);
for (int i=2;i<N;i++){
x = a+(i-1)*h;
suma+=integrando(x);
}
I = h*((integrando(a)+integrando(b))/2.0 + suma);
cout << "Valor aproximado: erf(1) ~= " << I << endl;
cout << "Valor exacto: erf(1) = 0.842701" << endl;
}
double integrando(double);
int main(){
double a=0, b=1,x;
int N,np;
double h;
double suma=0;
cout << "Integracion de Romberg para erf(1)" << endl;
cout << "Dimension de tabla: " ;
cin >> N;
double ** R = new double * [N];
for (int i=0;i<N;i++){
R[i] = new double[N];
}
np = 1; // Numero de paneles
for (int i=0;i<N;i++,h/=2,np*=2){
if (i==0){
R[0][0] = h*(integrando(a)+integrando(b))/2.0;
}
else {
suma = 0;
for (int j=1;j<=np-1;j+=2){
x = a+j*h;
suma += integrando(x);
}
R[i][0] = R[i-1][0]/2.0 + h*suma;
}
}
int m = 1;
for (int j=1;j<N;j++){
m *= 4;
for (int i=j;i<N;i++){
R[i][j] = R[i][j-1] + (R[i][j-1]-R[i-1][j-1])/(m-1);
}
}
Apéndices.
345
Apéndice A
Transferencia a diskettes.
La filosofı́a de diferentes unidades (A:, B:,. . . ) difiere de la estructura única del sistema
de archivos que existe en unix. Son varias las alternativas que existen para la transferencia
de información a diskette.
Una posibilidad es disponer de una máquina win9x con ftp instalado y acceso a red.
Empleando dicha aplicación se pueden intercambiar archivos entre un sistema y el otro.
347
348 APÉNDICE A. TRANSFERENCIA A DISKETTES.
Apéndice B
Son dos de los Shells interactivos más empleados. Una de las principales ventajas de tcsh
es que permite la edición de la lı́nea de comandos, y el acceso a la historia de órdenes usando
las teclas de cursores.1
349
350 APÉNDICE B. LAS SHELLS CSH Y TCSH.
!!:gs/str1/str2/
(global substitute) Repite la última orden reemplazando todas las ocurrencias de la
cadena str1 por la cadena str2.
!$
Es el último argumento de la orden anterior que se haya tecleado.
completado es total y el shell deja un espacio tras el nombre. En caso contrario hace sonar
un pitido. Pulsando Ctrl-D el shell muestra las formas existentes para completar.
prompt
Es una variable de cadena que contiene el texto que aparece al principio de la lı́nea de
comandos.
savehist
Permite definir el número de órdenes que se desea almacenar al abandonar el shell. Esto
permite recordar las órdenes que se ejecutaron en la sesión anterior.
352 APÉNDICE B. LAS SHELLS CSH Y TCSH.
Apéndice C
Los editores tipo emacs se parecen mucho y en su mayorı́a sus comandos son los mismos.
Para ejemplificar este tipo de editores nos centraremos en XEmacs, pero los comandos y
descripciones se aplican casi por igual a todos ellos. Los editores tipo emacs constan de tres
zonas:
La zona de edición: donde aparece el texto que está siendo editado y que ocupa la
mayor parte de la pantalla.
Emacs es un editor que permite la edición visual de un archivo (en contraste con el modo
de edición de vi). El texto se agrega o modifica en la zona de edición, usando las teclas
disponibles en el teclado.
Además, existen una serie de comandos disponibles para asistir en esta tarea.
La mayorı́a de los comandos de emacs se realizan empleando la tecla de CONTROL o la
tecla META1 . Emplearemos la nomenclatura: C-key para indicar que la tecla key debe de
ser pulsada junto con CONTROL y M-key para indicar que la tecla META debe de ser pulsada
junto a key. En este último caso NO es necesario pulsar simultáneamente las teclas ESC y
key, pudiendo pulsarse secuencialmente ESC y luego key, sin embargo, si se usa ALT como
META deben ser pulsadas simultáneamente. Observemos que en un teclado normal hay unos
50 caracteres (letras y números). Usando SHIFT se agregan otros 50. Ası́, usando CONTROL
y META, hay unos 50 · 4 = 200 comandos disponibles. Además, existen comandos especiales
llamados prefijos, que modifican el comando siguiente. Por ejemplo, C-x es un prefijo, y si C-s
es un comando (de búsqueda en este caso), C-x C-s es otro (grabar archivo). Ası́, a través
de un prefijo, se duplican el número de comandos disponibles sólo con el teclado, hasta llegar
a unos 200 · 2 = 400 comandos en total.
Aparte de estos comandos accesibles por teclas, algunos de los cuales comentaremos a
continuación, existen comandos que es posible ejecutar por nombre, haciendo ası́ el número
de comandos disponibles virtualmente infinito.
1
Dado que la mayorı́a de los teclados actuales no poseen la tecla META se emplea ya sea ESC o ALT.
353
354 APÉNDICE C. EDITORES TIPO EMACS.
Abortar y deshacer
En cualquier momento, es posible abortar la operación en curso, o deshacer un comando
indeseado:
C-g abortar
C-x u deshacer
Archivos
Ventanas
Emacs permite dividir la pantalla en varias ventanas. En cada ventana se puede editar
texto e ingresar comandos independientemente. Esto es útil en dos situaciones: a) si necesi-
tamos editar un solo archivo, pero necesitamos ver su contenido en dos posiciones distintas
(por ejemplo, el comienzo y el final de archivos muy grandes); y b) si necesitamos editar o ver
varios archivos simultáneamente. Naturalmente, aunque son independientes, sólo es posible
editar un archivo a la vez. A la ventana en la cual se encuentra el cursor en un momento
dado le llamamos la “ventana actual”.
El cambio del cursor a una ventana cualquiera se puede hacer también rápidamente a
través del mouse.
Comandos de movimiento
Algunos de estos comandos tienen dos teclas asociadas, como se indica a continuación.
355
Mayúsculas y minúsculas
Por ejemplo, veamos el efecto de cada uno de estos comandos sobre la palabra EmAcS, si
el cursor está sobre la letra E (¡el efecto es distinto si está sobre cualquier otra letra!):
Transposición
Los siguientes comandos toman como referencia la posición actual del cursor. Por ejemplo,
C-t intercambia el carácter justo antes del cursor con el carácter justo después.
Búsqueda y reemplazo
El concepto de kill buffer es mucho más poderoso que lo explicado recién. En realidad,
muchos comandos, no sólo M-w y C-w, copian texto en un kill buffer. En general, cualquier
comando que borre más de un carácter a la vez, lo hace. Por ejemplo, C-k borra una lı́nea.
Lo que hace no es sólo borrarla, sino además copiarla en un kill buffer. Lo mismo ocurre
con los comandos que borran palabras completas (M-d, M-Backspace), y muchos otros. Lo
357
interesante es que C-y funciona también en todos esos casos: C-y lo único que hace es tomar
el último texto colocado en un kill buffer (resultado de la última operación que borró más de
un carácter a la vez), y lo coloca en el archivo. Por lo tanto, no sólo podemos copiar o mover
“regiones”, sino también palabras o lı́neas. Más aún, el kill buffer no es borrado con el C-y,
ası́ que ese mismo texto puede ser duplicado muchas veces. Continuará disponible con C-y
mientras no se ponga un nuevo texto en el kill buffer.
Además, emacs dispone no de uno sino de muchos kill buffers. Esto permite recuperar
texto borrado hace mucho rato. En efecto, cada vez que se borra más de un carácter de una
vez, se una un nuevo kill buffer. Por ejemplo, consideremos el texto:
Si en este párrafo borramos la primera lı́nea (con C-k), después borramos la primera
palabra de la segunda (con M-d, por ejemplo), y luego la segunda palabra de la última,
entonces habrá tres kill buffers ocupados:
Al colocar el cursor después del punto final, C-y toma el contenido del último kill buffer
y lo coloca en el texto:
segunda linea,
y la tercera. finalmente
segunda linea,
y la tercera. la
buffer 1 : finalmente
buffer 2 : La primera linea del texto,
buffer 3 : la
Sucesivas aplicaciones de M-y después de un C-y rotan sobre todos los kill buffers (que
pueden ser muchos). El editor, ası́, conserva un conjunto de las últimas zonas borradas durante
la edición, pudiendo recuperarse una antigua a pesar de haber seleccionado una nueva zona,
o borrado una nueva palabra o lı́nea. Toda la información en los kill buffers se pierde al salir
de emacs (C-c).
Resumimos entonces los comandos para manejo de los kill buffers:
358 APÉNDICE C. EDITORES TIPO EMACS.
Definición de macros
La clave de la configurabilidad de emacs está en la posibilidad de definir nuevos comandos
que modifiquen su comportamiento o agreguen nuevas funciones de acuerdo a nuestras nece-
sidades. Un modo de hacerlo es a través del archivo de configuración $HOME/.emacs, para lo
cual se sugiere leer la documentación disponible en la distribución instalada. Sin embargo, si
sólo necesitamos un nuevo comando en la sesión de trabajo actual, un modo más simple es
definir una macro, un conjunto de órdenes que son ejecutados como un solo comando. Los
comandos relevantes son:
Todas las sucesiones de teclas y comandos dados entre C-x ( y C-x ) son recordados por
emacs, y después pueden ser ejecutados de una vez con C-x e.
Como ejemplo, consideremos el siguiente texto, con los cinco primeros lugares del ránking
ATP (sistema de entrada) al 26 de marzo de 2002:
Supongamos que queremos: (a) poner los nombres y apellidos con mayúscula (como de-
berı́a ser); (b) poner las siglas de paı́ses sólo en mayúsculas.
Para definir una macro, colocamos el cursor al comienzo de la primera lı́nea, en el 1,
y damos C-x (. Ahora realizamos todos los comandos necesarios para hacer las tres tareas
solicitadas para el primer jugador solamente: M-f (avanza una palabra, hasta el espacio antes
de hewitt; M-c M-c (cambia a Hewitt, Lleyton); M-u (cambia a AUS); Home (vuelve el
cursor al comienzo de la lı́nea); ↓ (coloca el cursor al comienzo de la lı́nea siguiente, en el 2).
Los dos últimos pasos son importantes, porque dejan el cursor en la posición correcta para
ejecutar el comando nuevamente. Ahora terminamos la definición con C-x ). Listo. Si ahora
ejecutamos la macro, con C-x e, veremos que la segunda lı́nea queda modificada igual que
la primera, y ası́ podemos continuar hasta el final:
M-(number)
Por ejemplo, si deseamos escribir 20 letras e, basta teclear M-20 e. Esto es particularmente
útil con las macros definidos por el usuario. En el ejemplo anterior, con el ránking ATP,
después de definir la macro quedamos en la lı́nea 2, y en vez de ejecutar C-x e 4 veces,
podemos teclear M-4 C-x e, con el mismo resultado, pero en mucho menos tiempo.
Para terminar la discusión de este editor, diremos que es conveniente conocer las secuencias
de control básico de emacs:
C-a, C-e, C-k, C-y, C-w, C-t, C-d, etc.,
porque funcionan para editar la lı́nea de comandos en el shell, como también en muchos
programas de texto y en ventanas de diálogo de las aplicaciones X Windows. A su vez, los
editores jed, xjed, jove también usan por defecto estas combinaciones.
360 APÉNDICE C. EDITORES TIPO EMACS.
Apéndice D
D.1. Introducción
Octave es un poderoso software para análisis numérico y visualización. Muchos de sus
comandos son compatibles con Matlab. En estos apuntes revisaremos algunas caracterı́sticas
de estos programas. En realidad, el autor de este capı́tulo ha sido usuario durante algunos años
de Matlab, de modo que estos apuntes se han basado en ese conocimiento, considerando los
comandos que le son más familiares de Matlab. En la mayorı́a de las ocasiones he verificado
que los comandos descritos son también compatibles con Octave, pero ocasionalmente se
puede haber omitido algo. . . .
Matlab es una abreviación de Matrix Laboratory. Los elementos básicos con los que se
trabaja con matrices. Todos los otros tipos de variables (vectores, texto, polinomios, etc.),
son tratados como matrices. Esto permite escribir rutinas optimizadas para el trabajo con
matrices, y extender su uso a todos los otros tipos de variables fácilmente.
a=3;
a
361
362 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
a=5
a
a=3
a=5
a=5
– Un número sin punto decimal es tratado como un entero exacto. Un número con punto
decimal es tratado como un número en doble precisión. Esto puede no ser evidente
en el output. Por default, 8.4 es escrito en pantalla como 8.4000. Tras la instrucción
format long, sin embargo, es escrito como 8.40000000000000. Para volver al formato
original, basta la instrucción format.
D.3.2. Matrices
Este tipo de variable corresponde a escalares, vectores fila o columna, y matrices conven-
cionales.
Construcción
Las instrucciones:
a = [1 2 ; 3 4]
D.3. TIPOS DE VARIABLES 363
ó
a = [1, 2; 3, 4]
1 2
definen la matriz . Las comas (opcionales) separan elementos de columnas distintas,
3 4
y los punto y coma separan elementos de filas distintas. El vector fila (1 2) es
b = [1 2]
1
y el vector columna es
2
c = [1;2]
Un número se define simplemente como d = [3] ó d = 3.
Nota importante: Muchas funciones de Octave/Matlab en las páginas siguientes acep-
tan indistintamente escalares, vectores filas, vectores columnas, o matrices, y su output es
un escalar, vector o matriz, respectivamente. Por ejemplo, log(a) es un vector fila si a es un
vector fila (donde cada elemento es el logaritmo natural del elemento correspondiente en a),
y un vector columna si a es un vector columna. En el resto de este manual no se advertira
este hecho, y se pondrán ejemplos con un solo tipo de variable, en el entendido que el lector
está conciente de esta nota.
Concatenación de matrices
1 2 7
Si a = ,b= 5 6 ,c= , entonces
3 4 8
d = [a c]
1 2 7
d=
3 4 8
d = [a; b]
1 2
d = 3 4
5 6
364 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
d = [a [0; 0] c]
1 2 0 7
d=
3 4 0 8
D.3.3. Strings
Las cadenas de texto son casos particulares de vectores fila, y se construyen y modifican
de modo idéntico.
Construcción
Las instrucciones
r = ’b’
t(9) = ’s’
Concatenación
t = ’un buen texto’;
t1 = [t ’ es necesario’]
D.3.4. Estructuras
Las estructuras son extensiones de los tipos de variables anteriores. Una estructura consta
de distintos campos, y cada campo puede ser una matriz (es decir, un escalar, un vector o
una matriz), o una string.
Construcción
Las lı́neas
persona.nombre = ’Eduardo’
persona.edad = 30
persona.matriz_favorita = [2 8;10 15];
D.4. OPERADORES BÁSICOS 365
definen una estructura con tres campos, uno de los cuales es un string, otro un escalar, y otro
una matriz:
persona =
{
nombre = ’Eduardo’;
edad = 30;
matriz_favorita = [2 8; 10 15];
}
s = ’Eduardo’
persona.nombre = ’Claudio’
persona.matriz_favorita(2,1) = 8
persona =
{
nombre = ’Claudio’;
edad = 30;
matriz_favorita = [2 8; 8 15];
}
entonces
c = a.*b
5 12
c=
21 32
c = a./b
0.2 0.3333
c=
0.42857 0.5
366 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
a = [1 2 3];
b = [4 2 1];
c = (a<3);
d = (a>=b);
c = (1, 1, 0)
d = (0, 1, 1)
D.4.4. El operador :
Es uno de los operadores fundamentales. Permite crear vectores y extraer submatrices.
D.6. Comandos
En esta sección revisaremos diversos comandos de uso frecuente en Octave/Matlab. Esta
lista no pretende ser exhaustiva (se puede consultar la documentación para mayores detalles),
y está determinada por mi propio uso del programa y lo que yo considero más frecuente
debido a esa experiencia. Insistimos en que ni la lista de comandos es exhaustiva, ni la lista
de ejemplos o usos de cada comando lo es. Esto pretende ser sólo una descripción de los
aspectos que me parecen más importantes o de uso más recurrente.
for
n=3; a=[1 4 9]
for i=1:n
a(i)=i^2;
end
Para Octave el vector resultante es columna en vez de fila.
Observar el uso del operador : para generar el vector [1 2 3]. Cualquier vector se puede
utilizar en su lugar: for i=[2 8 9 -3], for i=10:-2:1 (equivalente a [10 8 6 4 2]), etc.
son válidas. El ciclo for anterior se podrı́a haber escrito en una sola lı́nea ası́:
for i=1:n, a(i)=i^2; end
D.6. COMANDOS 369
b) if a==[3 8 9 10]
b = a(1:3);
end
c) if a>3
clear a;
elseif a<0
save a;
else
disp(’Valor de a no considerado’);
end
while s
comandos
end
Mientras s es 1, se ejecutan los comandos entre while y end. s puede ser cualquier
expresión que dé por resultado 1 (verdadero) ó 0 (falso).
break
Interrumpe ejecución de ciclos for o while. En loops anidados, break sale del más interno
solamente.
Funciones lógicas
Además de expresiones construidas con los operadores relacionales ==, <=, etc., y los
operadores lógicos &, | y ~, los comandos de control de flujo anteriores admiten cualquier
función cuyo resultado sea 1 (verdadero) ó 0 (falso). Particularmente útiles son funciones
como las siguientes:
all(a) 1 si todos los elementos de a son no nulos, y 0 si alguno es
cero
any(a) 1 si alguno de los elementos de a es no nulo
isempty(a) 1 si a es matriz vacı́a (a=[])
370 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
Otras funciones entregan matrices de la misma dimensión que el argumento, con unos o
ceros en los lugares en que la condición es verdadera o falsa, respectivamente:
finite(a) 1 donde a es finito (no inf ni NaN)
isinf(a) 1 donde a es infinito
isnan(a) 1 donde a es un NaN
Por ejemplo, luego de ejecutar las lı́neas
x = [-2 -1 0 1 2];
y = 1./x;
a = finite(y);
b = isinf(y);
c = isnan(y);
se tiene
a = [1 1 0 1 1]
b = [0 0 1 0 0]
c = [0 0 0 0 0]
Otra función lógica muy importante es find:
find(a) Encuentra los ı́ndices de los elementos no nulos de a.
Por ejemplo, si ejecutamos las lı́neas
x=[11 0 33 0 55];
z1=find(x);
z2=find(x>0 & x<40);
obtendremos
z1 = [1 3 5]
z2 = [1 3]
find también puede dar dos resultados de salida simultáneamente (más sobre esta posi-
bilidad en la sección D.6.2), en cuyo caso el resultado son los pares de ı́ndices (ı́ndices de fila
y columna) para cada elemento no nulo de una matriz
y=[1 2 3 4 5;6 7 8 9 10];
[z3,z4]=find(y>8);
da como resultado
z3 = [2;2];
z4 = [4;5];
z3 contiene los ı́ndice de fila y z4 los de columna para los elementos no nulos de la matriz
2 4
y>8. Esto permite construir, por ejemplo, la matriz z5=[z3 z4] = , en la cual cada
2 5
fila es la posición de y tal que la condición y>8 es verdadera (en este caso, es verdadera para
los elementos y(2,4) e y(2,5)).
D.6. COMANDOS 371
Por ejemplo:
x = [1 2 3];
y = [4 5];
[X,Y] = meshgrid(x,y);
da
1 2 3 4 4 4
X= , Y = .
1 2 3 5 5 5
Notemos que al tomar sucesivamente los distintos pares ordenados (X(1,1),Y(1,1)), (X(1,2),Y(1,2)),
(X(1,3),Y(1,3)), etc., se obtienen todos los pares ordenados posibles tales que el primer ele-
mento está en x y el segundo está en y. Esta caracterı́stica hace particularmente útil el
comando meshgrid en el contexto de gráficos de funciones de dos variables (ver secciones
D.6.7, D.6.7).
Constantes especiales
Octave/Matlab proporciona algunos números especiales, algunos de los cuales ya mencio-
namos en la sección D.3.1.
√
i, j Unidad imaginaria ( −1 )
inf Infinito
NaN Not-A-Number
pi El número π (= 3.1415926535897 . . .)
Funciones elementales
Desde luego, Octave/Matlab proporciona todas las funciones matemáticas básicas. Por
ejemplo:
D.6. COMANDOS 373
Funciones especiales
Además, Octave/Matlab proporciona diversas funciones matemáticas especiales. Algunos
ejemplos:
bessel Función de Bessel
besselh Función de Hankel
beta Función beta
ellipke Función elı́ptica
erf Función error
gamma Función gamma
Ası́, por ejemplo, bessel(alpha,X) evalúa la función de Bessel de orden alpha, Jα (x),
para cada elemento de la matriz X.
D.6.4. Polinomios
Octave/Matlab representa los polinomios como vectores fila. El polinomio
p = cn xn + · · · + c1 x + c0
Podemos efectuar una serie de operaciones con los polinomios ası́ representados.
poly(x) Polinomio cuyas raı́ces son los elementos de x.
polyval(p,x) Evalúa el polinomio p en x (en los elementos de x si éste es
un vector)
roots(p) Raı́ces del polinomio p
d = eig(a)
[V,D] = eig(a)
La primera forma deja en d un vector con los autovalores de a. La segunda, deja en D una
matriz diagonal con los autovalores, y en V una matiz cuyas columnas son los autovalores, de
modo que A*V = V*D. Por ejemplo, si a =[1 2; 3 4], entonces
5.37228
d=
−0.37228
y
5.37228 . . . 0 0.41597 . . . −0.82456 . . .
D= , V = .
0 −0.37228 . . . 0.90938 . . . 0.56577 . . .
La primera columna de V es el autovector de a asociado al primer autovalor, 5.37228 . . ..
a) Máximos y mı́nimos
Si a es un vector, max(a) es el mayor elemento de a. Si es una matriz, max(a) es un vector
fila, que contiene el máximo elemento para cada columna.
D.6. COMANDOS 375
a = [1 6 7; 2 8 3; 0 4 1]
b = max(a) b = (2 8 7)
Se sigue que el mayor elemento de la matriz se obtiene con max(max(a)).
min opera de modo análogo, entregando los mı́nimos.
b) Estadı́stica básica
Las siguientes funciones, como min y max, operan sobre vectores del modo usual, y sobre
matrices entregando vectores fila, con cada elemento representando a cada columna de la
matriz.
c) Orden
sort(a) ordena los elementos de a en orden ascendente si a es un vector. Si es una matriz,
ordena cada columna.
1 −3 0
b = sort([1 3 9; 8 2 1; 4 -3 0]); b = 4 2 1
8 3 9
d) Transformada de Fourier
Por último, es posible efectuar transformadas de Fourier directas e inversas, en una o dos
dimensiones. Por ejemplo, fft y ifft dan la transformada de Fourier y la transformada
inversa de x, usando un algoritmo de fast Fourier transform (FFT). Especı́ficamente, si
X=fft(x) y x=ifft(X), y los vectores son de largo N:
N
(j−1)(k−1)
X
X(k) = x(j)ωN ,
j=1
N
1 X −(j−1)(k−1)
x(j) = X(k)ωN ,
N k=1
donde ωN = e−2πi/N .
D.6.7. Gráficos
Una de las caracterı́sticas más importantes de Matlab son sus amplias posibilidades gráfi-
cas. Algunas de esas caracterı́sticas se encuentran también en Octave. En esta sección revi-
saremos el caso de gráficos en dos dimensiones, en la siguiente el caso de tres dimensiones, y
luego examinaremos algunas posibilidades de manipulación de gráficos.
376 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
Gráficos bidimensionales
Para graficar en dos dimensiones se usa el comando plot. plot(x,y) grafica la ordenada
y versus la abscisa x. plot(y) asume abscisa [1,2,...n], donde n es la longitud de y.
Ejemplo: Si x=[2 8 9], y=[6 3 2], entonces
plot(x,y)
Por default, Octave utiliza gnuplot para los gráficos. Por default, los puntos se conectan
con una lı́nea roja en este caso. El aspecto de la lı́nea o de los puntos puede ser modificado.
Por ejemplo, plot(x,y,’ob’) hace que los puntos sean indicados con cı́rculos (’o’) azules
(’b’, blue). Otros modificadores posibles son:
- lı́nea (default) r red
. puntos g green
@ otro estilo de puntos b blue
+ signo más m magenta
* asteriscos c cyan
o cı́rculos w white
x cruces
Dos o más gráficos se pueden incluir en el mismo output agregando más argumentos a
plot. Por ejemplo: plot(x1,y1,’x’,x2,y2,’og’,x3,y3,’.c’).
Los mapas de contorno son un tipo especial de gráfico. Dada una función z = f (x, y),
nos interesa graficar los puntos (x, y) tales que f = c, con c alguna constante. Por ejemplo,
consideremos
2 2
z = xe−x −y , x ∈ [−2, 2], y ∈ [−2, 3] .
Para obtener el gráfico de contorno de z, mostrando los niveles z = −.3, z = −.1, z = 0,
z = .1 y z = .3, podemos usar las instrucciones:
x = -2:.2:2;
y = -2:.2:3;
[X,Y] = meshgrid(x,y);
D.6. COMANDOS 377
Z = X.*exp(-X.^2-Y.^2);
contour(Z.’,[-.3 -.1 0 .1 .3],x,y); # Octave por default (gnuplot)
contour(x, y, Z.’,[-.3 -.1 0 .1 .3]); # Octave con plplot y Matlab
Las dos primeras lı́neas definen los puntos sobre los ejes x e y en los cuales la función
será evaluada. En este caso, escojimos una grilla en que puntos contiguos están separados por
.2. Para un mapa de contorno, necesitamos evaluar la función en todos los pares ordenados
(x, y) posibles al escoger x en x e y en y. Para eso usamos meshgrid (introducida sin mayores
explicaciones en la sección D.6.3). Luego evaluamos la función [Z es una matriz, donde cada
elemento es el valor de la función en un par ordenado (x, y)], y finalmente construimos el
mapa de contorno para los niveles deseados.
Gráficos tridimensionales
También es posible realizar gráficos tridimensionales. Por ejemplo, la misma doble gaus-
siana de la sección anterior se puede graficar en tres dimensiones, para mostrarla como una
superficie z(x, y). Basta reemplazar la última instrucción, que llama a contour, por la si-
guiente:
mesh(X,Y,Z)
Observar que, mientras contour acepta argumentos dos de los cuales son vectores, y el
tercero una matriz, en mesh los tres argumentos son matrices de la misma dimensión (usamos
X, Y, en vez de x, y).
Nota importante: Otro modo de hacer gráficos bi y tridimensionales es con gplot
y gsplot (instrucciones asociadas realmente no a Octave sino a gnuplot, y por tanto no
equivalentes a instrucciones en Matlab). Se recomienda consultar la documentación de Octave
para los detalles.
378 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
Manipulación de gráficos
Los siguientes comandos están disponibles para modificar gráficos construidos con Octa-
ve/Matlab:
a) Ejes
axis([x1 y1 x2 y2]) Cambia el eje x al rango (x1, x2), y el eje y al rango (y1, y2).
b) Tı́tulos
c) Grillas
x = linspace(1,10,30);
y = x.^3;
plot(x,y);
gset term postscript color
gset output ‘‘xcubo.ps’’
replot
gset term x11
Las tres primeras lı́neas son los comandos de Octave/Matlab convencionales para graficar.
Luego se resetea el terminal a un terminal postscript en colores (gset term postscript si
no deseamos los colores), para que el output sucesivo vaya en formato postscript y no a
la pantalla. La siguiente lı́nea indica que la salida es al archivo xcubo.ps. Finalmente, se
redibuja el gráfico (con lo cual el archivo xcubo.ps es realmente generado), y se vuelve al
terminal XWindows para continuar trabajando con salida a la pantalla.
Debemos hacer notar que no necesariamente el gráfico exportado a Postscript se verá igual
al resultado que gnuplot muestra en pantalla. Durante la preparación de este manual, nos
dimos cuenta de ello al intentar cambiar los estilos de lı́nea de plot. Queda entonces advertido
el lector.
D.6.8. Strings
Para manipular una cadena de texto, disponemos de los siguientes comandos:
lower Convierte a minúsculas
upper Convierte a mayúsculas
Ası́, lower(’Texto’) da ’texto’, y upper(’Texto’) da ’TEXTO’.
Para comparar dos matrices entre sı́, usamos strcmp:
strcmp(a,b) 1 si a y b son idénticas, 0 en caso contrario
Podemos convertir números enteros o reales en strings, y strings en números, con los
comandos:
int2str Convierte entero en string
num2str Convierte número en string
str2num Convierte string en número
380 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
Por ejemplo, podemos usar esto para construir un tı́tulo para un gráfico:
archivo = fopen(’archivo.dat’,’w’);
Esto abre el archivo archivo.dat para escritura (’w’), y le asigna a este archivo un número
que queda alojado en la variable archivo para futura referencia.
Los modos de apertura posibles son:
r Abre para lectura
w Abre para escritura, descartando contenidos anteriores si los
hay
a Abre o crea archivo para escritura, agregando datos al final
del archivo si ya existe
r+ Abre para lectura y escritura
w+ Crea archivo para lectura y escritura
a+ Abre o crea archivo para lectura y escritura, agregando datos
al final del archivo si ya existe
En un archivo se puede escribir en modo binario:
fread Lee datos binarios
fwrite Escribe datos binarios
o en modo texto
fgetl Lee una lı́nea del archivo, descarta cambio de lı́nea
fgets Lee una lı́nea del archivo, preserva cambio de lı́nea
fprintf Escribe datos siguiendo un formato
fscanf Lee datos siguiendo un formato
Referimos al lector a la ayuda que proporciona Octave/Matlab para interiorizarse del
uso de estos comandos. Sólo expondremos el uso de fprintf, pues el formato es algo que
habitualmente se necesita tanto para escribir en archivos como en pantalla, y fprintf se
puede usar en ambos casos.
La instrucción
fprintf(archivo,’formato’,A,B,...)
D.6. COMANDOS 381
imprime en el archivo asociado con el identificador archivo (asociado al mismo al usar fopen,
ver más arriba), las variables A, B, etc., usando el formato ’formato’. archivo=1 corresponde
a la pantalla; si archivo se omite, el default es 1, es decir, fprintf imprime en pantalla si
archivo=1 o si se omite el primer argumento.
’formato’ es una string, que puede contener caracters normales, caracteres de escape o
especificadores de conversión. Los caracteres de escape son:
\n New line
\t Horizontal tab
\b Backspace
\r Carriage return
\f Form feed
\\ Backslash
\’ Single quote
Por ejemplo, la lı́nea
da como resultado
Una tabulacion y un ’’original’’ cambio de linea
aqui
Es importante notar que por default, el cambio de lı́nea al final de un fprintf no existe,
de modo que, si queremos evitar salidas a pantalla o a archivo poco estéticas, siempre hay
que terminar con un \n.
Los especificadores de conversión permiten dar formato adecuado a las variables numéricas
A, B, etc. que se desean imprimir. Constan del caracter %, seguido de indicadores de ancho
(opcionales), y caracteres de conversión. Por ejemplo, si deseamos imprimir el número π con
5 decimales, la instrucción es:
fprintf(’Numero pi = %.5f\n’,pi)
El resultado:
Numero pi = 3.14159
Los caracteres de conversión pueden ser
%e Notación exponencial (Ej.: 2.4e-5)
%f Notación con punto decimal fijo (Ej.: 0.000024)
%g %e o %f, dependiendo de cuál sea más corto (los ceros no
significativos no se imprimen)
Entre % y e, f, o g según corresponda, se pueden agregar uno o más de los siguientes
caracteres, en este orden:
En el siguiente ejemplo veremos distintos casos posibles. El output fue generado con las
siguientes instrucciones, contenidas en un script:
a = .04395;
fprintf(’123456789012345\n’);
fprintf(’a = %.3f.\n’,a);
fprintf(’a = %10.2f.\n’,a);
fprintf(’a = %-10.2f.\n’,a);
fprintf(’a = %4f.\n’,a);
fprintf(’a = %5.3e.\n’,a);
fprintf(’a = %f.\n’,a);
fprintf(’a = %e.\n’,a);
fprintf(’a = %g.\n’,a);
El resultado:
12345678901234567890
a = 0.044.
a = 0.04.
a = 0.04 .
a = 0.043950.
a = 4.395e-02.
a = 0.043950.
a = 4.395000e-02.
a = 0.04395.
En la primera lı́nea, se imprimen tres decimales. En la segunda, dos, pero el ancho mı́nimo
es 10 caracteres, de modo que se alı́nea a la derecha el output y se completa con blancos. En
la tercera lı́nea es lo mismo, pero alineado a la izquierda. En la cuarta lı́nea se ha especificado
un ancho mı́nimo de 4 caracteres; como el tamaño del número es mayor, esto no tiene efecto
y se imprime el número completo. En la quinta lı́nea se usa notación exponencial, con tres
decimal (nuevamente, el ancho mı́nimo especificado, 5, es menor que el ancho del output,
luego no tiene efecto). Las últimas tres lı́neas comparan el output de %f, %e y %g, sin otras
especificaciones.
Si se desean imprimir más de un número, basta agregar las conversiones adecuadas y los
argumentos en fprintf. Ası́, la lı́nea
da por resultado
x = 1:5;
y1 = exp(x);
y2 = log(x);
a = [x; y1; y2];
fprintf = (’%g %8g %8.3f\n’,a);
da el output
1 2.71828 0.000
2 7.38906 0.693
3 20.0855 1.099
4 54.5982 1.386
5 148.413 1.609
384 APÉNDICE D. UNA BREVE INTRODUCCIÓN A OCTAVE/MATLAB
Apéndice E
Asignación dinámica.
int main()
{
cout<<"Ingrese la dimension deseada :" ;
int dim ;
cin >> dim ;
double * matriz = new double[dim] ; // Reserva la memoria
for(int i=0; i < dim; i++) {
cout << "Ingrese elemento "<< i <<" : ";
cin >> matriz[i] ;
}
385
386 APÉNDICE E. ASIGNACIÓN DINÁMICA.
int main()
{
int width;
int height;
width = 200;
height = 400;
Primero se crea, con new, un puntero (matriz) de dimensión 200, que representará las
filas. Cada uno de sus elementos (matriz[i]), a su vez, será un nuevo puntero, de dimensión
400, representando cada columna. Por tanto, matriz debe ser un puntero a puntero (de
dobles, en este caso), y ası́ es definido inicialmente (double ** ...). Esto puede parecer
extraño a primera vista, pero recordemos que los punteros pueden ser punteros a cualquier
objeto, en particular a otro puntero. Luego se crean los punteros de cada columna (primer
ciclo for). A continuación se llenan los elementos con ceros (segundo ciclo for). Finalmente,
se libera la memoria, en orden inverso a como fue asignada: primero se libera la memoria de
cada columna de la matriz (delete [] matriz[i], tercer ciclo for), y por último se libera
la memoria del puntero a estos punteros (delete [] matriz).
Apéndice F
make y Makefile.
make es un comando particularmente útil cuando se trata de hacer tareas repetitivas y que
involucran a un número posiblemente grande de archivos distintos para producir un resultado
final. La idea es sencilla: dar el comando make significa, básicamente, decirle al sistema:
“haga todo lo que sea necesario hacer para crear una versión actualizada del resultado final,
considerando que algunos de los archivos necesarios para producirlo pueden haber cambiado
desde la última vez”. A través de algunos ejemplos simples veamos cómo es posible traducir
este mandato en instrucciones que un computador pueda entender.
int main() {
cout << "Hola" << endl;
return 0;
}
El comando g++ -o hola hola.cc creará un ejecutable a partir de esta fuente. Por su-
puesto, al dar nuevamente el comando g++ -o hola hola.cc el proceso de compilación
comenzará nuevamente. Esto no es muy eficiente, porque hola sólo depende de hola.cc, y
no necesita ser recreado a menos que hola.cc sea modificado.
Usemos, en cambio, el comando make. Creemos el archivo hola.cc (si ya existe, modifi-
quémoslo agregándole un espacio en blanco, por ejemplo), y luego ejecutemos el comando:
user@hostname:~$ make hola
La siguiente lı́nea es escrita en el terminal:
g++ hola.cc -o hola
387
388 APÉNDICE F. MAKE Y MAKEFILE.
Esta vez make no tiene ningún efecto. Ésa es toda la gracia de make: darse cuenta de
qué necesita ser recreado, porque sabe qué resultado depende de qué archivo fuente.
hola: hola.cc
g++ -o $@ $<
La sintaxis es la siguiente:
– hola: es un target (blanco). Indica que la siguiente regla sirve para generar el archivo
hola.
– Luego de los dos puntos, vienen las dependencias de hola. En este caso, sólo es hola.cc.
– En la segunda lı́nea, viene la regla para generar hola. Aquı́ se han usado dos variables
predefinidas de make (hay muchas más): $@ es el target de la regla, y $< es la dependencia
de la regla. En este caso, entonces, una vez que make reemplaza dichas variables, la regla
queda como g++ -o hola hola.cc, que es por supuesto lo que queremos.
Un punto muy importante: En la regla, antes de los caracteres g++, no hay 8 espacios
en blanco, sino un carácter de tabulación, TAB. De hecho, si uno reemplazara ese TAB por
8 espacios, make se darı́a cuenta. La versión 3.81 de GNU make, por ejemplo, entrega el
siguiente mensaje al intentar ejecutarlo:
user@localhost:~$ make
Makefile:2: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.
F.2. CREANDO UN MAKEFILE 389
Este Makefile hace exactamente lo mismo que en la Sec. F.1, pero ahora no necesitamos
descansar en las dependencias default: make hola busca el target hola en el archivo Makefile
en el directorio actual, y revisa sus dependencias. Si alguna de las dependencias es más
reciente que el archivo hola, entonces ejecuta las lı́neas que constituyen la regla. (En el
ejemplo analizado, la regla consta de una sola lı́nea, pero podrı́a ser por supuesto más de una
lı́nea si para generar el target se debe ejecutar más de un comando.)
Observemos que, puesto que make toma decisiones sólo en base a las fechas de modificación
de los archivos, para probar que las reglas funcionan adecuadamente no es necesario editar
hola.cc. Basta con touch hola.cc, con lo cual la fuente aparecerá como más reciente que
el ejecutable.
En un mismo Makefile puede haber más de una regla. Por ejemplo:
hola: hola.cc
g++ -o $@ $<
chao.dvi: chao.tex
latex $<
En este caso, además del ejecutable hola, podemos generar un archivo chao.dvi, compi-
lando con LATEX el archivo chao.tex. Es claro, entonces, que un solo Makefile nos permite
generar muchos archivos distintos, con una única lı́nea de comando, del tipo make <target>.
Si se omite el argumento, make ejecuta el primer target que encuentra en el Makefile. Por
ello, puede ser útil, dependiendo de la aplicación, poner al principio un target que simplemente
genere todos los archivos necesarios:
all:
make hola
make chao
hola: hola.cc
g++ -o $@ $<
chao.dvi: chao.tex
latex $<
Ası́, el comando make, sin argumentos, compilará lo necesario para obtener los archivos finales
actualizados.
Notamos que all es un target especial, pues no tiene dependencias. Es decir, se ejecuta
incondicionalmente. Otro uso habitual de un target sin dependencias es el siguiente:
all:
make hola
make chao
hola: hola.cc
g++ -o $@ $<
390 APÉNDICE F. MAKE Y MAKEFILE.
chao.dvi: chao.tex
latex $<
clean:
rm hola chao.dvi chao.log chao.aux
Ahora, make clean borra todos los productos de compilación y archivos auxiliares que
hayan podido generarse, quedando sólo lo importante: las fuentes, y el propio Makefile.
\documentclass[12pt]{article}
\usepackage{graphicx}
\begin{document}
Una figura:
\includegraphics{figura.eps}
\end{document}
clean:
rm texto.dvi texto.log texto.aux
¿Qué pasa ahora si figura.eps, a su vez, es generada a partir de otros archivos fuente? Por
ejemplo, digamos que creamos, con xfig, el archivo figura.fig. En principio, si editamos
la figura, podemos no sólo grabarla como un archivo fig, sino también como un archivo
postscript, con lo cual la figura queda actualizada. Existe otro modo, un poco más eficiente:
en xfig grabamos sólo la versión fig, y generamos el eps con la lı́nea de comandos:
Ahora podemos incluir este comando como una regla adicional en el Makefile:
figura.eps: figura.fig
fig2dev -L eps $< $@
clean:
rm texto.dvi texto.log texto.aux figura.eps
Para cada dependencia declarada, make verifica si existe alguna regla para construirla. En
el caso de texto.tex no la hay, ası́ que continúa. Para figura.eps la hay: verifica la fecha
de modificación de figura.fig, y determina si es necesario actualizar figura.eps. Ahora
que todas las dependencias de texto.dvi están actualizadas, revisa si es necesario actualizar
texto.dvi.
Son claras entonces las ventajas de make: Un proyecto dado, por complicado que sea, se
puede escribir en términos de productos finales y dependencias, posiblemente múltiples, y
posiblemente encadenadas. Naturalmente, proyectos más complicados tendrán un Makefile
más complicado. Pero una vez construido éste, el producto final se obtiene simplemente con
el comando make.1
Observemos, adicionalmente, que en el último Makefile agregamos al target clean la
orden de borrar figura.eps. Ahora dicho archivo puede ser generado siempre a partir de
figura.fig, por tanto no es necesario mantenerlo en disco.
Es claro que este proceso es cada vez más engorroso mientras más figuras tenga un archi-
vo. Aquı́ es donde es útil definir variables de usuario, para evitar modificar tantas veces el
1
El software distribuido en forma de código fuente habitualmente hace uso de esta herramienta. Ası́,
independiente de la complejidad del software, el usuario simplemente debe dar un solo comando, make, para
obtener el ejecutable final.
392 APÉNDICE F. MAKE Y MAKEFILE.
figuras=figura.eps otra_figura.eps
figura.eps: figura.fig
fig2dev -L eps $< $@
otra_figura.eps: otra_figura.fig
fig2dev -L eps $< $@
clean:
rm texto.dvi texto.log texto.aux $(figuras)
De este modo, agregar más figuras implica sólo dos modificaciones: agregar un valor a la
definición de figuras, y agregar la regla correspondiente.
Pero aún es posible una simplificación adicional: si todas las figuras eps van a venir de
fig, ¿será posible que make entienda eso de una vez y para siempre, y no tener que darle
reglas redundantes? Sı́, es posible, y eso se logra gracias a los patrones, simbolizados con el
carácter especial %. Observemos el siguiente Makefile, equivalente al anterior:
figuras=figura.eps otra_figura.eps
$(figuras):%.eps:%.fig
fig2dev -L eps $< $@
clean:
rm texto.dvi texto.log texto.aux $(figuras)
figuras=figura.pdf otra_figura.pdf
$(figuras):%.pdf:%.fig
fig2dev -L pdf $< $@
clean:
rm texto.pdf texto.log texto.aux $(figuras)
Ahora podemos automatizar todo este proceso a través del comando make. Basta con
construir un Makefile con las siguientes condiciones:
El ejecutable final (hola) depende de los object files involucrados, y se construye lin-
keándolos entre sı́.
Cada object file depende de su respectiva fuente (archivos .cc y .h), y se construye
compilando con g++ -c).
make clean puede borrar todos los object files y el ejecutable final, pues se pueden
construir a partir de las fuentes.
hola: $(objects)
g++ $(objects) hola.cc -o $@
$(objects):%.o:%.cc %.h
g++ -c $< -o $@
clean:
rm hola $(objects)
Observémos cómo hemos usado los patrones para crear dependencias múltiples: cada
archivo o depende de dos archivos con el mismo nombre, pero con extensiones cc y h.
Ya con este Makefile, de ahora en adelante, crear el ejecutable hola será mucho más
sencillo: editar los archivos fuentes y dar el comando make.
############################################################
# Makefile for C++ program EMBAT
# Embedded Atoms Molecular Dynamics
#
# You should do a "make" to compile
############################################################
#Compiler name
CXX = g++
#Linker name
396 APÉNDICE F. MAKE Y MAKEFILE.
LCC = g++
embat : $(OBJFILES)
$(LCC) -o embat $(OBJFILES) $(LIBFLAGS)
$(OBJFILES): %.o:%.cc
$(CXX) -c $(CPPFLAGS) $< -o $@
############################################################
clean:
rm embat $(OBJFILES)
############################################################
Podemos reconocer varias de las técnicas discutidas en las secciones anteriores. El eje-
cutable embat se construye a partir de ciertos OBJFILES, que se construyen compilando los
archivos cc respectivos. Se compila usando g++, desplegando todos los warnings (opción
Wall), con nivel de optimización 4 (opción O4), y usando las librerı́as matemáticas de C++
(opción m). Observamos también algunos aspectos adicionales:
G.1. Objetivo.
Se espera que tras leer este apéndice el lector interesado sea capaz de:
Elaborar sus propias paginas web utilizando PHP, a partir de las cuales se pueda
intercambiar información de manera segura mediante el sistema gestor de base de datos
MySql.
G.2. Prerequisitos
Conocimiento básico sobre qué es un navegador, es decir, tener cierto tiempo utilizándo-
lo.
399
400 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
G.3.1. Ejemplos
Página Estática en html.
• Archivo hola.html
<html>
<title> :::::El Clasico Hola mundo::: </title>
<body> Hola mundo! </body>
</html>
El ejemplo anterior corresponde a lo más simple en una página web en html ; notar que el
script está estructurado mediante el uso de etiquetas del tipo: <algo> .... </algo>.
• Archivo hola.php
<html>
<?php $h="Hola mundo!"; ?>
<title> :::::El Clasico <?php echo $h; ?>::: </title>
<body> <?php echo $h; ?> </body>
</html>
Creando scripts.
En las secciones posteriores se listarán los comandos que permiten generar scripts, el
modo de chequearlos y, de esta forma, aprender mediante ensayo y error es:
Luego de seguir estos pasos, el navegador desplegará en la pantalla la ejecución del script.
Es importante señalar que el navegador no acusa errores de manera tan implacable como
compiladores u otros intérpretes (p.ej. Python), por lo cual se recomienda ser especialmente
riguroso.
<html>: Esta etiqueta delimita en qué parte del script comienza y termina el código en
html.
<body>: Contiene la información que será desplegada en la pantalla, ya sea texto imáge-
nes, sonido, etc.
Cabe destacar que ninguna de las etiquetas mencionadas es obligatoria; puede precindirse
de ellas si tan sólo se quiere escribir texto sin ninguna estructura.
<br>: Introduce un salto de lı́nea. A diferencia de las etiquetas anteriores, ésta no tiene
una etiqueta de cerrado.
<hr>:Introduce una lı́nea horizontal. Al igual que en la etiqueta anterior, ésta es des-
apareada.
<font>: Etiqueta que permite definir atributos sobre el texto, tales como el porte o el
color. Por ejemplo, si se requiere texto en rojo: <font color="red">.
El campo bgcolor corresponde al color de fondo; text al color del texto; link y alink, a los
colores de los links por visitar y visitados respectivamente.
Alternativamente, es posible poner una foto de fondo de página, simplemente hay que
suplir bgcolor por:
<body background="fondo.jpg">
Se recomienda poner fotos pequeñas que sean visualmente agradables como mosaicos, de
lo contrario, puede variar el cómo se vean dependiendo del navegador, además de hacer más
pesada la página. El siguiente ejemplo utiliza las herramientas desarrolladas.
Ejemplo
• Archivo ejemplo2.html
<html>
<title> :::Herramientas de estilo::: </title>
<body bgcolor="#336699" text="#000033" link="#660000" alink="#33ff00">
<h1 align="center" > <font color="red">
Sobre lo que se hablará en esta página estática
</font> </h1>
<p align="right">
<em> ...Aquı́ por ejemplo una cita para comenzar</em>
</p>
<br>
<p algin="center">
Escribiendo la parte medular de la página.....<br>
Es posible escribir una formula sin caracteres especiales como la siguiente:
3
La cual por razones obvias debe ser vista a color.
404 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
<p align="center">(a<sub>11</sub>+a<sub>22</sub>+....)
<sup>2</sup>=(traza)<sup>2</sup></p>
<p align="left"> Finalmente, se espera un manejo
<b> básico </b> de <em> html </em> si se ha logrado comprender
<u> este ejemplo por completo </u></p>
</body>
</html>
Otros recursos.
Como se señaló en la introducción, la caracterı́stica más relevante de una página web es
su capacidad de interconexión con otras mediante links. Asimismo, existen otros recursos
ampliamente usados que mejoran el aspecto y orden de una página en html.
<img src="path">
En la orden anterior, la imagen (dentro del espacio que puede utilizar) se encuentra
alineada a la izquierda y tiene un marco de "20" horizontal por "30" vertical.
Links.
El enlace, es decir, el espacio de la página donde el cursor del mouse cambia y permite
acceder a la página siguiente, puede corresponder tanto a texto como a una imagen:
Los path pueden ser relativos5 si se trata de material presente en la misma máquina;
de tratarse de un enlace externo a la página, debe especificarse la URL completa.
Enlace sobre una imagen: Opera exactamente de la misma manera que un enlace
de texto y solo cambia el argumento dentro de la etiqueta.
Tablas.
Una tabla permite administrar el espacio en una página de manera eficiente y es especial-
mente útil cuando se quiere una página ordenada y sin muchas caracterı́sticas gráficas. La
tabla es delimitada por la etiqueta <table>, dentro de ésta, las filas quedan delimitadas por
la etiqueta <tr>. A su vez, el elemento de cada columna queda atrapado en la etiqueta <td>.
Dentro de los elementos de la tabla es posible utilizar prácticamente cualquier etiqueta. A
continuación, un esquema sobre cómo se programa una tabla:
<table>
<tr> <td> "a21"</td> <td> "a22"</td> <td> "a23"</td> <td> "a24"</td> </tr>
<tr> <td> "a31"</td> <td> "a32"</td> <td> "a33"</td> <td> "a34"</td> </tr>
<tr> <td> "a41"</td> <td> "a42"</td> <td> "a43"</td> <td> "a44"</td> </tr>
</table>
Figura G.1: Esquema de una tabla en html, utilizando los elementos de una matriz.
G.6.3. Formularios.
Toda la preparación previa que se ha llevado a cabo tiene como fin el proveer de interfaz
gráfica a la página dinámica. El instrumento que permitirá recibir información desde el visi-
tante son los formularios, el qué se hace con dicha información escapa de las posibilidades de
html. Para procesar la información debe recurrirse a otros lenguajes.
Diseño de formularios.
Existen una serie de formularios que deben escogerse según lo requiera la clase de página
que se éste programando. A continuación se listan las etiquetas para implementar los de
406 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
mayor uso, recuérdese que se está dentro de un formulario y por lo tanto dentro de una
etiqueta <form>.
Cajas de texto: Para crear una dentro del formulario basta escribir:
Si bien son válidas otras etiquetas adicionales, cabe destacar que el atributo type tam-
bién admite otras alternativas de rellenado tales como password, el cual oculta el texto
introducido. Usualmente, la información recaudada en los formularios es procesada de
manera algorı́tmica, por lo que conviene dar menos libertad sobre qué información es
ingresada por el usuario. Para ello se le hace optar, como se muestra en los 3 siguientes
diseños de formularios.
Listado de opciones: La sintaxis es la siguiente:
<select name="escoga">
<option value="op1">nombrealt1</option>
<option value="op2">nombrealt2</option>
<option value="op3">nombrealt3</option>
<option value="op4">nombrealt4</option>
</select>
Sobra decir que un formulario puede también ser mixto, es decir, contener listados de
opciones cajas de textos y/o botones de radio.
Envió de la información.
Una vez que se ha finalizado la definición del formulario, debe agregarse un botón que
envı́e la información, el cual corresponde a otro input type. Adicionalmente, si el formulario
es muy largo es posible agregar un botón que ponga el formulario en blanco. Las sintaxis son
las siguientes:
Botón de envió de la información: <input type="submit" value="boton_de_envio">
Botón de reseteo: <input type="reset" value="resetear">
G.6. PÁGINAS BÁSICAS EN HTML. 407
Ejemplos
Con las nuevas herramientas es posible construir páginas como las expuestas en los si-
guientes ejemplos: usando el primer ejemplo de esta sección como un archivo de nombre
ejemplo2.html, se puede construir un pequeño árbol de páginas con los siguientes 2 scripts.
El primer script requiere una foto llamada inicio.jpg en el directorio local.
• Archivo ejemplo3.html
<html>
<title> :::Utilizando los nuevos recursos::: </title>
<body bgcolor="#666666" text="#660000" link="#66FF00" alink="#660066">
<hr>
<table align="center"><tr><td><a href="ejemplo2.html"> ejemplo<br>
anterior </a></td><td> <a href="ejemplo4.html">
<img src="inicio.jpg" align="center" hspace="30" vspace="20"></a>
</td> <td> <a href="http://www.uchile.cl">enlace externo</a>
</td>
</tr></table> <hr>
</body>
</html>
• Archivo ejemplo4.html
<html>
<title>.::Ejemplo del uso de formularios tipo caja de texto::.</title>
<body bgcolor="#000000" text="#FFFFFF" link="#66FF00" alink="#00FF99">
<br>
<h2 align="center"> Complete con sus datos y gane!<h2>
<form " method="post" action="procesa.php">
<table align="center">
<tr><td> Nombre:</td><td><input type="text" name="Nombre"></td></tr>
<tr> <td>e-mail:</td><td> <input type="text" name="email"></td></tr>
<tr><td>Telefono:</td><td> <input type="number" name="fono"></td></tr>
<tr><td>Dirección:</td><td> <input type="text" name="direc"></td></tr>
<tr><td></td><td><input type="submit" value="enviar"></td></tr>
</form>
</table>
</body>
</html>
<html><title>::Listado de opciones:::</title>
<body bgcolor="#000000" text="#FFFFFF" link="#66FF99" alink="#660033">
<h1 algin="left"><u>Particulas subatomicas</u></h1>
408 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
G.7. MySql.
G.7.1. Iniciando sesión.
MySql es un administrador de base de datos que tiene una estructura de usuarios similar
a la de UNIX: existe un superusuario llamado root y los usuarios ordinarios, cada uno con su
propia cuenta. Se supondrá en los siguientes ejemplos la posesión de una cuenta cuyo nombre
de usuario es lamp y de palabra clave bd. Para comenzar a interactuar con el administrador
de bases de datos, hay que iniciar una sesión en éste desde la consola del siguiente modo:
usa@host:$mysql -u lamp -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 6 to server version: 4.0.24_Debian-10sarge2-log
Type ’help;’ or ’\h’ for help. Type ’\c’ to clear the buffer.
mysql>
La orden que se da desde la consola es: usa@host:$mysql -u lamp -p, que quiere decir:
“comienza una sesión de MySql con el nombre de usuario lamp (-u lamp) y pide el password
(-p)”. La última lı́nea corresponde al prompt de MySql.
o en general:
Notar que los string deben ir entre comillas dobles o simples, no ası́ los números. Si se
escribe dos veces el mismo registro, la base de datos guardará dos registros diferentes
con exactamente la misma información y los diferenciará por su fecha de ingreso.
• Orden:
La mayorı́a de las veces es necesario saber parcialmente de qué forma vendrán
los datos listados, para ello es necesario introducir un orden y una tolerancia, de
esta forma es posible controlar exactamente qué hacer aparecer en el resultado de
la búsqueda. Supóngase que se quieren mostrar todos los elementos de la agenda
ordenados alfabéticamente por nombre, para esto hay que escribir:
Queda claro que hace la condición ORDER BY. Supóngase que se desea invertir el
orden y poner los últimos 3 registros, para ello debe escribirse:
Esta sentencia permite la actualización de los datos, es decir, toma un registro viejo
y le modifica algún campo. Supóngase que en el ejemplo de la agenda telefónica se
quiere cambiar el teléfono a un usuario llamado “Juan”, quien tiene un nuevo teléfono
“8571646”. Para hacerlo debe introducirse la orden:
+--------+-------+
| nombre | clave |
+--------+-------+
| andres | te45 |
+--------+-------+
1 row in set (0.00 sec)
Se ha expuesto lo más básico para poder interactuar con la base de datos. El conocimiento
de esta sintaxis volverá más rápidos, seguros y eficientes lo programado; sin embargo, el
alcance del apéndice utilizará tan sólo las herramientas aquı́ expuestas.
<html>
<?php $h="Hola mundo!";?>
<title> :::::El Clasico <?php echo $h; ?>::: </title>
<body> <?php echo $h; ?> </body>
</html>
<html>
<title> :::::El clasico Hola Mundo!:::</title>
<body> Hola Mundo! </body>
</html>
Debe quedar completamente claro que el código en PHP jamás abandona el servidor web,
por lo tanto, el código en PHP se encuentra inserto dentro del html y no viceversa. Dentro
de un script en html es posible, las veces que sea necesario y donde sea necesario, escribir
código en PHP.
G.8.2. Variables.
Las variables en PHP pueden ser string, números o arreglos. Para desplegar en pantalla
una variable se utiliza la instrucción echo. El ejemplo anterior constituye un caso simple de
esto.
Ejemplo
• Archivo procesa.php
Este archivo simplemente toma las variables y las despliega en pantalla. Se debe notar
que la información contenida en un formulario queda contenida en el grupo de variables
$_POST[’nombredelavariable’]. Con este conocimiento es posible rehacer todo ejercicio
propuesto en el capı́tulo de C++ donde se pidan variables y opere con ellas en PHP. Cabe
destacar todo lo que es posible mezclar html y PHP.
ejemplo while
<?php $i=1;
$j="Ejemplo de un bucle haciendo iteraciones, iteracion no :";?>
<html><body bgcolor="#336699" text="000033" link="660000" alink="#33ff00">
<h2 align=’center’> LOOP <em> while </em>. </h2>
<p align="right">
<?php while($i<10)//comentario en php
{echo $j . $i; $i++; ?> <br> <?php } ?>
</p></body></html>
ejemplo if
El condicional if logra que el script haga diferentes cosas según el valor de alguna variable.
A fin de economizar código, a continuación se ejemplificará el uso del control de flujo if
procesando la información introducida en el formulario del ejemplo4b.html. La idea es tener
la información contenida en variables diferentes y que ésta sea desplegada en la pantalla
según se elija. La sintaxis del if es exactamente igual que en C++. • Archivo psa.php
<?php
$opcion=$_POST[’psa’];
//proton
$pmasa="1,672 * 10^27 kg";
$pcarga="1,60217653(14)*10^(-19)C";
$ps="-";
//neutron
$nmasa="1,672 * 10^27 kg";
$ncarga="0";
$ns="no tiene carga";
//electron
$emasa="9,10 * 10^(-31) kg";
$ecarga="1,60217653(14)*10^(-19)C";
$es="-";
//general
G.8. PROGRAMACIÓN EN PHP. 415
$masa;
$carga;
$signo;
if ($opcion=="proton")
{$masa=$pmasa;
$carga=$pcarga;
$signo=$ps;}
else if ($opcion=="electron")
{$masa=$emasa;
$carga=$ecarga;
$signo=$es;}
else
{$masa=$nmasa;
$carga=$ncarga;
$signo=$ns;}
?>
<html><title> informacion</title>
<body bgcolor="#000000" text="#FFFFFF" link="#66FF99" alink="#660033">
<table><tr><td>La particula: </td><td> <?php echo $opcion; ?> </td></tr>
<tr><td>tiene una masa de :</td><td> <?php echo $masa; ?> </td></tr>
<tr><td>tiene una carga de signo :</td><td> <?php echo $signo; ?> </td></tr>
<tr><td>tiene una cantidad de carga :</td><td> <?php echo $carga; ?> </td></tr>
</table></html>
G.8.6. Sesión.
PHP tiene la capacidad de definir variables globales sobre un conjunto de páginas a
elección; para ello debe realizarse una sesion. Éstas son usualmente utilizadas cuando se posee
una estructura de usuarios. Para poder crear una sesión debe contarse con: un árbol de páginas
ya armado, una página donde se inicie la sesión, y una página donde se termine la sesión. Para
ilustrar el uso de las sesiones se considerarán 4 páginas: el formulario del ejemplo4.html; el
ejemplo anterior procesa.php(con un par de modificaciones) como página de inicio de sesión;
una nueva página ejemplo5.php, que gracias a la sesión es capaz de recuperar las variables;
y una página de cierre de sesión salir.php, la cual vuelve al ejemplo4.html. Es importante
416 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
adquirir el hábito de generar árboles de páginas lo más intuitivos posible, pues éstas suelen
ser usadas por personas con poco o nulo conocimiento de su construcción y gran parte de su
éxito radicará en su simpleza. Sin más preámbulos, los ejemplos son los siguientes:
Ejemplos.
• Nuevo archivo procesa.php
<?php
session_start();//inicio de la sesión
header("Cache-control: private");
//esta lı́nea se escribe para no borrar los formularios como lo hace i.e.
?>
<html><title> recuperando las variables </title>
<body bgcolor="#000000" text="#FFFFFF" link="#66FF00" alink="#00FF99">
<h2 align="center"> La información tirada a pantalla<h2>
<p align="center"> Tu nombre es <b><?php echo $_POST[’Nombre’];?>
</b>, vives en <b> <?php echo $_POST[’direc’]; ?> </b>. Tu e-mail es <b>
<?php echo $_POST[’correo’]; ?>
</b>, además tu teléfono es <?php echo $_POST[’fono’];?> </p>
<h1 align="center"> <a href="ejemplo5.php"> <font color="red">
Aqui para seguir </font></a></h1>
</body>
</html>
<?php
$_SESSION[’nombre’]= $_POST[’Nombre’];
$_SESSION[’mail’]= $_POST[’correo’];
//Definiendo las variables globales de session.?>
• Archivo ejemplo5.php
<?php
session_start();
?>
<html>
<body bgcolor="#000000" text="#FFFFFF" link="66FF00" alink="00FF99">
<table align="center">
<tr><td> <b>
Las variables aun se recuperan y son:
</b></td></tr>
<tr><td> <b>
El nombre era: <em>
<?php echo $_SESSION[’nombre’]; ?>
</em> </b></td> </tr>
<tr><td> <b>
El correo era: <em>
G.8. PROGRAMACIÓN EN PHP. 417
1. Conectarse a MySql.
2. Escoger la base.
5. Enviarla (si habı́a que escribir en la base de datos, con esto es suficiente).
1. $conexion=mysql_connect(’localhost’,’lamp’, ’bd’)or
die(’No es posible conectar’.mysql_error());
Se conecta al servidor de MySql local bajo el usuario “lamp”. Podrı́a parecer en principio
un poco inseguro que aparezca la clave del usuario explı́citamente en el script; sin
embargo, recuérdese que esta parte del script está escrita en lenguaje PHP y por lo
tanto jamás abandona la máquina donde se encuentra el script.
3. Para leer o escribir en la base de datos, basta crear una variable de string con la sin-
taxis de lo requerido en lenguaje sql. Por ejemplo, supóngase que se quiere escribir en
la agenda un nombre guardado en la variable $nombre y un teléfono almacenado en la
variable $telefono; la sintaxis es:
$p="insert into agenda(nombre, telefono) values (’$nombre’,’$telefono’)";
4. Por otra parte, si quiere escogerse un registro particular, por ejemplo el número te-
lefónico del usuario $usuario , la sintaxis es:
$u="select nombre,telefono from agenda where nombre=’$usuario’";
5. Independiente de si se quiera leer o escribir, si la petición está en una variable $p, esta
se ejecuta en MySql mediante la orden:
$pedido=mysql_query($p) or die (’no se pudo’);
$fila=mysql_fetch_row($pedido);
$arreglo=mysql_fetch_array($pedido);
G.9. EJEMPLO FINAL. 419
En ambos casos, el arreglo se recorre de la misma manera que en C++. Por ejemplo,
en el primer caso, si quiere obtenerse el primer elemento, éste corresponderá a la va-
riable $fila[0].En contraste, el segundo caso, permite seleccionar los elementos del
arreglo por su nombre dentro de la tabla de MySql. Por ejemplo, si tenemos una tabla
con los campos nombre, dirección, entonces los elementos del arreglo corresponden
a $arreglo[’nombre’] y $arreglo[’dirección’] respectivamente. En la práctica es
mucho más utilizada esta representación que la de row, pues es más fácil identificar
los elementos. Finalmente, cabe señalar que en ambos casos, los arreglos respectivos,
contienen un sólo registro, pese a que el query, contenga más de uno. Para obtener
todos los registros arrojados por el query, basta recorrerlo con un while, de la siguiente
forma.
Ésta es toda la sintaxis que es necesario hacer desde la consola de MySql. Ahora toca
“pensar el árbol de páginas” y la comunicación que tendrán éstas con la base de datos.
<?php session_start();
$nombre=$_POST[’nombre’];
$clave=$_POST[’clave’];
$tipo=$_POST[’usrcls’];
$conexion=mysql_connect(’localhost’,’lamp’, ’bd’)or
die(’No es posible conectar’.mysql_error());
mysql_select_db(’ramo’)or die (’error al conectar a la base’);
if($tipo==1)
{$p="select Nombre,clave from alumnos where Nombre=’$nombre’ and
clave=PASSWORD(’$clave’)";
$q=mysql_query($p) or die(’no se pudo hacer el pedido’);
$b=mysql_fetch_row($q);
if($b==false)
{echo "usted no es reconocido por el sistema";
?> <a href="log.html"><font color="red"> Volver</font></a>
<?php}
else
{$_SESSION[’estado’]="conectado"; require(’notas.php’);}}
else if($tipo==2)
{$p="select Nombre,clave from administrador where Nombre=’$nombre’
and clave=PASSWORD(’$clave’)";
$q=mysql_query($p) or die(’no se pudo hacer el pedido’);
$b=mysql_fetch_row($q);
if($b==false)
{echo "usted no es reconocido por el sistema";
?> <a href="log.html"><font color="red"> Volver</font></a><?php}
else{$_SESSION[’estado’]="conectado"; require(’acciones.php’);}}
else{require("log.html");}?>
La función del script es la siguiente: primero recupera las variables introducidas en el
formulario y se conecta a la base de datos ramo; después, en función del valor de la variable
’usrcls’, decide dónde buscar la información del nombre de usuario y claves respectivas. Fi-
nalmente, la última lı́nea contempla la posibilidad de que se haya intentado acceder a esta
página sin haber pasado por el formulario, requiriéndoló. A partir de este script, el árbol de
páginas se bifurca en 2 : la “rama del usuario” y la “rama del administrador”. Los nom-
bres de las páginas ya fueron nombrados en el último script. Se lista a continuación el script
acciones.php, el cual permite al administrador ingresar información de manera intuitiva.
Archivo • acciones.php
<?php session_start();if ( $_SESSION[’estado’] != ’conectado’ )
{die( "Ud no esta logueado!.Click aqui para <a href=’log.html’>volver</a>");}?>
<html><title> Administrando las notas</title>
<body bgcolor="#000000" text="#66FF00" link="#CC0033" alink="#66FF66">
<font color="red"><u><a href="salir.php"><h4 align="right"> Cerrar sesión.</h4>
<h1 align="center"><font color="blue"> acciones.</h1>
<table><tr><td><?php require(’nuser.php’);?></td><td>
422 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
<?php require(’nnota.php’);?>
</td></tr></table></body></html>
Las primeras lı́neas chequean que el usuario se haya “logueado” y especifica el estilo de la
página. Luego, se utiliza la función require para solicitar los 2 formularios que corresponden a
las acciones que pueden ser realizadas por el administrador: crear nuevos usuarios y agregar
notas a cada usuario. Ambos formularios son del tipo caja de texto y la información de éstos
es enviada a un archivo del mismo nombre con una “p” final (de procesar). Los archivos
que procesan la información solo escriben directamente en la base de datos la información
obtenida de los formularios. Finalmente, cabe destacar el link “cerrar sesión”, el cual conduce
a un script que cual destruye todas las variables de sesión y devuelve a la página de “logueo”
log.html, lo que se presenta a continuación:
Archivo • salir.php
A continuación se presentan los scripts que permiten poner notas, crear nuevos usuarios
y sus respectivos archivos de proceso: Archivo • nnota.php.
Debiera quedar completamente claro lo que ejecuta este script: recupera las variables y revisa
que no se haya sobrepasado la cota máxima de las 8 notas por alumno. De ser ası́, procede
a escribir la nueva información en la base de datos. Cabe notar que la información no esta
escrita con la sentencia INSERT, sino UPDATE, por lo cual las notas pueden ser cambiadas. La
segunda acción habilitada por el administrador es la de agregar nuevos usuarios, lo cual se
hace de una manera totalmente análoga a los scripts anteriores:
Archivo • nuser.php
Con esto se da por finalizado el proceso de introducir información al sistema. Ahora solo
resta generar un script que muestre la información correspondiente a cada usuario.
Archivo • notas.php
$pedido=mysql_query($pedir);
$notas=mysql_fetch_row($pedido);
?>
<html>
<title> Notas: </title>
<body bgcolor="#333300" text="#3300FF" link="33FF33" alink="#669900">
<h2 align="right"> <a href="salir.php" >
<font color="red" <u> Cerrar sesion.</u>
</a> </h2>
<h3 align="left"> <font color="green"> Alumno <?php echo $nombre;?></h3>
<table align="left"><tr><td><b> Sus notas son</b></td></tr>
<tr><td>Eval.No :</td><td>Nota</td><tr>
<?php
$i=1;
$g=0;
$cont=0;
while($i<=8)
{ $k=$i-1;
echo "<tr><td>". $i . "</td><td>" .$notas[$k]. "</td></tr>";
$i++;
if($notas[$k]>=1){$cont++;}
$g=$g+$notas[$k];
}
if($g==0){echo "usted no tiene notas";}
else{$t=$g/$cont;}?>
<br><br>
<tr><td>Su promedio final es:</td><td><b><?php echo $t; ?> </b> </td></tr>
</table>
</body></html>
Sobre el último ejemplo cabe destacar: se piden todas las notas del alumno en cuestión; la
base de datos devuelve para esto la información que es convertida en fila (mysql_fetch_row());
luego la fila comienza a ser recorrida utilizando un control de flujo while, el cual va generando
a tiempo de ejecución una tabla en html ; las variables auxiliares $i, $g y $cont son utilizadas
para calcular el promedio del número de notas que existan.
G.10. Conclusiones.
El ejemplo ha sido chequeado siguiendo los pasos dados en este apéndice y funciona de
manera básica (pudiendo ser mejorado),pues su fin no es ser operativo, sino explicativo.
Podrı́a parecer que se trata de una gran cantidad de código, sin embargo, la mayor parte
del tiempo se está repitiendo lo mismo con variaciones obvias. Esperando haber logrado
una comprensión aceptable por parte del lector, A partir de las herramientas entregadas, es
posible realizar proyectos bastante más ambiciosos tales como:
Sistema de votaciones en lı́nea.
G.10. CONCLUSIONES. 425
No se repitan usuarios.
Sobra decir para el lector con algo de experiencia en programación que todas estos pro-
blemas serán fácilmente solucionables utilizando el condicional if, de manera de cubrir todas
las posibilidades. Esto no se ha hecho, pues volvı́a el ejemplo demasiado redundante y por lo
tanto se ha priorizado su funcionalidad.
426 APÉNDICE G. HERRAMIENTAS BÁSICAS EN EL USO DE L.A.M.P.
Figura G.2: Los 256 colores posibles de desplegar en una página en html, con su respectivo
código.
Colección
Libros de Matemática
en PDF