Tema 1: Nociones básicas sobre IDL Para entrar en IDL (Interactive Data Language) 1 desde la lı́nea de comandos debe ejecutarse idl o bien idlde (IDL Development Environment). En estas notas supondremos que entramos tecleando idl pero si prefieres usar la otra opción será todo muy similar. Si todo va bien, al entrar debe aparecer finalmente la lı́nea de comandos siguiente: IDL> IDL posee ayuda dentro de su propio entorno que se obtiene escribiendo: IDL>? No dudes en consultarla cuando sea necesario. Para salir ejecutamos el comando: IDL>exit IDL puede usarse ejecutando programas previamente escritos (lo habitual en cuanto tengamos algo relativamente complicado que programar) o bien directamente, a través de la lı́nea de comandos. En este primer tema aprenderemos algunas nociones básicas usando la lı́nea de comandos directamente. 1. Forma de los comandos Para mostrar por pantalla el resultado de una operación podemos escri- bir: IDL> print,3.1*5.1234 15.8825 Aquı́ print es el nombre del comando (llamado en IDL procedure, procedimiento). Cada comando va siempre seguido de una coma (,). Finalmente 3.1*5.1234 (3.1 multiplicado por 5.1234) es el argumento. Cuando tengamos más de un argumento los separamos mediante comas: 1 IDL requiere de una licencia comercial. Existe una versión libre compatible (aunque no 100 %) que funciona bajo LINUX: GDL - GNU Data Language. Puedes descargarla de: http://gnudatalanguage.sourceforge.net/ 1 IDL> print,10^4,cos(0.) 10000 1.00000 Los comandos admiten también opciones denominadas keywords (palabras claves). La forma general que tienen es: procedure,argumento_1,...,keyword_1=opción,... En los ejemplos anteriores puede especificarse el formato de salida mediante la opción format. Si escribimos: IDL> print,3.1*5.1234,format=’(f7.3)’ 15.883 forzamos a que nos muestre el resultado del producto como un número real (f) de 7 caracteres (5 dı́gitos más el punto más el signo menos si lo hubiera habido) con 3 decimales. Si usamos el formato (f20.16) veremos que nos aparecen 16 decimales aunque eso no significa que el resultado tenga esa precisión; en ningún caso debemos confundir la presentación (que es lo que nos da la opción format) con la precisión del cálculo. Ejercicio 1. Considera la operación: IDL> print,5./3. 1.66667 ¿Cuántos decimales significativos tiene el resultado? 2 Otra forma usual de escribir el resultado es con potencias de diez. El formato es similar al anterior pero con e en lugar de f. Por ejemplo si escribimos: IDL> print,3.1*5.1234,format=’(e11.4)’ 1.5883e+01 vemos que el resultado (1.5883 · 101 ), quince y pico, aparece con 4 decimales. El número 11 representa el número total de caracteres que contiene el resultado, a saber: el signo inicial (en este caso al ser un + deja un blanco), el dı́gito antes del punto, el propio punto, los cuatro decimales, la letra e (que significa diez elevado a), el signo del exponente (en este caso un +) y los dos dı́gitos del exponente. Es decir 7 caracteres más el número de decimales. Como regla general podemos usar 8+número de decimales que queramos (en doble precisión el exponente puede tener tres dı́gitos). 2 Puedes consultar la respuesta a los ejercicios al final del documento. 2 2. Variables La forma más sencilla de crear una variable es asignándole un valor. Por ejemplo, si escribimos: IDL> a=3*5 habremos creado la variable A (no se distingue entre mayúsculas y minúsculas) y asignado el valor 3 × 5. Podemos usar cualquier combinación de letras y números para los nombres de las variables (también se admite el subı́ndice y el $) pero siempre deben empezar por una letra. Por ejemplo son válidas las variables: mas, vec, a1, velo2, x 1, x$a. No es válida 3b. Las variables tienen dos atributos importantes: 1. Tipo de datos. La variable A anterior es de tipo INTEGER (entero). También pueden ser de tipo real, doble precisión, complejo, etc. 2. Tipo de estructura. La variable A anterior es un escalar. Hay también vectores, arrays n x m (matrices), etc. En cada momento podemos ver el tipo de variable y estructura (en el caso de los escalares su valor) mediante el comando help. Por ejemplo: IDL> help,a A INT = 15 Las variables pueden cambiar sus atributos como consecuencia de una operación. Por ejemplo si hacemos: IDL> a=sqrt(a) IDL> help,a A FLOAT = 3.87298 vemos que la variable A pasa a ser un número de tipo FLOAT (floating point number o número con coma flotante) correspondiente a los números reales de menor precisión en IDL, concretamente entre 6 y 7 decimales significativos. Las operaciones anteriores pueden escribirse en una sola lı́nea usando el sı́mbolo &: IDL> a=3*5 & A a=sqrt(a) & help,a FLOAT = 3.87298 En otros lenguajes de programación que probablemente conozcas las variables se definen una vez, digamos al principio del programa, y luego siempre son del mismo tipo (las variables enteras pueden cambiar su valor pero siempre serán enteras, etc.). En IDL no. El hecho de que las variables se definan de forma dinámica tiene sus ventajas: en el ejemplo anterior se calcula la 3 raı́z cuadrada de un número como se hace usualmente y en consecuencia se transforma en real. Pero es importante no olvidarse de las desventajas, en concreto podemos terminar pensando que siempre va a funcionar como intuitivamente esperamos y hay excepciones. En este otro ejemplo: IDL> a=1 & b=3 IDL> print,a/b 0 ¡1/3=0! La división de dos números enteros es la parte entera del cociente. ¿Era eso lo que querı́amos? posiblemente no. Si alguno de los números de un cociente es real el resultado es un número real. Ası́, en el ejemplo anterior, si no queremos que tome sólo la parte entera podemos escribir: IDL> a=1. & b=3. IDL> print, a/b 0.33333 ;ahora, al a~ nadir el punto, A y B son reales. Hemos usado el punto y coma (;) para indicar que lo que sigue es un comentario. Puede estar al principio de la lı́nea o después de un comando. Al programar, los comentarios son muy importantes. Más problemas con los INTEGER: En IDL por defecto estos son de 2 bytes: los números positivos llegan hasta el 32767. Si nos pasamos ocurre esto: IDL> ent=32760 IDL> i=ent+10 & print,i -32766 Con un disparate como este es muy posible que el programa termine por abortar y podamos darnos cuenta del error. No es tan infrecuente pasarnos de 32767, por ejemplo si la variable corresponde al número de datos de un fichero. Ası́ que en muchos casos puede ser una buena idea introducir un LONG INTEGER: IDL> ent=32760L ;la L del final distingue este tipo de enteros IDL> i=ent+10 & print,i ;10L estarı́a bien igualmente 32770 IDL> help,i I LONG = 32770 Los números en doble precisión (14 decimales significativos aproximadamente) pueden introducirse de una forma similar, en este caso añadiendo la letra d: IDL> print,5.d/3.d,format=’(f17.14)’ 1.66666666666667 4 que puede compararse con el resultado del primer ejercicio que se encuentra al final del documento. Igual que usamos el comando & para poner dos sentencias en la misma lı́nea, podemos usar el signo $ para continuar un comando en la siguiente lı́nea: IDL> b=1+2-2+$ IDL> 4 IDL> print,b 5 Como indicamos en su momento, el comando $ se puede usar también como parte del nombre de una variable (por ejemplo var$1); sólo si está al final de la lı́nea y va después de un operador o similar (consulta la ayuda, ?$, para más detalles) indica continuación de lı́nea. Si lo pones al principio de la lı́nea de comandos verás que te envı́a al intérprete de UNIX/LINUX, sin cerrar la sesión de IDL. Puedes volver a la sesión de IDL con el comando exit. 3. Vectores La forma mas sencilla (pero no la más frecuente) de crear un “vector” o array de una dimensión es escribiendo sus elementos entre corchetes: 3 IDL> v=[1,2,4,2] IDL> help,v V INT = Array[4] Aquı́ INT quiere decir que V es una variable de tipo INTEGER mientras que Array[4] nos indica que es un array con cuatro componentes. En IDL muchos operadores y funciones trabajan directamente sobre arrays. Muchas veces el resultado es el que intuitivamente era de esperar: en el ejemplo que sigue sumamos a un vector, 2*v, un escalar, 1, entendiendo que lo que se quiere es sumar el escalar a cada uno de los elementos del vector. IDL> w=2*v+1 IDL> print,v,w 1 2 3 5 4 9 2 5 3 En las versiones antiguas de IDL (anteriores a la 5.0) se usaban los paréntesis en lugar de los corchetes para indicar los elementos de un array. Por compatibilidad, en las nuevas versiones esto sigue funcionando. Sin embargo, dado que los paréntesis se usan para indicar los argumentos de las funciones -sqrt(a), cos(a)- esto puede prestarse a confusión en particular cuando una función y un array poseen el mismo nombre. Por ello no es recomendable usar paréntesis para indicar componentes de arrays aunque sea una práctica muy extendida. 5 La suma, el producto y la división entre dos vectores corresponde respectivamente a la suma, el producto y la división entre componentes: IDL> z=v+w IDL> print,z 4 7 13 7 IDL> v1=[1.5,2.1,3.2] & v2=[3.0,4.2,6.5] IDL> p=v1*v2 & print,p 4.50000 8.82000 20.8000 IDL> d=v1/v2 & print,d 0.500000 0.500000 0.492308 3.1. Subı́ndices Aunque la velocidad de cálculo es muy superior si se trabaja con todos los elementos de un array a la vez, esto no siempre es posible o deseable. Consideremos el vector: IDL> t=[1,2,3,4,5] Para identificar un elemento usamos la notación t[i], siendo i el ı́ndice buscado (que es de tipo INTEGER o en su caso LONG). Como ejemplo si escribimos: IDL> print,t[1] 2 vemos que t[1] es el segundo elemento del vector t. El primero es t[0]. Por otra parte, podemos manejar un conjunto seguido de elementos usando los dos puntos. Por ejemplo: IDL> print,t[0:2] 1 2 3 corresponde a los elementos de t del 0 al 2. Conviene recordar que las variables pueden cambiar sus atributos, incluidas sus dimensiones, mediante una operación. Por ejemplo, para añadir un elemento al final del vector t escribimos: IDL> t=[t,6] & print,t 1 2 3 4 5 6 y si ahora queremos añadir un 7 como tercer elemento: IDL> t=[t[0:1],7,t[2:5]] & print,t 1 2 7 3 6 4 5 6 Consideremos por último algunas funciones básicas que nos permiten crear vectores de forma sencilla (usamos los comandos help y print para ver lo que hacen estas funciones): IDL> v1=findgen(4) & print,v1 0.00000 1.00000 2.00000 3.00000 IDL> v2=indgen(4) & print,v2 0 1 2 3 IDL> help,v1,v2 V1 FLOAT = Array[4] V2 INT = Array[4] IDL> v3=fltarr(4) & print,v3 0.00000 0.00000 0.00000 0.00000 IDL> v4=dblarr(4) & print,v4 0.0000000 0.0000000 0.0000000 0.0000000 IDL> help,v3,v4 V3 FLOAT = Array[4] V4 DOUBLE = Array[4] Cuando necesites una descripción completa de las funciones que acabamos de introducir usa la ayuda (por ejemplo ?findgen). Por otra parte recuerda que la precisión de los números FLOAT y DOUBLE (unos 7 decimales el primero y unos 14 el segundo) no se corresponde con los que nos muestra el compando print por defecto. Ejercicio 2. Crea un vector de 1000 elementos que cubra uniformemente el intervalo [0, 2.5]. A continuación quı́tale los datos 344 y 567. 4. Arrays de dos dimensiones Podemos crear un array 3 x 2 (3 filas x 2 columnas) escribiéndolo columna a columna, de la siguiente forma: IDL> ar=[[1,2,3],[4,5,6]] IDL> help,ar AR INT = Array[3, 2] Vemos que efectivamente se trata de una matriz 3 x 2. Para listarla por pantalla ponemos la transpuesta porque IDL escribe lo que convencionalmente se llaman filas y columnas al revés: IDL> print,transpose(ar) 1 4 2 5 3 6 7 Veamos como operar con arrrays. Para ello creamos dos matrices cuadradas a modo de ejemplo. La primera de la siguiente forma: IDL> a=findgen(9) IDL> a1=reform(a,3,3) IDL> help,a,a1 A FLOAT A1 FLOAT = Array[9] = Array[3, 3] La función reform(a,m,n) nos transforma el vector A en un array de m filas x n columnas. Para la segunda matriz tomamos la transpuesta: IDL> a2=transpose(a1) Probamos a sumar, multiplicar y dividir los dos arrays (los resultados del print están quitados pues son demasiado largos): IDL> IDL> IDL> IDL> IDL> print,a1 print,a2 b1=a1+a2 & print,b1 b1=a1*a2 & print,b2 b3=a1/(a2+1.) & print,b3 Vemos que se suma, multiplica y divide elemento a elemento. Estas son operaciones que nos interesará hacer frecuentemente. Cuidado con no confundir la multiplicación elemento a elemento con la multiplicación de matrices; para obtener esta última debemos poner: IDL> b4=a1#a2 Usa el comando print, transpose(b4) para comprobar que obtienes lo esperado. 4.1. Subı́ndices Supongamos que tenemos una matriz AR cuyos elementos se van conociendo secuencialmente. Por ejemplo supongamos que por el momento sólo sabemos que ar[1,2]=3.5. Entonces para poder asignar un valor a uno sólo de los elementos del array es necesario tener definida la matriz previamente. Una forma sencilla de conseguirlo es haciendo inicialmente cero todos sus elementos. Ası́ escribirı́amos (recuerda la función fltarr): IDL> ar=fltarr(2,4) IDL> ar[1,2]=3.5 ;empezando por cero: fila 1, columna 2 IDL> print,transpose(ar) 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 3.50000 0.00000 8 Nuevamente listamos la transpuesta para que se visualice con la apariencia estándar. Es importante recalcar que sólo cuando asignamos el resultado de una operación a uno o a un conjunto de elementos de un array (es decir cuando en el miembro izquierdo de una igualdad ponemos la variable con corchetes) es necesario tenerlo definida previamente. Por el contrario, las matrices b1, b2, b3 y b4 anteriores las introdujimos directamente pues trabajábamos con todos sus elementos a la vez. Una de las cosas que cuesta más acostumbrarse en IDL (y en otros lenguajes de caracterı́sticas similares) es a operar con trozos de un array. A modo de ejemplo supongamos que tenemos una serie de datos que representan distintas magnitudes (temperatura, presión, densidad). Como por el momento no sabemos como leer ficheros de datos, vamos a crear unos números aleatorios que hagan las veces de datos: IDL>data=randomu(seed,300) La función randomu devuelve números aleatorios uniformemente distribuidos en el intervalo (0,1). Para conseguir el resultado deseado la variable que hemos llamado seed puede ser en realidad cualquier variable no definida previamente. Consulta la ayuda para completar la información. Supongamos que estos números representan los datos indicados anteriormente. Por tanto tenemos datos de temperatura, presión y densidad agrupados en 100 instantes de tiempo distintos. En concreto supondremos que la temperatura, presión y densidad en el instante inicial corresponde a los tres primeros datos del vector:4 IDL> print,data[0:2] 0.957516 0.740806 0.483755 Los siguientes 3 datos, data[3:5], son la temperatura, presión y densidad en el instante siguiente: IDL> print,data[3:5] 0.304793 0.939899 0.735009 y ası́ sucesivamente. Nuestro objetivo final, a modo de ejemplo, es pasar la temperatura de grados centı́grados a Kelvin. En general, si vamos a operar por separado con cada una de las magnitudes, puede ser una buena idea crearnos una matriz que nos separe por filas o por columnas cada una de las variables. Para ello escribimos: 4 Dado que se trata de números aleatorios, los valores concretos que tome el vector data en cada sesión de IDL pueden ser distintos y no tienen porqué coincidir con los mostrados aquı́. 9 IDL> tpd=reform(data,3,100) IDL> help, tpd TPD FLOAT = Array[3, 100] Vemos que la función reform nos transforma el vector data en un array de 3 filas y 100 columnas de forma que en la primera fila tenemos la temperatura, en la segunda la presión y en la tercera la densidad. ¿Seguro? ¿o tendrı́amos que haber tomado 3 columnas y 100 filas? Vamos a comprobar que lo hemos hecho bien usando el comando print adecuadamente. En concreto la temperatura (primera fila, es decir, fila 0) en los dos primeros instantes (columnas 0 y 1) corresponderá a: IDL> print,tpd[0,0:1] 0.957516 0.304793 Vemos que efectivamente coinciden con los datos mostrados anteriormente. Por otra parte, la temperatura, presión y densidad en el instante inicial corresponderán a la primera columna de la matriz tpd. Entonces le decimos: filas 0 a 2, columna 0: IDL> print,tpd[0:2,0] 0.957516 0.740806 0.483755 que efectivamente coincide con lo mostrado al principio. Para indicar “todas” también podemos usar el sı́mbolo *. La sentencia anterior: “todas las filas, la primera columna” es equivalente a: IDL> print,tpd[*,0] Supongamos que tenemos la temperatura en grados centı́grados y la queremos pasar a Kelvin. Con la separación en filas de los datos que hemos hecho es muy fácil: IDL> tpd[0,*]=tpd[0,*]+273.15 es decir sumamos 273.15 a los elementos de la primera fila (la cero) y todas las columnas (*). 4.2. Subarrays Supongamos ahora que tenemos el array tpd pero que nos interesa usar (por ejemplo para guardar) solo la presión y la densidad entre los instante 50 y 60. Entonces creamos un subarray con las filas 1,2 (presión y densidad respectivamente) y las columnas desde la 49 a la 59, de la siguiente forma: IDL> sub_tpd=tpd[1:2 , 49:59] 10 Si, por el contrario, queremos almacenar la presión desde el instante 50 hasta el final podemos hacer lo siguiente: IDL> p=tpd[1,49:99] o bien: IDL> p=tpd[1,49:*] donde el * en este contexto significa “hasta el final”. Esto es útil si el número de datos puede variar lo que ocurrirá frecuentemente. 5. Gráficas Veremos en este apartado lo más elemental. A lo largo del curso iremos aprendiendo algunas cosas más sobre gráficas. Busquemos algo que dibujar. Por ejemplo la suma de dos sinusoides: Creamos un vector con 100 datos uniformemente espaciados en el intervalo [0, 6π]: IDL> x=findgen(100)*6.*!pi/99. Fı́jate como hemos introducido el número π en precisión simple, en doble precisión es !dpi. Creamos un vector con la señal (f1 y f2 son las frecuencias; a1 y a2 las amplitudes; las fases las ponemos igual a 0): IDL> f1=0.15 & f2=0.2 IDL> a1=4. & a2=2. IDL> y=a1*sin(2.*!pi*f1*x)+a2*sin(2.*!pi*f2*x) Dibujamos y(x): IDL> plot,x,y,xtitle=’tiempo (s)’,ytitle=’velocidad (m/s)’ Aquı́ hemos usado el procedimiento plot con los argumentos x, y junto con dos opciones para dibujar los carteles en cada eje. Consulta la ayuda (?plot) cuando sea necesario para ver el resto de opciones. El nombre de las opciones pueden abreviarse mientras no exista ambigüedad, por ejemplo también es válido: IDL> plot,x,y,xtit=’tiempo (s)’,ytit=’velocidad (m/s)’ pero no: IDL> plot,x,y,xt=’tiempo (s)’,yt=’velocidad (m/s)’ Si queremos dibujar otra función sobre esta gráfica lo podemos hacer de la siguiente forma: 11 IDL> w=abs(y) IDL> oplot,x,w,linestyle=2 El procedimiento oplot (overplot) permite pintar encima de lo que hay. Consulta la ayuda para ver las posibilidades según te vaya haciendo falta. En el ejemplo anterior el procedimiento plot nos abrió automáticamente una ventana gráfica. En realidad esto ocurrió porque no tenı́amos ninguna abierta en ese momento. Si ahora queremos un dibujo en otra ventana debemos especificarlo. Para abrir ventanas podemos usar el comando: IDL> window,1 donde en lugar de 1 podemos poner cualquier número entero entre 0 y 31. 5 En la cabecera de la ventana puedes ver el número de cada una (el dibujo anterior está en la 0, ¿no?). Dibujemos algo en la pantalla nueva, por ejemplo: IDL> z=x*y IDL> plot,x,z,tit=’NUEVO’ La ventana gráfica no siempre sale en la posición y con el tamaño que queremos. A parte de usando el cursor de la forma estándar, para cambiar los valores por defecto podemos usar las siguientes opciones: IDL> window, 1, xsize=200, ysize=300 IDL> window, 2, xpos=75, ypos=150 ;tama~ no en pixeles ;posición origen A la hora de crear documentos es muy frecuente incluir figuras que muestren los resultados de un cálculo. En general la mejor forma de introducirlos en los procesadores de texto es guardando previamente las figuras en un fichero postScript o similar. Supongamos que queremos guardar la gráfica que generamos en la ventana 0 como un fichero postScript encapsulado en un fichero de nombre (por ejemplo) figura.prueba.eps. Lo podemos hacer de la siguiente forma: IDL> IDL> IDL> IDL> IDL> IDL> set_plot,’ps’ device,filename=’figura.prueba.eps’,/encapsulated plot,x,y,xtit=’tiempo (s)’,ytit=’velocidad (m/s)’ oplot,x,w,linestyle=2 device,/close set_plot,’x’ El primer comando pasa de modo ventana a fichero postScript, luego abrimos el fichero con el nombre que queramos, dibujamos con los mismos comandos que antes: plot, oplot, etc., cerramos el fichero y por último volvemos al modo pantalla para los siguientes dibujos (suponiendo que eso sea lo que nos interese). La Figura 1 muestra el resultado. 5 No existe un lı́mite para el número de ventanas. Para abrir la que tenga el ı́ndice menor por encima de 31 todavı́a sin usar en la sesión debe escribirse la sentencia window,/free. 12 Figura 1: Este es el resultado de añadir el fichero figura.prueba.eps que acabamos de generar al procesador de texto (LATEX 2ε concretamente). Para decir verdad la opción /encapsulated que pusimos antes nos permite cambiar el tamaño de la gráfica a posteriori con el procesador de texto. Entonces si, como suele ocurrir frecuentemente, nos interesa reducir el tamaño de la gráfica en el texto, conviene aumentar previamente con IDL el tamaño de las letras y números de los ejes para que se vean bien, esto se hace con la opción charsize que nos multiplica el tamaño de los caracteres por un factor, concretamente la figura tiene: plot,...,chars=1.5 6. Respuesta a los ejercicios Ejercicio 1: Por defecto el comando print nos enseña un número determinado de decimales pero la precisión puede ser mayor. Para ver más decimales ponemos: IDL> print,5./3.,format=’(f15.12)’ 1.666666626930 y sabiendo que el resultado debe ser “uno con seis periodo” resultan como mucho 7 decimales significativos. Ejercicio 2: Al tener decimales, el vector debe ser real. Como el comando findgen empieza en 0, el último número será el 999. Entonces para que sea 2.5 lo reescalamos adecuadamente. Luego para quitar los puntos tenemos en cuenta que el elemento 344 es a[343] y el 567 es a[566]. Ası́: IDL> a=findgen(1000)*2.5/999. IDL> print, a[0],a[999] 0.00000 2.50000 IDL> b=[a[0:342],a[344:565],a[567:999]] IDL> help,a,b A FLOAT = Array[1000] B FLOAT = Array[998] 13