2. Functions and subroutines

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.

2.1. Parameters

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

2.1.1. Passing keywords

Passing undefined keywords to a routine would cause an error. However, it is often required to 'pass through' keywords to lower level procedures. This can be established by adding a formal keyword parameter _EXTRA to the parameter list.

2.1.2. Inquiery routines

A subroutine often needs to inquire about certain conditions or variables with which it is invoked. Useful intrinsic functions for that are:

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

 

2.1.3. Error handling

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.

2.1.4. Compiling Procedures

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.

2.2. System variables

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.

2.3. Input and Output

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

2.3.1 Associated variables

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)

2.3.2 Higher level I/O routines

There lots of higher level I/O routines that read and/or write common data formats. A selection of readable (many also writable) formats: