Functions and subroutines (in IDL often called procedures) are used to structure large IDL programs. They are compiled separately prior to execution or automatically when called the first time. Once compiled they stay in memory for next usage. Therefore, a function or subroutine has to be recompiled when the source code is modified.
Functions return one specific value, which may be of any data type, scalar or array. The calling sequence is
variable=function_name(arg_1,arg_2,...,arg_n)
A subroutine does not return any value and is simply called by its name
subroutine_name, arg_1, arg_2, ..., arg_n
Like Fortran, and unlike C, IDL passes arguments by reference, hence procedures can change their values and the calling program will receive the changed values.
If a procedure name is unknown to IDL when encountered, it will try to compile a file called <procedure_name.pro>. Therefore the natural way to store the source code of a function or subroutine module is to put it in a file with the corresponding name.
To define a function, use the following syntax:
FUNCTION function_name, arg_1, arg_2, .... arg_n
....
Statements
....
RETURN, value
END
Very similarly, procedures have this structure:
PRO procedure_name, arg_1, arg_2, .... arg_n
....
Statements
....
RETURN
END
Note that the only difference is that no value is returned to the calling program. As arrays are first class elements, a function can also return arrays.
IDL distinguishes two types of parameters used in procedures: positional parameters and keywords.
Positional Parameters are identified by their position in the argument list. A calling program may call a procedure with less or equal number of positional parameters.
Keyword parameters are defined by this syntax:
Keyword_name=variable_name
The caller program then supplies a value to Keyword name, which in the procedure may be referenced by variable name.
xyz,key=1. ; in the main program PRO xyz,key=value print,value END will print 1.000
It is common practise to use the same name for the keyword as for the associated variable, so many times you will find something like
PRO test,x,y,z,switch=switch,silent=silent,error=error
Keywords can be abbreviated until uniqueness, switch may be reduced to sw and error to e, if no other keyword begins with these characters. This practise is not recommended because it is confusing. (The associated variable of course can not be abbreviated.
Many times keywords are used as logical switches. In that case they have to be set to logical true which is accomplished by setting them to the value one. An abbreviation for keyword=1 (integer 1) is
/keyword
keyword_set(keyword_name): returns 1 if keyword_name is defined, else 0
n_params(): returns actual number of parameters passed by caller
n_elements(variable): return total number of elements of a variable
size(variable): returns a long integer vector with information
about type and size of the
argument. Its length is equal to the dimension of the argument plus
3. The first element
is the number of dimensions, followed by the number of elements in
each dimension. The
we have a type identifyer and the total size.
The type of variable is returned with the following code values:
0 | Undefined |
1 | Byte |
2 | Integer |
3 | Long integer |
4 | Real |
5 | Double Precision |
6 | Complex |
7 | String |
8 | Structure |
9 | Double Precision Complex |
10 | Pointer |
11 | Object |
While programming you should always consider all cases possible,
not just what you expect to
happen. Consider a function that should return the sign of an variable
and zero if its value is zero.
Naively we may begin with:
function sign1, x if ( x gt 0.) then return,1 else $ if (x lt 0.) then return, -1 else $ return, 0 end
This works well until we try for instance a complex argument. The next
step may be to restrict
the argument type:
function sign2, x res=size(x) nr=n_elements(res) type=res(nr-2) if (type ge 1) and (type le 5) then BEGIN if ( x gt 0.) then return,1 else $ if (x lt 0.) then return, -1 else $ return, 0 ENDIF ELSE BEGIN print,' Can not handle data type: ', type return, 0 ENDELSE end
That works allright for all scalar values but fails when the argument is
a vector or array. We might
further restrict our program to scalars, or extend it:
function sign3, x res=size(x) nr=n_elements(res) type=res(nr-2) if (type ge 1) and (type le 5) then BEGIN nz=where(x ne 0) x(nz)=x(nz)/abs(x(nz)) return,fix(x) ENDIF ELSE BEGIN print,' Can not handle data type: ', type return, 0 ENDELSE end Exercise: Write a function or subroutine that solves the quadratic equation eg. a*x^2 + b*x + c = 0
By default, IDL stops program execution if an error occurs and remains in that program unit. This is useful while testing programs. To control this behavior, use the
ON_ERROR, <N>
command at the beginning of a procedure. If N=0 in case of errors IDL will stop and stay where it is (default). With N=1 it will return to main level, with N=2 to the caller program level.
I/O Errors can be handled by a command
ON_IOERROR, label
which will branch to the position of label: in the procedure, avoiding program termination.
To signal errors you may use the
MESSAGE, text
command which prints the supplied text on the terminal, together with the procedure name that issued the command.
Mathematical errors can be checked by the FINITE and CHECK_MATH commands. See the manual for detail.
Procedures are compiled either automatically when invoked the first time, or manually by the
.RUN filename or
.RNEW filename
executive commands. .RNEW works exactly like .RUN but also erases all main level variables before compiling. Both are executive commands, hence can not be used as program statements.
Note that a procedure always has to be recompiled when source code changes have been made.
IDL maintains a set of common system variables which are read- and/or writable from any program segment. These variables begin with an exclamation mark "!".
Some useful variable are:
Variable Name | Type | Contents |
---|---|---|
!path | String | all directories that IDL scans for programs |
!pi/!dpi | Real/Double | Pi in single and double precision |
!prompt | String | IDL prompt (user settable |
!err | Long | Code number of last encountered error |
!error | Long | Code number of last error message |
!err_string | String | last error message |
!version | Structure | Information about Operating System and IDL version |
!d | Structure | Device specific information |
!p | Structure | Plot specific information |
!x,!y,!z | Structures | Plot axis information |
for instance
print,!d.n_colors
will print you the number of colors available to IDL.
I/O in IDL is very 'Fortran-alike', you open a file and associate a logical unit number with it, perform read or write and print operations on it, then close it.
To open a file read only, use openr, to open a new file for write and and read, use openw, if the file already exists openu.
openr, unit_number, file_name
openw, unit_number, file_name
openu, unit_number, file_name
where file_name is the name of the file that already exists or that you want to create, and unit_number is an integer value you associate with the file for later read/write statements. Useful keywords
append | append data at the end of file |
delete | delete file automatically when closing |
get_lun | let IDL choose the unit_number |
f77_unformatted | (unix only) to maintain compatibility with Fortran files. |
All formated I/0 to files is performed by a corresponding pair of routines
readf, unit_number, variable_1, variable_2, ....
printf, unit_number, variable_1, variable_2, ....
both routines accept a keyword format=string, where string is are formatting commands exactly like in the Fortran Format command. Parenthesis have to be included.
IDL can also read from a string variable, exactly as if it were a line in a formatted input file
reads, string, variable_1, variable_2, ...
To read/print to the screen, use the analogous commands
read, variable_1, variable_2, ...
print, variable_1, variable_2, ...
IDL> read,num,prompt='Gimme a number >' Gimme a number >5 IDL> print, num, format='(i4.4)' 0005
Unformatted I/0 is performed by these routines:
readu, unit_number, variable_1, variable_2, ...
writeu, unit_number, variable_1, variable_2, ...
Usage of logical unit numbers:
-2 | standard err |
-1 | standard out |
0 | standard in |
1-99 | user usage |
100-128 | reserved for get_lun |
Instead of assigning a fixed logical unit number, the smarter way is let IDL decide on it. There are two routines that provide and clear logical unit numbers.
get_lun,number openr,number,<filename> ... close,number free_lun,number
IDL provides efficient random access to files by means of a so called associated variable that maps a data structure onto a file already open to I/O. Calling sequence:
result=assoc(unit,array_structure,offset)
Imagine you have a FITS file 'test.fits' that contains a 1024x1024 image with 32 bit integers where you want to see the 512th line. First open the file for reading
openr, lun, 'test.fits',/get_lun
Now you map lines of 1024 long integers onto the file structure and skip the header block, extract and plot the 512th line:
line=assoc( lun, lonarr(1024), 2880) plot, line(511)
There lots of higher level I/O routines that read and/or write common data formats. A selection of readable (many also writable) formats: