This chapter explains in detail how to use the Module Builder to construct a module. It describes how you:
It also includes some examples of user functions for simple modules.
Advanced topics are marked with **. As an ordinary user, you can usually skip these passages on a first reading.
The user function or subroutine is a program that details what you want the module to do. You can write it yourself or use a program that already exists. The user function file contains the algorithm that actually performs the data transformation. It also contains calls to any API subroutines you are using in the module.
You can write the program in C++, C, or Fortran 77; however, all user functions must export a C-style user interface. Writers of C++ user functions must qualify their routines accordingly. For example, you can qualify a routine called funcName() as
extern "C" funcName()
Note: In the IRIS Explorer environment, all files containing C++ code have the suffix .C.
You can write the user function at any time, but you cannot build the module without it. You can set up other parts of the module, such as the input and output ports and connections, save the module resources, and then pull up the half-finished module and build it later, when the user function is complete. You can also generate a prototype and build the module later.
The IRIS Explorer application programming interface (API) is a set of functions in C and corresponding subroutines in Fortran that give the module writer greater control over module behavior. For example, instead of the Module Builder generating code to connect the user function to the ports, the writer can use the API to do so. The writer can also build more complex modules by using the API to make specific calls to other parts of IRIS Explorer. The API is described fully in the IRIS Explorer Reference Pages.
Note: If your user function is written in C++, use the C API. Since C++ is a superset of C, the C API can be called directly by C++.
If you try to build a geometry module in Fortran, your only access to the Geometry library is through the IRIS Explorer API subroutines. If you want to use the Open Inventor geometry library directly you must build geometry modules in C or C++, you will also need a Developer's Licence for Open Inventor on your platform.
If you are writing a module, you can use cxScriptCommand to issue scripting commands from within the module. When you call cxScriptCommand, a message transmission and several context switches result, so performance is improved if you can bundle expressions in a single string.
For more information on syntax of cxScriptCommand and the Skm language, refer to Appendix B in the IRIS Explorer User's Guide.
You use the Module Builder to create the module resources file and the control panel for a module and to build and install the module so that it can be launched in the Map Editor.
You may need to run the Module Builder from the directory in which you want the completed module to reside; otherwise, the module may not build. See Building and Installing Modules in this Chapter for more information. Type the mbuilder command in a shell window.
The Module Builder starts up, and its main window appears on the screen. The menu bar at the top of the window provides access to various commands, which are briefly outlined below:
Upon startup, the Module Builder's main window
appears on the screen. If it has been started without a filename, the main
window comes up with default_module_name
in the Module Name slot. In this window, you set the variables for the
module resources file, a binary file called <modulename>.mres,
which contains all the information required to build the module.
The .mres file template is created as you apply the information from
each of the Module Builder windows (see Moving Around
the Windows in Chapter 2) and is saved when you first select Save
Resources from the File menu.
To use the Module Builder to look at an existing module resources file,
use the Open command on its File menu. Alternatively, specify
the name of the file on the command line; for example, to look at the structure
of an existing module such as GenLat, type:
where the environment variable EXPLORERHOME
should be defined
to point to the base of the IRIS Explorer installation directory. The Module
Builder opens with the information pertinent to the module displayed in its
windows.
Five windows can be opened from the main Module Builder window, but
only one besides the main window can be active at a time. If you try to
open more than one, a dialogue box appears and tells you which window is
already open. The active window may be buried under other windows or it
may be iconified. To re-display the open window, click on the corresponding
button in the main Module Builder window.
To select a text type-in slot in any of the Module Builder windows,
click on the slot. The active slot is outlined in black and has a cursor
in it. You can type into the active slot.
When you have entered information in a window, you can use one of the
three buttons at the bottom of the window:
Changes are not incorporated into the module template until you click
on Apply or OK.
To save the template as a modulename.mres file, you must use Save
Resources on the File menu. You should do this often, to avoid losing
your work.
Filling out the Module Builder main window is equivalent to setting
the variables required by the language compiler and link editor.
The modulename can be:
opens the Module Builder with LatToGeom. As you move through
the Module Builder windows, you have access to the module's internal structure,
called the module resources.
If you want to look at a different module resource file or to create another
new module, use the File menu in the Module Builder main window. To
select another modulename.mres file,
click Open
and select the file from the file browser that appears.
To create a new module entirely from scratch, select New.
Enter the name of a module in the Module Name slot (see Figure 2-1). To enter or change the name of a module
in the slot, click the text slot to highlight it and type a new name.
Each module name must begin with an alphanumeric character. Since they
are UNIX
files, you should follow the appropriate file-naming conventions.
If you are modifying an existing module to create a new one, you should
save the modified resource file with a new module name and leave the original
modulename.mres files intact.
Note: Because you cannot change the source code of an existing
IRIS Explorer module, the modifications you can make to these modules are
very slight; however, you can modify your own modules endlessly, if you
wish.
By default, IRIS Explorer saves newly created modules in the working
directory. The module is saved in the form of a modulename.mres file
when you select:
Enter the name of the file or files that contain your computational, or user,
function in the User Func File text slot. For example, the Gradient3D
module has one user function file, called Gradient3D.f.
Note: Your module may use an alternate executable, that is, a user
function connected with another module (see Building Modules
in Chapter 2).
If so, don't enter the name of the user function in the User Func File
slot, since it will be entered on the build
options control panel.
If the user function is large and complex, you can break it down into
smaller sub-functions, each of which is in its own file. In the User Func
File slot, list two or more files with a space between each name.
You can mix languages (C++, C, and Fortran), source files (filename.C,
filename.c, or filename.f), and object files (filename.o)
in this slot. For example, Render, which is a very complex module,
uses render.C, event.C, init.C, SoSceneViewer.C,
SvManipList.C, cxGeoObject.C, and several other files.
The module builder uses standard make rules to determine file
dependencies and to build the necessary object files from C, C++, and Fortran
source files.
You need not supply a Makefile unless you have a specific reason for
doing so; for example, if you use library (.a) files in the User Func
File slot you need to supply your own Makefile in the User Makefile
slot (see User Makefiles in this Chapter).
In IRIS Explorer, *.inc files have no
cpp directives in them at all. To use #include syntax,
name your source file *.fi, and the IRIS Explorer Makefiles
will filter it for you. If your Fortran compiler naturally supports cpp,
you can still use *.f. To use include statement syntax,
name your source file *.f.
Use of certain data types requires that you explicitly include certain
files. If you use cxLattice, you may need to include Typedefs.inc.
If you use cxPyramid, you must include cxLattice.inc.
If you use DataTypes.inc, however, it includes Typedefs.inc
explicitly.
The Module Builder automatically passes the include directories such
as $EXPLORERHOME/include
to the compiler when compiling the module's source code. However, you may want
to include directories or files from other places for a particular module.
Use the Includes slot to do this.
Enter the name of each included file as you would list it in a compile
command. Use the form -I<directory>; for example,
-I../myInclude.
Thus, to build the AnimateCamera module required the following include
command:
which appears in the Module Builder Includes slot for the module (see
Figure 2-1); here, CXSYSINCLUDE is an
environment variable, set when the module was built. This contains the name of a
directory where the required include files for the module are to be found.
If you include a system-defined variable by itself, it takes the form
${VARIABLE}; for example, ${GEOMETRYINCS}. You can also
use the -D option for variables; for example, the Render module
uses -DEXPLORER. For more information on these options, refer
to the man page for the UNIX cc(1) command.
Some modules, such as those handling graphics, geometry, or mathematical
procedures, and those written in Fortran, must be linked to related libraries.
IRIS Explorer automatically links certain default libraries with the module
for Fortran and geometry modules if you have:
The system variables ${FORTRANLIBS} and ${GEOMETRYLIBS}
include any default libraries that are machine-dependent.
You may wish to:
Use the Libraries slot to do this.
Enter the name of each library as you would list it in a link command.
Use the form For more information on the -l and -L options, refer to the
man page for the UNIX ld(1) command.
** If you are creating a module with a user-defined data type (UDT) on an
input or output port, IRIS Explorer links the associated libraries with the
module; however, if you use the UDT inside a module that has no UDT port, to
create a modified lattice, for example, you must specify the associated library
in the Libraries slot. User-defined data types are described in Chapter 8 - Creating User-defined Data
Types.
In general, the Module Builder uses standard make rules to determine the
relationships among individual module files and to determine which files to
recompile when a change is made to some aspect of the module.
Note: You can create and use your own Makefile or
Imakefile if:
Give your Makefiles names such as MyMakefile or
Makefile.user. The names Makefile and Imakefile are
reserved for use by the system and you should avoid them.
Note: The Imakefile extends the functionality of
make by providing include facilities. It deals with X Window(tm)
features.
For details on creating your own Makefiles, refer to Using Makefiles in Appendix A.
The module ports allow you to get data into and out of the module. They
constitute the interface between the module itself and other modules, the
external interface. You can
define up to 40 input or output ports for a module, each of which must
have a unique name. If the Module builder detects a duplicate name, it
renames the second port and issues a message.
Note: IRIS Explorer automatically adds new syncronisation ports called
Fire and Firing Done to all modules when they are placed in the
Map Editor (see Using the Synchronization
Ports in the IRIS Explorer User's
Guide). In addition, if the module is a loop controller, it also
adds a Loop Ended synchronisation port. To prevent confusion, you should
therefore avoid using these names for the ports which you define when you build
the module.
Figure 2-2 shows the Input Ports window for
VectorGen. This module has two ports, Input and Scale
Factor. When you define an input port, you give the Module Builder the
information that it needs to allow legal connections between this port and
output ports on other modules.
Three fields need to be defined: the port name, its data type, and its
status.
The port name goes in the text type-in slot. You can give a port any
name you wish, as long as it is unique within the module. Descriptive names,
such as Color Input, make the module easier to use than, say,
In1. The name appears on the Input Ports menu of the module
control panel in the Map Editor.
The data type on the port provides crucial information about data structures.
It denotes the form in which incoming data must be cast for the port to
accept it. The center column of option menus lists the available data types,
of which you select one.
For more details on IRIS Explorer data types, see Chapter 3 - Using the Lattice Data
Type through Chapter 8 - Creating
User-defined Data Types.
IRIS Explorer has seven built-in data types. These are listed on the data
type pop-up menu shown in Figure 2-2. In
addition, you can create your own data types (see Chapter 8 - Creating User-defined Data
Types) and if any of these have been added, they will also appear on the
data type pop-up menu for both the Input and Output Ports. For example, the
pop-up menu for the Output Ports window in Figure
2-6 shows an additional user-defined data type called tstLattice.
Users can connect only ports with the same data types, except for
cxGeneric (footnote). For example, if
Input has the cxPyramid data type on its port, it can receive
only a connection from a pyramid output port on another module. IRIS Explorer
data types are all pointers to C structures, so they can be linked to Fortran
user subroutines because Fortran can accept variables passed as pointers.
If you select the:
The Map Editor uses the constraint options for type checking when modules
are connected to one another in a map. If the incoming connection passes
a lattice that is incompatible with the options defined on the receiving
port, a warning message appears and the connection is not made.
The status flag menu lets you specify whether
the port is optional or required. If an input port is:
The actual firing sequence of a module is controlled by the firing algorithm.
For more detailed information on the passage of data into and out of ports,
refer to Appendix B - The Firing
Algorithm.
The cxLattice data type is flexible enough to accommodate a variety
of lattice types. You can more narrowly define the kind of data that a single
lattice port can accept by setting options in the Lattice Constraints window
(see Figure 2-3).
You can set any restrictions that make sense for your lattice port.
This applies to both input and output ports which accept or produce lattices.
For example, Figure 2-3 shows the lattice
definition for a port called Input, which is expecting a colormap. The
colormap lattice is a 1-D lattice with three (RGB) or four (RGBA) floating point
data variables per node. It has uniform coordinates and it must contain both
data and coordinate values.
Table 2-1 shows how these specification are
applied in the Lattice Constraints window.
Each field shown in the Lattice Constraints window defines a part of
the lattice data type structure. The limits you set in this window should
be consistent with the way the lattice is defined in the user function.
For more details on the lattice data type, see Chapter 3 - Using the Lattice Data
Type.
Click the radio buttons to activate the options. The light-colored,
concave form indicates that the option is allowed.
In such cases, the Module Builder performs type coercion on the incoming data
to convert it to the primitive type that the user function argument requires
(see Automatic Type Coercion of Arrays).
Note: The lattice primitive data types are C data types. If your user
function is written in Fortran, select the C equivalents of the Fortran data
types the user function expects (see Table 3-1 in
Chapter 3).
** You can use the Data Optional/Coords Optional setting to allow the module
to accept a lattice that has coordinate values but no data values. This lets
you describe the geometry of a shape (such as a sphere or a pyramid mesh) by
using coordinate values only. You can then feed in a lattice containing data
values only that show, for example, the variation in stress levels over the
pyramid surface.
If you are working with lattice data and coordinate arrays, you can
use the automatic type coercion facility. You can write a module's user
function to operate on a single data type, such as a float, but allow the
module to accept any primitive type as input on a specific port. The Module
Builder then generates code to coerce data on that input port from its
native primitive type to the primitive type required by the function argument
to which the port is connected.
The Module Builder applies its coercion rules automatically during port
and argument specification, based on the selections made in the Lattice
Constraints window and the function arguments.
At run-time, the data is converted into the desired type, copied into memory,
then passed to the computational function. The coercion proceeds as a series of
individual coercions. Figure 2-4 shows the
traversal path and the coercion pairs.
The C language process is:
D => F => LI => SI => B, or double to float to long int
to short int to byte.
The Fortran language process is:
double precision => real => integer => integer*2 => character*1.
Out-of-range data is clipped to the acceptable range. Such coercions
as
B => SI => LI => F => D cause no loss of precision.
The following paragraphs describe the coercion process:
Click OK to have your constraints accepted and to close the window.
You are returned to the Input Ports window. Click OK to accept the port
specifications and to return to the Module Builder main window.
You can use the cxPyramid data type in a variety of ways. Some
pyramids are built with every layer specified, and others are constructed in
compressed form, with reference made to a standard structure in a pyramid
dictionary for the compressed layers. The Pyramid Constraints window (see Figure 2-5) lets you specify whether the port can
accept compressed or uncompressed pyramids and also establishes the level at
which compression can take place.
For details on pyramid compression, see Chapter 4 - Using the Pyramid Data Type.
Click on the radio buttons to make your selections.
Click OK to have your constraints accepted and to close the window.
You are returned to the Input Ports window. Click OK to accept the port
specifications and return to the Module Builder main window.
To display the Output Ports window (see Figure
2-6), click the Output Ports button on the Module Builder main window. The
Map Editor uses the output port definition to decide whether a legal connection
can be made between it and a given input port on another module.
The Output Ports window resembles the Input Ports window, except that
it has only two fields, the port name and the data type.
The example in Figure 2-6 shows one output
port with the port name Output. The data type it produces is
cxGeometry, this meaning that the output port must be connected to an
input port that accepts geometry data. For more details on the Geometry data
type, see Chapter 5 - Using the Geometry Data
Type.
If you are creating an output port that produces a lattice, you set
the data type to be cxLattice and the Lattice Constraints window
appears again. Set the constraints for the output lattice in exactly the
same way as you would set those for the input lattice.
The Output Ports window does not require you to set the status flag
because output ports are optional by definition; the module's firing pattern
depends on the state of the input ports, not the output ports. A module
that has the correct connections and data on its input ports will fire
whether or not any output ports are wired.
You can write a smart module, that is, one that can track whether its
outputs are wired and block certain computations if they are not connected.
For example, the Render module has a camera output port for which data
is calculated and output only if the port is wired.
You can write code for a module with two output ports to check which output
is wired and compute the data for only that one.You can then write a hook
function for connect output and call the API function cxFireASAP
from that hook function so that when you wire up the previously unwired output,
the module will fire and send the new data downstream.
For more details on hook functions see Chapter 9 - Advanced Module Writing.
When you define the function arguments, you are specifying the interface
between the module itself, defined here as the Module Control Wrapper (MCW),
and the user function. The Module Builder uses this information to generate
bridging code, which takes the form of the Module Data Wrapper (MDW). This
is the module's internal interface.
You use the Build Options control panel to
specify whether or not you want the Module Builder to write the MDW for you.
** You can take control of the interface and write your own interface
code, in which case you do not need an MDW. If you write your own bridging
code, you need not define the function arguments. Simply leave the
Func Args
and Connections windows blank.
For more details on writing wrapperless modules see Chapter 9 - Advanced Module Writing.
You must enter the function name in the Func Name slot in this window (see Figure 2-7 and Figure 2-8) even if the module has no MDW;
otherwise, the module will not build.
The Module Builder requires you to list each function argument and its
correct type and referencing style so that the MDW can be written correctly. To
display the Function Arguments window (see Figure 2-7), click the Function Args...
button on the Module Builder main window.
The examples in Figure 2-7 and Figure 2-8 show the argument lists for the
VectorGen and AtomicSurfmodules. The fields in the Function Args
window are described below.
The Func Name is the name of the user function or subroutine. It is
repeated at the bottom of the window, along with the complete list of arguments.
The Module Builder uses this field to create a call to the function.
Type the name in the text type-in slot exactly as it appears in the
user function file.
The Language button of the Function Args window allows you to select
the language in which the user function or subroutine is written. The Module
Builder uses this field to determine the calling conventions for the wrapper
code (which is generated in C).
Select C, Fortran, or C++ from the Language pop-up menu.
The Arg Name section of the Function Args window lists the function
argument names, entered in the correct calling sequence. The Module Builder
uses this list to establish the calling sequence of arguments for the function
when it creates the Module Data Wrapper (MDW).
You can enter up to 45 function arguments per function. Each argument
must have a unique name. The Module Builder renames duplicate arguments.
The Type section of the Function Args window lets you select a data
type for each argument variable. These differ for C/C++ and Fortran.
Figure 2-7 shows the data type options menu
for a C function. The menu is the same for C++ functions. Figure 2-8 shows the options for a Fortran
subroutine.
Select one option from the menu for each argument.
Table 2-2 shows the relationship between the C
and Fortran primitive data types that IRIS Explorer recognizes.
**
In the C and C++ options menu, IRIS Explorer data types or subsidiary types
(cx<DatatypeName>) are followed by an asterisk (*). They are
automatically passed by reference because they are arrays written in C. In
Fortran, you refer to the IRIS Explorer data types by using integers that
contain a pointer to the C data structure. The C structures that are referenced
can be manipulated through the Fortran API, but not directly in the syntax of
the language. The pointers are uniformly treated as opaque handles that
are passed around in integer variables.
The References section of the Function Args window lets you set the method
whereby the argument variable is passed. Figure 2-7 shows the data type options menu for
a C function. The menu is the same for C++ functions. Figure 2-8 shows the data type options menu for
a Fortran function.
The choices are by value or reference in C or C++, by reference only in
Fortran. The reference menus are shown in Figure 2-9, and described in more detail in Table 2-3 which shows the choices for C, C++,
and Fortran.
Note: The term references is used here in the C sense, rather
than the C++ sense, where it has a slightly different meaning.
The choices in the Return Val? section of the Function Args window indicate
whether the user function or subroutine returns a value. If it does, you must
select a type and reference as you did for each argument. The return value can
be used when you set up connections from the function arguments to the output
ports (see Connecting Arguments to Ports below).
When you have entered and defined all the function arguments in their
correct calling sequence, click OK to save the argument list and close
the window.
The user function arguments, once defined, must be associated with the
input and output ports so that incoming data goes to the correct function
arguments for processing before being passed in turn to an output port.
To display the Connections window, click on the Connections
button on the Module Builder main window. The Connections window displays
graphically the links between inputs, function arguments, and outputs.
When you first create a module, no connection wires are shown. The wires
appear after you have used the mouse to make a link between an argument
and a port.
Figure 2-10 shows the connections between the
function arguments for VectorGen and its ports. There are two input
ports, Input and Scale Factor, and one output port,
Output. The function arguments are listed in the center column. The open
Port menu in the center of the window is the Connections menu for Scale
Factor, which passes the parameter data type.
There are also three pseudo input ports listed under Input
Ports. These are described in Using Pseudo Input
Ports.
Each port has a Connections menu that gives its data type structure and lists
the constituent elements of the data type. The number of dots in front of each
element name indicates how many levels below the top-level data structure you
have penetrated.
For example, in Figure 2-10 it is clear from
the menu that a link has been made between the Value element in the Parameter
structure and the Scale Factor function argument. This means that any
data value passed from an upstream module to the Scale Factor input port
of VectorGen is sent in turn to the Scale Factor function
argument. The Value element has one dot in front of it; it is one level below
the Parameter structure and on a par with the Type element.
Lattice, pyramid, and pick data types have several substructures that can be
wired to or from function arguments. Figure 2-11
shows the Connections window for AtomicSurf, which has an input port,
Molecule, that accepts the pyramid data type. Part of the Connections
menu for this port is shown. As you can see, Pyramid Structure, which includes
the entire contents of the data type, is connected to the function argument
ipyr.
The other items on the menu are the substructures of the pyramid data type
(more accurately, of the base lattice of the pyramid. For more on the pyramid
structure, see Chapter 4 - Using the
Pyramid Data Type). They include:
You can write function arguments that accept data from any one of these
substructures if you wish.
You can use a pointer to the C structure to pass a complete IRIS Explorer
data type directly to a function argument if the user function is written
in Fortran or the function argument is broken into smaller elements of
the data type.
The physical mechanism of wiring ports and functions together and
disconnecting them again is exactly the same as that used in the Map
Editor. Click on an input port with the right mouse button, select the element
to be wired, and then click on the function argument (or output port). The
Connections menu for function arguments usually has only one item, Select
(unless it is an array, or if it has a connection
from a pseudo input port). To break a connection,
simply reverse the process.
The --> icons in the Connections menus show which elements of an
input or output port are associated with each other or with function arguments.
You can make only one connection to a given function argument from an input
port, and only one connection from a given function argument to an output port;
however, you can make several connections from an input port or to an output
port. For example, the Input input port in Figure 2-10 has four connections, one to each of
four function arguments.
All function arguments must have a connection to either an input or
an output port, but all ports do not necessarily have to be connected.
Figure 2-12 shows how connection options are
highlighted in the Connections window.
There are two kinds of highlighting:
Unavailable items are grayed out (in Figure
2-12, these are the remaining input ports).
This color coding helps you to see whether the output port will receive
all the data it needs to construct a complete data type.
In many cases, you can connect the top-level data structure on the input port
directly to the top-level data structure on the output port in order to pass
default values. For example, in a module that accepts a lattice on an input port
and outputs a lattice on an output port, you can connect the cxLattice
structure at the top of the Input Port menu to the cxLattice structure
at the top of the Output Port menu.
In this way, all data that is not affected by a function argument will be
passed by default from the input port to the output port. The members of
cxLattice that were operated on by function arguments will acquire new
data that is passed in turn to the output port to replace the old data. See, for
example, the Connections window for the ChannelSelector module in Figure 1-10, where most of the parts of the
input lattice are passed directly to the output lattice, while nDataVar
and the data part of the output lattice are set explicitly via the function
arguments.
At the bottom of the Input Ports list are the three so-called pseudo
input ports, <<Storage>>, <<Constant Value>>, and
<<Extern>> (see Figure 2-13). They
cannot be wired directly to output ports, unlike the regular input ports. In
most circumstances you would not need to use the facilities of pseudo-ports, but
they can be particularly useful if you do not wish to re-write or are unable to
change your existing source code. For example, this would be the case if your
existing source code has function parameters that have constant values in the
context of your module. You would set these values using the <<Constant
Value>> pseudo-port.
You can connect the pseudo-ports to function arguments to do these things:
Once you have wired the <<Storage>> or <<Constant
Value>> ports to a function argument, the argument's Connection menu
acquires a second item. To set the argument, click on it after the connection
has been made.
The second item, Set Storage or Set Constant Value, when selected, invokes
the Set argument value window. Figure 2-13
shows the connection between the <<Constant Value>> input port and
the ilat function argument in AtomicSurf, the ilat Connections menu, and
the value set (NULL, which is the default). See Allocating Storage Space for more information about
temporary storage.
You can enter any legal C expression in the text slot, including references
to library or user-supplied routines that can be resolved at link time.
It is a good idea to comment on your entries for future reference.
<<Extern>> can be wired to only a function argument whose
variable name refers to the name of another function or subroutine. This
function must be in a module source file and referenced in the User Function
File slot in the Module Builder main window (see Figure 2-1).
You can use <<Extern>>:
Here is an example of how to use <<Extern>>. Suppose you
have a function named calcDerivative and
you want the user function to use this derivative calculating function
in its global optimization calculation. You would need to:
You can use the <<Storage>> pseudo input port to allocate
temporary storage space which may be used inside the user function. The
space can be used as follows:
Two API routines are useful for computing the size of arrays for wiring to
the storage allocator (see the IRIS
Explorer Reference Pages for details):
After the user function has been invoked, the allocated storage is freed.
The Module Data Wrapper automatically frees working space that has been
held temporarily by the user function. The Module Builder uses reference
counting to maintain temporaries assigned to output ports as part of the
output port list.
You must free any temporaries that you create inside the user function.
These are the basic rules to follow when connecting ports and arguments:
At the bottom of each Input Port menu is the Data Changed item (see Figure 2-10). You can use it to record whether the
module received new data on that particular port during the most recent
firing. Depending on the answer, the user function may then act to collect new
data from the port, or ignore the old data. Data Changed returns a long integer
with the value 0 (to indicate old data) or 1 (to indicate that new data has
arrived on the port).
You can wire Data Changed to any scalar function argument.
A function argument that is referenced as an array has a Copy
button in the Connections window
(see Figure 2-14). Depending on whether the
function is to overwrite the contents of the array, this allows you to choose
between (a) passing a pointer to the array itself and (b) making a
copy of the array and passing a pointer to the copy.
The Copy button appears at the left side of the array function argument. When
you click a Copy button, it is lit and the word Copy is displayed.
This copy facility is important if you intend to modify the contents
of an argument whose original values come from an input port. Since data
from an input port resides in shared memory, it cannot be written into
without polluting the data space of other modules and causing a module
in the map to fail.
When the Copy button is ON,
it tells the Module Builder to make a working copy of the array so that you can
modify it. This is useful even if you intend to use it only as a temporary
working space, without writing it to an output port.
For example, in a module that manipulates the grid data of a lattice without
changing the coordinates, data will be overwritten; therefore,
the Copy button should be turned ON.
If not, a pointer to the array is passed to the user function, which is usually
more inefficient.
Scalars are always copied before being passed to the user function.
You can use the Prototypes menu in the Module Builder main window (see
Figure 2-15) to create a prototype user function
and a help file for your new module.
The Module Builder will create a user function prototype for you if
you have not already written it, or do not have existing code for it. You
must already have named the function, selected its language and laid out
the calling sequence of the function arguments in the
Func Args window.
The Module Builder uses this information to write a stub function that
is the user function as described, but without any action.
When you select Create function prototype from the Prototypes
menu, a window appears, in which you enter the name you want for the new user
function (see Figure 2-16). The file is created
in the current working directory (usually the same directory as the other module
files). If a file with the name you have selected already exists, the prototype
file is renamed <filename>.user.
This example shows a basic prototype for a user function myModule.c.
The module has lattice input and output ports and parameter input ports.
The user function is written in C and has 12 function arguments.
Once you have the prototype file, you can open it and finish writing
the user function at your leisure. If you create the prototype file before
you have defined the function in the Module Builder, the file will not
contain the function arguments.
You can generate a function prototype for each hook function you want to
write for a module. To create the hook function, use the Hook
Funcs...
option on the
Build
menu (for details, refer to Hook Functions in
Chapter 9). Then select Create function prototype from the
Prototypes menu to create the hook function prototypes.
Here are examples of the two types of hook function prototype in Fortran.
If you want the Module Builder to create a help file template for you, select
Create document prototype from the Prototypes menu. The help file
is saved in the working directory with the name <modulename>.doc.
A skeleton file contains the port names, data types, and other variables
that you have specified during module building. You should add any descriptive
information you want users to read when they invoke help for your module.
This information should include a description of the module's algorithm,
ports, known problems or limitations, and related modules. Be careful not
to disturb any line beginning with %%.
This example shows what the prototype help file for myModule.c,
called myModule.doc, would look like.
If you are modifying an existing module and changing some aspect of its
control panel (such as using different widgets or new ports), select Update
document from the
Build
menu and its submenu Options (see Figure
2-17) to update the existing <modulename>.doc file and
preserve any existing annotation.
Control over module building is provided by the
Build
menu (see Figure 2-17). This gives a way to set
defaults for the module and to build, install and clean the executable. The
resource and document files may be updated from this menu.
All the menu options except
Hook Funcs...
are discussed below. Hook functions are discussed in Hook Functions in Chapter 9.
You can set a number of options in the Build Options control panel (see Figure 1-19). This is invoked by selecting
Options... from the
Build
menu. The options on the control panel are:
The remaining options on the
Build
menu are used to
create and install
the module executable, and to update the resource and help files. Output from
these operations appears in the window from which the module builder was
started.
The Module Builder is an easy-to-use tool which enables the creation
of IRIS Explorer modules in the
UNIX environment.
However, some users may prefer to do some of this work with a text-based command
line. In UNIX this is achieved by using Makefiles. You are referred to Appendix A - Using Makefiles for more
information.
The Module Builder handles the details of the module building and
installation environment,
but it does not handle creation of data types. These you must make yourself as
described in
Chapter 8 - Creating User-defined Data
Types.
When you do so, you may want to designate a different installation
directory for your new data types and modules.
You must define one very important environment variable,
EXPLORERUSERHOME, to indicate to the Module Builder where new modules
and type files are to be installed. EXPLORERUSERHOME is the name of the
directory that is the root of the user's installation tree.
Typically, the value of this variable would be /usr/local/explorer
or perhaps $HOME/explorer. For example, in the C-shell you
would type this command:
When modules and types are built, the generated Makefile installs
a number of items under EXPLORERUSERHOME,
creating subdirectories as necessary.
This is the directory structure created under $EXPLORERUSERHOME:
A few environment variables control alternatives when modules are built
and installed. They need not have any particular value, and they take effect:
You must set the DEBUG option before you make the module
Makefile if you want to be able to debug your module. For more
information, see Debugging a Module. When you issue the make install command
in a directory used for building modules or data types, the files are created
and installed in the directories according to the environment variables
you have set. If you ever change the value of these variables, you must
recreate the Makefile in your directory
to reflect the new value.
You can do this with the command:
If you do not already have a Makefile, you must use the command:
This command makes you a new Makefile, which you can use to
recreate your modules and type-related files.
Modules built in a directory are listed in a special file named MODULES;
the Module Builder creates this file for you. New data types must be listed
in a file named TYPES,
which you must create by hand, since there is no mechanism for listing
new data types automatically.
The TYPES file must contain the names of the data type files
to be processed in the current directory, but without the .t suffix.
For example, if you have data type files named NewType.t and MyType.t,
then you must create a TYPES file that contains the lines:
For more on .t files, see Chapter 8 - Creating User-defined Data
Types.
Both the MODULES and the TYPES files must be present
before you run cxmkmf. Then cxmkmf creates the Makefile
commands required to build and install code properly for these data
types.
For more information on Makefiles, see Appendix A - Using Makefiles.
IRIS Explorer modules are compiled by default with the compiler's optimizer
option turned on. To create a debuggable module executable
set
the DEBUG environment variable before you issue the cxmkmf
command (see Appendix A - Using
Makefiles):
You can then debug it. To do this, start IRIS Explorer and bring
myModule (along with any other modules that you may require) into the Map
Editor. This starts the module running as a process called myModule, to which
you can attach your debugger.
Use the UNIX ps(1) command and search
for the line containing the name of your
executable. It will be a string like:
Then, in a separate shell window, bring up your favorite debugger and attach
it to the running process (your debugger may require the process id rather than
its name; this can be obtained from the output of ps(1)). Using
dbx, for example:
Upon attaching the debugger to the process, you will probably find that the
process is sleeping within the select system call. In this state, the module is
awaiting new input before firing.
Set a breakpoint at a suitable point within your module (for example, the
entry point to the user function), then fire the module by passing it new input
data.
After telling it to continue by typing
Here are user functions for two simple modules, coded in C and in Fortran.
The first one is a channel selecting module, and the second is a simple
pass-through module. The source code for these modules is in
$EXPLORERHOME/src/MWGcode/ModuleBuilder/C/* and
$EXPLORERHOME/src/MWGcode/ModuleBuilder/Fortran/*.
This module selects a channel from an input lattice having multiple
channels. It is a variant on the simple module used as the example in Chapter 1 - Building a Module.
This module copies all lattice input data to the output lattice, doubling
the data values if they are of type float.
Figure 2-1 Module Builder Main Window
mbuilder $EXPLORERHOME/modules/GenLat.mres
Moving Around the Windows
The Main Window
The Module Name
mbuilder LatToGeom.mres
or
mbuilder LatToGeom
Naming Modules
Modifying Existing Modules
Saving Modules
The User Function File
Using Several Function Files
** Writing Fortran User Functions
The Include Files
-I$(CXSYSINCLUDE)
Associated Libraries
The system-defined variable is ${FORTRANLIBS}.
Note: Even if you have a C user function, IRIS Explorer gives you
${FORTRANLIBS}if you have a <filename>.f or
<filename>.fi file in your User Func File list.
Geometry input port. The system-defined variable is
${GEOMETRYLIBS}.
-l<library> for individual libraries, and -L<directory>
to add directories to the library search path. For example, to build a
module that makes use of a copy of the NAG C library which has been
installed in /usr/local/lib/nagc.a, enter:
-L/usr/local/lib -lnagc
User Makefiles
Creating Ports
Defining Input Ports
Input Port Characteristics
Figure 2-2 Input Ports Window
** Firing Sequences
Setting Lattice Constraints
Figure 2-3 Lattice Constraints Window
Colormap Lattice Specs
Button Positions
A 1-D lattice
Button #1 in the Num Dimensions field is ON
Three or four data variables
Buttons #3 and #4 in the Num Data Variables field are ON
Float data values
The Float button in the Primitive Data Type list is ON
Uniform coordinates
The Uniform button in the Coord Type list is ON
Curvilinear coordinate variables per node
Buttons 1 through 3 in the Num Coord Dimensions list are ON
Required values
The Required button is ON for both the Data Structure
and Coord Structure fields
Defining Lattice Constraint Fields
** Automatic Type Coercion of Arrays
Figure 2-4 Sequence of Type Coercions
Note: This feature makes module writing and integration much easier
for the module writer, but it also requires time and a great deal of memory
on the part of IRIS Explorer. It may be more efficient to write a module
so that it accepts the correct data type without any coercion. This way,
you will avoid the considerable memory bloat that is the cost
of coercion.
Returning to the Port Window
Setting Pyramid Constraints
Defining Pyramid Constraint Fields
Figure 2-5 The Pyramid Constraints Window
Figure 2-5 shows these default settings
for pyramid constraints:
Returning to the Port Window
Defining Output Ports
Figure 2-6 Output Port Window
Defining Port Characteristics
** Checking the Port Status
Defining Function Arguments
Creating an Argument List
Func Name
Language
Figure 2-7 Function Arguments Window for a C Function
Arg Name
Figure 2-8 Function Arguments Window for a Fortran Subroutine
Type
C and C++
Fortran
char (byte)
character*(1)
short
integer*2
int
integer
long
integer
int
logical
float
real
double
double precision
pointer
integer
cx*
integer
References
(a)
(b)
C and C++
Fortran
Purpose
Scalar
Scalar
Used for passing in a single value. Scalars are passed by value in
C and C++
Array
Array
Used for passing in or modifying a data array. Expects multiple values.
Arrays are passed by reference in C and C++.
& Scalar
Because Fortran always passes parameters by reference, there is only
one option, Scalar, for passing Fortran scalar values.
Used for passing a value out by means of the argument list. Passes
a pointer to a scalar.
& Array
Array Pointer
Used for passing out an array by passing a pointer to an array reference,
such as an array allocated in the function. Also useful for returning a
pointer to allocated storage.
Return Val?
Connecting Arguments to Ports
Figure 2-10 Connections Window
Understanding the Connections Menu
Figure 2-11 Connections Menu for the Molecule Port
Creating a Link
Highlighted Connections
Figure 2-12 Highlights in the Connections Window
Passing Default Values
Using Pseudo Input Ports
(a)
(b)
** Allocating Storage Space
Mapping Rules
** Checking the State of Input Ports
** Copying Arrays
(a)
(b)
Creating File Prototypes
Figure 2-15 The Prototypes Menu
The User Function Prototype
Figure 2-16 Function Prototype Window
#include <cx/cxLattice.api.h>
#include <cx/cxParameter.api.h>
#ifdef __cplusplus
extern "C" {
#endif
void MyModulec (
long dim,
long dim,
long dim,
long *dims,
long nDataVar,
double *dataArray,
long nCoordVar,
float *coordArray,
long *nDimOut,
long *dimsOut,
double *dataOut,
float *coordOut );
#ifdef __cplusplus
}
#endif
/* ------------------------------------------- */
/* ------------------------------------------- */
void MyModulec (
long Idim,
long Jdim,
long Kdim,
long *dims,
long nDataVar,
double *dataArray,
long nCoordVar,
float *coordArray,
long *nDimOut,
long *dimsOut,
double *dataOut,
float *coordOut )
{
}
Hook Function Prototypes
C Hook function for IRIS Explorer module actions. /*Remove hook /*
SUBROUTINE FREMOVE()
print *,'Remove hook function called.'
RETURN
END
C Hook function for IRIS Explorer module actions. /* Connection /*
SUBROUTINE FCONNIN( PORTNAME, LINKTAG)
CHARACTER*(*) PORTNAME
*INTEGER LINKTAG
print *,'Connect input hook function called: ',
1*PORTNAME, LINKTAG
RETURN
END
The Help File
myModule
%%------------ DESCRIPTION ------------
<Replace this with your module description>
%%------------ INPUTS ------------
%PORT Input 1-D
%TYPE Lattice
%CONSTRAINT 1-D
%CONSTRAINT curvilinear
<Describe the purpose of the port here>
%PORT I dimension
%TYPE Parameter
<Describe the purpose of the port here>
%PORT J dimension
%TYPE Parameter
<Describe the purpose of the port here>
%PORT K dimension
%TYPE Parameter
<Describe the purpose of the port here>
%%------------ WIDGETS ------------
%%------------ OUTPUTS ------------
%PORT Output 3-D
%TYPE Lattice
%CONSTRAINT 3-D
%CONSTRAINT curvilinear
<Describe the purpose of the port here>
%%------------ PROBLEMS ------------
<Describe any known problems/limitations here>
%%------------ SEEALSO ------------
<List associated or related modules here>
Building Modules
Figure 2-17 The Build Menu
Selecting Build Options
Building and Installing Modules
** Building modules outside of the Module Builder
Configuring the Build Environment
Defining EXPLORERUSERHOME
setenv EXPLORERUSERHOME /usr/local/explorer
Setting Environment Variables
The variables are:
When you use the make install command, modules will be installed
using symbolic links, rather than being copied. This can result in substantial
disk space savings.
The symbol table will be removed from the module executable when it is
installed. Although you gain disk space, you are no longer able to debug
the module.
Creating a Makefile
make Makefile
cxmkmf
NewType
MyType
Debugging a Module
setenv DEBUG 1
cxmkmf
make myModule
18088 ? 0:01 myModule
dbx -P myModule
Process 13832 (myModule) stopped at [_select:12 +0x8,0xfabcfd8]
Source (of select.s) not available for Process 13832
(dbx) stop in mymod
Process 13832: [3] stop in mymod
(dbx) cont
the
debugger will stop execution at your breakpoint, and you can begin to debug your
code.
Examples of Simple Modules
The ChannelSelect Module
C Version:
/* Example module that does a channel select */
#include <cx/Typedefs.h>
#include <cx/DataTypes.h>
#include <cx/DataAccess.h>
#include <cx/UI.h>
#if (defined(_AIX) || defined(__sun))
#include <cx/cxOs.h>
#else
#include <cx/cxString.h>
#endif
void channel(long which, cxLattice *inLat, cxLattice **outLat)
{
long xLo, xHi, i, nData, dataLen, w;
long nDim,nDataVar,hasData,hasCoord,nCoordVar,*dims;
cxCoord *coord;
cxPrimType primType;
cxCoordType coordtype;
cxData *data;
char *oldData, *dataArray;
/* Get all the information about the input lattice */
cxLatDescGet(inLat,&nDim,&dims,&hasData,&nDataVar,&primType,
&hasCoord,&nCoordVar,&coordtype);
cxLatPtrGet(inLat,&data, (void **)&oldData,&coord,NULL);
/* Reset the range of the slider
* based on the number of input data variables
*/
xLo = 1;
xHi = nDataVar;
cxInWdgtLongMinMaxSet( "Which Data", xLo, xHi);
/* Reset range of widget if off-scale */
w = which;
if(w > xHi)
{
w = xHi;
cxInWdgtLongSet("Which Data",xHi);
}
if(w < xLo)
{
w = xLo;
cxInWdgtLongSet("Which Data",xLo);
}
/* Make a new lattice of the correct size */
w -= 1;
nDataVar = 1;
*outLat = cxLatDataNew(nDim,dims,nDataVar,primType);
/* Move the coordinate pointer from the old to the new lattice */
cxLatPtrSet(*outLat,NULL,NULL,coord,NULL);
/* Get the information about the new data array */
cxLatPtrGet(*outLat,NULL,(void **)&dataArray,NULL,NULL);
nData = cxDimsProd(nDim,dims,nDataVar);
dataLen = cxDataPrimSize(data);
/* Copy the data from the old array to the new array */
oldData += dataLen * w;
for(i=0;i<nData;i++)
{
bcopy(oldData,dataArray,dataLen);
oldData += dataLen * xHi;
dataArray += dataLen;
}
}
Fortran Version:
C Example module that does a channel select
C
SUBROUTINE CHAN(WHICH,INLAT,OUTLAT)
C
INCLUDE '/usr/explorer/include/cx/DataAccess.inc'
C
C .. Scalar Arguments ..
#if defined(IS_64BIT)
INTEGER*8 INLAT, OUTLAT, WHICH
INTEGER*8 NDV, NDIM, P0, PTYPE, CTYPE, DLEN, HASCRD,HASDAT
INTEGER*8 NDATA, NCV, XHI, W
INTEGER*8 DIMS(1)
#else
INTEGER INLAT, OUTLAT, WHICH
INTEGER NDV, NDIM, P0, PTYPE, CTYPE, DLEN, HASCRD,HASDAT
INTEGER NDATA, NCV, XHI, W
INTEGER DIMS(1)
#endif
C .. Local Scalars ..
INTEGER I, IER, J, K, L
C .. Local Arrays ..
CHARACTER DARRAY(1), OLDDAT(1)
C .. External Subroutines ..
EXTERNAL CXINWDGTLONGMINMAXSET, CXINWDGTLONGSET
C .. External Functions ..
EXTERNAL CXDATAPRIMSIZE, CXDIMSPROD, CXLATDATANEW,
* CXLATDESCGET, CXLATPTRGET
C .. Intrinsic Functions ..
INTRINSIC MAX, MIN
C .. Pointers to Lattice Structures ..
POINTER (PDIMS,DIMS)
POINTER (PDATA,DATA)
POINTER (PCOORD,COORD)
POINTER (POLDAT,OLDDAT)
POINTER (PARRAY,DARRAY)
C .. Executable Statements ..
C
C Get all the information about the input lattice
C
IER = CXLATDESCGET(INLAT,NDIM,PDIMS,HASDAT,NDV,PTYPE,HASCRD,
* NCV,CTYPE)
P0 = 0
IER = CXLATPTRGET(INLAT,PDATA,POLDAT,PCOORD,P0)
C
C Reset the range of the slider
C based on the number of input data variables
C Reset range of widget to be inside the scale
C
W = MIN(MAX(1,WHICH),NDV)
CALL CXINWDGTLONGMINMAXSET('Which Data',1,NDV)
CALL CXINWDGTLONGSET('Which Data',W)
C
C Make a new lattice of the correct size
C
W = W - 1
XHI = NDV
NDV = 1
OUTLAT = CXLATDATANEW(NDIM,DIMS,NDV,PTYPE)
C
C Move the coordinate pointer from the old to the new lattice
C
P0 = 0
#ifdef WIN32
IER = CXLATPTRSET(OUTLAT,P0,P0,PCOORD,P0)
#else
IER = CXLATPTRSET(OUTLAT,P0,%VAL(0),PCOORD,%VAL(0))
#endif
C
C Get the information about the new data array
C
P0 = 0
IER = CXLATPTRGET(OUTLAT,P0,PARRAY,P0,P0)
NDATA = CXDIMSPROD(NDIM,DIMS,NDV)
DLEN = CXDATAPRIMSIZE(PDATA)
C
C Copy the data from the old array to the new array
C
DO 40 I = 1, NDATA
K = DLEN*(I-1)
L = DLEN*((I-1)*XHI+W)
DO 20 J = 1, DLEN
DARRAY(K+J) = OLDDAT(L+J)
20 CONTINUE
40 CONTINUE
C
RETURN
END
The Pass-through Module
C Version:
/* Example module to show the use of some lattice API functions.
*
* Input lattice coordinates are copied to the output lattice
* Input lattice float data values are doubled for the output lattice
* Other input lattice data values are copied to the output lattice
*/
#include <cx/DataAccess.h>
#include <cx/DataTypes.h>
#include <cx/cxLattice.api.h>
#include <cx/Typedefs.h>
void pass(cxLattice *inlat, cxLattice **outlat)
{
float *outData;
long nDim, *dims, nDV, nCD;
long num_pts, i, hasData, hasCoord;
cxData *Data;
cxPrimType ptype;
cxCoordType ctype;
/* Extract descriptive information about inlat */
cxLatDescGet(inlat, &nDim, &dims, &hasData, &nDV, &ptype, &hasCoord,
&nCD, &ctype);
/* Duplicate the coordinates and data of inlat to outlat
* if the input data are not of type float
*/
if (ptype != cx_prim_float)
*outlat = cxLatDup(inlat, 1, 1);
else
{
/* Duplicate the coordinates of inlat to outlat
* if the input data are of type float
*/
*outlat = cxLatDup(inlat, 0, 1);
/* Extract pointers to data structure and float data in inlat */
cxLatPtrGet(inlat, &Data, NULL, NULL, NULL);
/* Insert a data structure into outlat equal to that of inlat */
cxLatPtrSet(*outlat, Data, NULL, NULL, NULL);
/* Extract pointers to the float data in outlat */
cxLatPtrGet(*outlat, NULL, (void **) &outData, NULL, NULL);
/* Double all data elements for outlat */
num_pts = cxDimsProd(nDim, dims, nDV);
for (i = 0; i < num_pts; i++)
*(outData + i) = 2.0 * (*(outData + i));
}
}
Fortran Version:
C Example module to show the use of some lattice API functions.
C
C Input lattice coordinates are copied to the output lattice
C Input lattice float data values are doubled for the output lattice
C Other input lattice data values are copied to the output lattice
C
SUBROUTINE PASS(INLAT,OUTLAT)
C
INCLUDE '/usr/explorer/include/cx/DataAccess.inc'
INCLUDE '/usr/explorer/include/cx/Typedefs.inc'
C
C .. Scalar Arguments ..
#if defined(IS_64BIT)
INTEGER*8 INLAT, OUTLAT
INTEGER*8 NDV, NDIM, P0, PTYPE, CTYPE, HASCRD, HASDAT
INTEGER*8 NDATA, NCV,DATPTR, NUMPTS
INTEGER*8 DIMS(1)
#else
INTEGER INLAT, OUTLAT
INTEGER NDV, NDIM, P0, PTYPE, CTYPE, HASCRD, HASDAT
INTEGER NDATA, NCV, DATPTR, NUMPTS
INTEGER DIMS(1)
#endif
C .. Local Scalars ..
INTEGER I, IER
C .. Local Arrays ..
REAL OUTDT(1)
C .. External ..
EXTERNAL CXDIMSPROD, CXLATDESCGET, CXLATDUP, CXLATPTRGET,
* CXLATPTRSET
C .. Pointers to Lattice Structures ..
POINTER (POUTDT,OUTDT)
POINTER (PDIMS,DIMS)
C .. Executable Statements ..
C
C Extract descriptive information about inlat
C
IER = CXLATDESCGET(INLAT,NDIM,PDIMS,HASDAT,NDV,PTYPE,HASCRD,
* NCV,CTYPE)
C
C Duplicate the coordinates and data of inlat to outlat
C if the input data are not of type float
C
IF (PTYPE.NE.CX_PRIM_FLOAT) THEN
OUTLAT = CXLATDUP(INLAT,1,1)
ELSE
C
C Duplicate the coordinates of inlat to outlat
C if the input data are of type float
C
OUTLAT = CXLATDUP(INLAT,0,1)
C
C Extract pointers to data structure and real data in inlat
C
P0 = 0
IER = CXLATPTRGET(INLAT,DATPTR,P0,P0,P0)
C
C Insert a data structure into outlat equal to that of inlat
C
P0 = 0
#ifdef WIN32
IER = CXLATPTRSET(OUTLAT,DATPTR,P0,P0,P0)
#else
IER = CXLATPTRSET(OUTLAT,DATPTR,%VAL(0),P0,%VAL(0))
#endif
C
C Extract pointers to the real data in outlat
C
P0 = 0
IER = CXLATPTRGET(OUTLAT,P0,POUTDT,P0,P0)
C
C Double all data elements for outlat
C
NUMPTS = CXDIMSPROD(NDIM,DIMS,NDV)
DO 20 I = 1, NUMPTS
OUTDT(I) = 2.0*OUTDT(I)
20 CONTINUE
END IF
C
RETURN
END
Last modified: Mar 01 17:26 1999
[ Documentation Home ]