Department of Physics and Astronomy


IDL Tutorial: Scripts, Procedures, and Functions

Of course you won't get very far with IDL if you have to type everything one line at a time. It would also help to store lists of commands that you find yourself repeating a lot. There are three kinds of command lists that you can use:

  1. Scripts. These are simple lists of commands that do not take any input. For example, you could copy all of the example commands from the preceding tutorials and paste them into a new text document, say, example.pro. You can run that text document as a script, and all of the commands will execute in sequence.

  2. Procedures. These are more sophisticated programs that take input as numerical or variable arguments. print is an example of a procedure.

  3. Functions. These are essentially identical to procedures, except that they return a value as if the procedure were itself a variable. sin is an example of a function.

Scripts

Scripts are really nothing more sophisticated than a list of IDL commands in a file.

Copy this file to a work space, say, into your public space (right click, save as).

examplescript.pro

Have a look at the script in a text editor (textpad works well here; I prefer emacs). If there is a command or a command option that you don't understand, try using the IDL online help to figure it out.

Next, in IDL, change the default working directory to the directory where you placed the script:

cd, 'U:\private'

or whatever is appropriate. Notice that cd is a procedure to change the current working directory. It takes a single argument, a text string giving the name of the working directory. (By the way, cut and paste can save a lot of headaches in directory changes.)

Execute the script:

@examplescript

Hopefully, you experienced a little magic here.

Now, copy the script to a new file, say, example2.pro. Change to script to do something new. Replace sin with cos, plot y vs. z, whatever floats your boat. Then

@example2

Not too shabby, eh?

When Things Go Horribly Wrong

Before we delve deeper into programming, please recognize that you will eventually write a program that's going to crash. I even put an intentional crash in the examples below. Perhaps there's some typo in your code, or you accidentally divide by zero, or who knows. When a procedure or function crashes in IDL, the command prompt will return, but IDL will be left in the middle of the program where the crash occurred.

Why this is good: You can display variables deep in the code to see what might have gone wrong.

Why this is bad: If you are buried deep in some sub-procedure or sub-function of the parent program, then you may not have access to the original variables that you sent to the program. For example, suppose you wrote a program that enhances the contrast of a digital image. That program may need to call upon a homebrewed function, which crashes owing to some bug. If you try to run the code again, it will fail because it no longer recognizes the variable that stored the original image!

You can think of programs and functions as geological layers in the soil. You are at the top, the pinnacle of evolution, but you are sending data down into the procedure strata to be processed. If your procedure calls another procedure, or function, that called procedure occupies the next deeper layer, and so on. But suppose your data are sent 5 layers deep (a procedure calls a procedure calls a procedure...) and then crashes. IDL will print some useful debugging info, such as the name of the function that crashed, and the line number in the text file, but you are still stuck 5 layers removed from your original procedure call.

To get back to the top level and restore your variables, simply enter,

retall

Retall is a sort of ejection seat in the event of a crash. Then, fix your code and recompile it:

.run examplecode

The command .run is the simplest way to compile an IDL procedure or function after it has been modified. (.run will also, by default, take you to the surface, but you may not want to recompile right away.)

Functions

We've already applied a simple example of a function call:

y = sin(x)

In this case, IDL applies the function sin to each element of the array x and stores the result in y. sin is a function because it expressly returns a value that can be evaluated directly by another variable.

Let's make our own function. Open a new text file called stepfunction.pro and enter the following commands.

function stepfunction, x, a
; This function returns 0 for x < a and 1 for x >= a
result = 1
if x LT a then result = 0
return, result
end

Notice that return is a necessary command for a function.

Save the file, and then execute it:

.run stepfunction

print, stepfunction(1, 3.)

print, stepfunction(5, 3.)

Do the results make sense?

Now, this function is a good example of poor programming practice. This code can only handle scalar values of x (assuming a scalar value for a). What we'd like to do is pass an array of values x to compare with a scalar a. For example, if you try the following,

x = findgen(10)

plot, x, stepfunction(x, 5.)

the code will crash. Try it, and see if you can interpret the error message.

Now, let's try rewriting the code to be more arrays friendly. There are different ways to do this; see if this example makes sense to you.

function stepfunction, x, a
; comments are separated using semi-colons
n = n_elements(x); size of x array
result = replicate(1., n); make a result array the same size as x
; identify by array index those
; elements where x is less than a
idx = where(x LT a, count)
; If there is at least 1 element that is < a, set the result to 0
if count GT 0 then result[idx] = 0
return, result
end

Re-compile and try,

x = findgen(10)

plot, x, stepfunction(x, 5.), psym=4, yrange=[-0.5,1.5]

Do the results make sense this time? By the way, the code necessarily expects the argument a to be scalar.


Exercise

Write an IDL function to generate the sinc function: sinc(x) = sin(x) / x. Plot it. There had better not be any infinities! Taylor-expand sin(x) for small x to see what I mean.

Procedures

Well, once you've written a couple of functions, all the surprise is lost. The code in a procedure is essentially identical, except that there is no return value. Procedures are also called directly and not as the argument of a variable assignment. For example, plot is a procedure:

plot, x, y

Make a new file called plotagauss.pro, and enter the following commands:

pro plotagauss, center, sigma, X=x, Y=y
; plots a gaussian function with
; center = x location of the peak of the bell curve
; sigma = characteristic width (in x) of the bell curve
; Optionally returns x & y with keyword arguments
x = findgen(1000) / 999; numbers running 0 to 1 in steps of 0.001
x = x * 6 * sigma - 3 * sigma; widen x to range over 6 sigma
x = x + center; center the x range on the bell curve center
arg = ((x – center)/sigma)^2
y = exp(-arg)
plot, x, y
end

Compile, and try

plotagauss, 1.0, 3.0

plotagauss, 2.0, 5.0

Pay attention to what happens to the axes. What we'd really like to do is plot two gaussians to compare. Happily, I allowed for the possibility of keywords. Keywords are optional arguments that can be passed to procedures or functions as additional inputs or to recover outputs. Try the following,

plotagauss, 1.0, 3.0, X=x1, Y=y1

plotagauss, 2.0, 5.0, X=x2, Y=y2

plot, x1, y1

oplot, x2, y2, linestyle=1

See if you can explain what happened here. Notice that oplot takes the keyword linestyle as an input. linestyle = 1 means plot a dotted line, instead of the default solid line.

Further Reading




Back to Index

Next Tutorial


Last modified by Jack Gallimore.