8 The User Code Interface

 8.1 The “LOGICAL USER” Function
 8.2 Building your own binary
 8.3 Debugging your code
 8.4 Local documentation
 8.5 Data access

If DIPSO can’t or won’t do something reasonably straightforward (e.g. a simple functional operation on a single spectrum) — or even, if you’re ambitious, something quite complicated — that you require of it, then you can avail yourself of the user interface. This consists of a logical function, USER, which gives simple and straightforward access to the contents of the ‘current’ arrays; and two user-callable subroutines, UPUSH and GETSTK, which allow you to respectively get data from and put data onto the stack. (These two routines are described at the end of this section.) You are also free to use any GKS, or SGS routines you may need.

If you want to do something that requires opening new input streams, it is recommended that you use streams 23-29 inclusive. DIPSO closes most i/o streams as soon as it has finished with them, but it always has stream 22 open, and you are strongly discouraged from using streams 5 and 6 for anything other than standard input and output (i.e. the designated device; normally the terminal when DIPSO is used interactively). (The reason that stream 22 is always open is that GKS sends its error messages to this stream. Such messages are more likely to annoy than enlighten DIPSO users!

8.1 The “LOGICAL USER” Function

To use the interface you will need to write a subprogram called USER which should follow the example given in $DIPSODIR/user.f.

The example subroutine contains almost all the additional documentation needed to understand how to ‘do your own thing’. This documentation may seem a bit technical, but don’t be put off by that (consult one of your local computer devotees if in doubt); the interface is really very easy to use. As a bare minimum, you could copy the example subprogram and just add in your own IF block:

  ELSE IF( CMD .EQ. ’<your command>’ ) THEN
     <carry out operations>

The USER function delivers lots of variables for you to play with, but the essential contents of the argument list are:

Sometimes you will want to treat PARAMS as a character string (for example, it might be a filename); but more often you will want to read numbers from it. You can do this in exactly the same way as DIPSO does by calling the routine DECODE:

        CALL DECODE( CMD, PARAMS, NP1, NP2, VALUES, PROMPTS, OK )

where:

CMD - character
The command name, passed to USER by DIPSO.
PARAMS - character
Associated parameters, passed as a string by DIPSO.
NP1 - integer
The minimum number of parameters (between 0 and 10).
NP2 - integer
The maximum number (greater than or equal NP1).
VALUES - real
The array into which real values, decoded from PARAMS, are passed.
PROMPTS - character
The NP1 prompts for mandatory parameters.
OK - logical
A success/failure switch.

The ‘PROMPTS’must be left justified, separated by blanks, and terminate with a blank. (Have a look at the $DIPSODIR/user.f code for examples of how to use DECODE.

8.2 Building your own binary

Having written your USER code, you can build your own dipso binary.

You must first set the environment variable SYSTEM appropriately for your system: alpha_OSF1 for AXP OSF/1, or sun4_Solaris for Sparc Solaris 2.x.

Then, you can compile and link a new binary by typing:

  % dipsosetup
  % my_dipso

If you wish to link in subroutines contained in object modules sub1.o, sub2.o etc, you should define an environment variable MY_OBJECTS specifying these object files:

  % setenv MY_OBJECTS "sub1.o sub2.o sub3.o"
  % dipsosetup
  % my_dipso

This leaves you with a personal copy of dipso in the directory you’re working in. You should then set up an alias to run this version in preference to the system version:

  % alias dipso <where ever>/dipso

8.3 Debugging your code

(This section can be skipped by people who never make programming errors!)

DIPSO is equipped with a condition handler to prevent crashes. DIPSO shouldn’t give a crash in the normal run of things (if you get one, please report it, giving fullest details possible — preferably a macro file which always results in the crash), but it may well do so in user-supplied code. In this case, you will normally want to disable the condition handler in order to get the system handler, which may tell you where the crash occurred. To turn the handler off, the HANDLER command can be invoked (use HANDLER 0). On some flavours of UNIX, this may result in the program appearing to freeze after a crash. If this happens, pressing control-C should return you normally to a system prompt.

8.4 Local documentation

If your site has a ‘user-enhanced’ version of DIPSO that is used by several people, then it might be convenient to put the executable into a local public directory. In this case you should persuade your node manager to create such a directory, with appropriate protections, and give it the environment variable LDIPSODIR. The local version can then be run, of course, as $LDIPSODIR/dipso.

In response to the COMM command, DIPSO first looks for a file $LDIPSODIR/command.hlp, which will be used if found. It will then go on to look for a file $DIPSODIR/comand.hlp which it will also use if found.

So it’s possible to put an appropriately modified version of command.hlp into LDIPSODIR to keep users informed of local additions to available commands. The local version of command.lis should contain only the extra local commands, not the standard commands, and should be formatted like the standard version.

Note, in previous versions of DIPSO, command information was stored in a file called command.lis which had a different format to command.hlp files and was processed differently. It is recommended that any local command.lis files still existing be converted into command.hlp format (see $DIPSODIR/comand.hlp for a description of the format). If DIPSO finds a local old-style command.lis file it will simply display its contents (as in previous versions of DIPSO) in preference to using any new-style command.hlp files. Note, however, that the extended functionality of the COMM command (i.e. word searches, command descriptions and classification) will not be available.

It’s possible to give HELP for local commands, too, although it can only be accessed as plain text (see USEHTX). The HELP command in plain text mode first runs through $DIPSODIR/help.lis but if it doesn’t find a command name there, it will try to look for an $LDIPSODIR/lhelp.lis, and search that for help information. This file should match the format of $DIPSODIR/help.lis, but need contain information only on local commands.

The first thing that DIPSO does is look for a file called $LDIPSODIR/updates.lis, and print out anything it contains. So if you’ve made changes, you can announce them to your local community through this mechanism.

8.5 Data access

If you want to do complex operations involving several data sets, you may well want to access data on the DIPSO stack. Well, you can; but first, you’ll need to understand a bit more about how DIPSO stores data.

8.5.1 More on data storage

A DIPSO data set contains a variety of information. First of all, there is a brief header string [CHARACTER*80 TITLE]. Then, of course, there are the X and Y data arrays [X(MAXPT), Y(MAXPT), MAXPT=64000], which contain the NPOINT pairs of data points. Now, in order to know where in the data any gaps occur, DIPSO maintains a separate ‘break’ array [BREAKS(MAXBRK), MAXBRK=1000] which contains the NBRK break points associated with the data set. A break point is the index, in the X and Y arrays, of the last point before a gap in the data set. Thus if there are 200 points in the data set, and there are breaks between the 7th and 8th, and 123rd and 124th, data points, then BREAKS(1)=7, BREAKS(2)=123, and BREAKS(NBRK)=200, where NBRK is 3. Note that the last point in a data set is always a break point, so that NBRK is always 1 or greater.

To allow compatibility with some other programs (notably IUEDR), DIPSO assumes that a specific Y value (zero by default) actually flags a gap in the data, for some i/o commands (e.g. SP0RD/WR). This will often be invisible to the user, but you ought to keep it in mind. Note also that if DIPSO reads in a data set where a gap is padded out with a whole string of zeros (e.g. from IUEDR), then it throws away all but a couple of them, to save space. (Try SP0RDing a hi-res IUEDR spectrum, then SP0WRing it; the output is much smaller than the input). This behaviour does not apply to the READ, WRITE, SAVE and RESTORE.

When using NDFs, there is another Y value which is also used (by all commands which access NDFs) to flag gaps in the data. This value is the standard Starlink “bad” value which is used to flag invalid or missing data in many other Starlink packages. Its value is -1.7014117E+38 (software generates these “bad” values automatically... you’re not expected to type them in!).

Finally, although DIPSO will plot general X,Y arrays, several of the applications commands expect and require data that have Angstrom or km/s as the X unit (e.g. EW). A variable, WORV (which means “Wavelength OR Velocity”), is used to flag data in which the “X” unit is km/s; if this is the case, then WORV=λ/c, where λ is the rest wavelength to which the velocities are referenced (in Angstroms) and c is the speed of light (km/s). Otherwise, WORV=1.0. (You’ll just have to think carefully about what you’re doing if your data are in frequency units, I’m afraid — WORV=1.0 will generally be associated with your data.)

8.5.2 Creating NDFs in your own programs

To output data from other programs in a form suitable for inputting to DIPSO with the (recommended) READ command requires the following minimal code (with appropriate values and names for all variables):

  
  *  Global Constants:
          INCLUDE ’SAE_PAR’         ! Include standard starlink constants
                                    ! such as SAI__OK.
  
  *  Local Variables:
          CHARACTER  COMM           ! This should be WRITE or SP0WR and
                                    ! causes the corresponding command to
                                    ! be simulated.
          CHARACTER  NDFNAM         ! Name of output NDF structure. NB, don’t
                                    ! include a file type!!
          INTEGER    NPOINT         ! Number of points in XV and YV.
          REAL       XV( NPOINT )   ! X axis values.
          REAL       YV( NPOINT )   ! Y axis values.
          CHARACTER  XLAB           ! X axis label.
          CHARACTER  YLAB           ! Y axis label.
          CHARACTER  TITLE          ! NDF title.
          INTEGER    NBRK           ! Number of points in ‘breaks‘ array.
          INTEGER    BREAKS( NBRK ) ! Breaks array.
          REAL       WORV           ! Wavelength of velocity parameter.
          INTEGER    STATUS         ! Should be SAI__OK on entry. SAI__OK on
                                    ! exit if successful.
  
  *  Create the output NDF.
          CALL WRITE_NDF ( COMM, NDFNAM, NPOINT , XV , YV , XLAB, YLAB,
       :                   TITLE , NBRK , BREAKS , WORV , STATUS )
  
  

Such programs then need to be linked with the DIPSO object libraries, and the NDF subroutine library (see SUN/33). To create a program called fred which uses WRITE_NDF to create an output NDF, do the following:

  % star_dev
  % dipsosetup
  % f77 -o fred fred.f -L$DIPSODIR -ldipsot -L/star/lib ‘ndf_link‘
  % star_dev remove

As well as being readable by DIPSO such output data sets will also be automatically readable by all the standard STARLINK packages on any of the supported operating systems.

8.5.3 Getting data from the stack

So, now you know what’s in a DIPSO data set; and thus, you have a good idea of the information on the stack. DIPSO lets you take copies of stack data using calls to the subroutine GETSTK:

        CALL GETSTK(INDEX, NPOINT, XV, YV, NBRK, BREAKS, TITLE,
       :            WORV, OK)

where:

INDEX - integer
The stack entry you want to access.
NPOINT - integer
On calling, the size of the arrays into which the XV and YV data are to be loaded; and on exit is the number of elements of the arrays which are occupied (i.e. the number of points).
XV - real
A user-supplied array, which contains the X values of the STACK entry on return. (It is your responsibility to ensure that the array is big enough to hold all the data from the STACK entry.)
YV - real
A user-supplied array to hold Y values on return.
NBRK - integer
On entry, NBRK is the size of the BREAKS array. On return, NBRKS contains the number of ’break points’ in the data set. (Again, you must ensure that enough space is available.)
BREAKS - integer
A user-supplied array of length NBRK, to hold the indexes of ‘break points’ in the XV array.
TITLE - character
The title associated with the data set.
WORV - real
Wavelength or Velocity.
OK - logical
Success/failure flag. OK = .FALSE. if the call to GETSTK is identified as unsuccessful.
8.5.4 Pushing data onto the stack

You can also push data onto the stack:

        CALL UPUSH( ASZE, XV, YV, NPOINT, BSZE, BREAKS, NBRK, TITLE,
       :            WORV, OK)

The arguments are the same as for GETSTK, except that ASZE (integer) is the size of your arrays holding the X and Y values, and BSZE (integer) is the size of your BREAKS array.

To encourage you to look on DIPSO as a tool with which you can interface your own software, it is worth noting that the ELF package (described below), all the Fourier analysis software, the IS routines, and the NEBCONT facility were added to DIPSO with very little more than the basic interface described above.

If you do write some software that you think may be of general interest, please contact Ian Howarth (idh@star.ucl.ac.uk); it may be possible to incorporate it into the public version of DIPSO.