Chapter 2 - Understanding the Module Builder

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.

Creating a User Function

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.

Using the IRIS Explorer API

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++.

** Geometry Modules in Fortran

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.

** The Scripting API

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.

Running the Module Builder

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:

File
Creating new modules, opening existing modules and saving modules. For more information on these options, see Opening Modules in Chapter 2.

Prototypes
Creating templates for the module source and help files. For more information, see Creating File Prototypes in Chapter 2.

Build
Compiling and installing the module executable. More information on these options is contained in Building Modules in Chapter 2.

Opening Modules

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.



Figure 2-1 Module Builder Main Window

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:

mbuilder $EXPLORERHOME/modules/GenLat.mres

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.

Moving Around the 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:

Cancel
Cancels all the information before it can be saved

Apply
Saves the information and leaves the window open

OK
Saves the information and closes 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.

The Main Window

Filling out the Module Builder main window is equivalent to setting the variables required by the language compiler and link editor.

The Module Name

The modulename can be:

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.

Naming Modules

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.

Modifying Existing Modules

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.

Saving Modules

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:

The User Function File

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.

Using Several Function Files

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).

** Writing Fortran User Functions

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 Include Files

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:

-I$(CXSYSINCLUDE)

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.

Associated Libraries

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
-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

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.

User Makefiles

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.

Creating Ports

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.

Defining Input Ports

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.

Input Port Characteristics

Three fields need to be defined: the port name, its data type, and its status.



Figure 2-2 Input Ports Window

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:

Required
It must be wired up and have data on the port before the module will fire. This implies that it must also have a connection from an upstream module. (This is the default.)

Optional
It need not be wired up for the module to fire. However, once it is wired up, it behaves like a required port and it must have data on the port before the module will fire.

** Firing Sequences

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.

Setting Lattice Constraints

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).



Figure 2-3 Lattice Constraints Window

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.

Table 2-1 Colormap Lattice Constraints

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

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.

Num Dimensions
Your port can accept lattices ranging from 1D upward. You can restrict it to accept just one set of dimensions, such as 3D only, or you can let it accept, for example, both 2-D and 3-D lattices, depending on how specific or how complex the user function is.

Num Data Variables
You can specify as many data variables as the user function can handle for example, temperature, pressure, and altitude.

Primitive Data Type
You can specify any or all of the primitive data types. You can allow the port to accept lattice data of all primitive types, even though the user function accepts only one type of data such as bytes, for example.

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).

Coord Type
You can specify any or all of the coordinate types. They are described in detail in Chapter 3 - Using the Lattice Data Type.

Num Coord Dimensions
If the module accepts or produces a curvilinear lattice, you must specify whether the lattice can have one, two, or three coordinates per node of the lattice.

Data Structure and Coord Structure
These options let you refine the port status, which is determined in the Input or Output Ports window (Required or Optional). You can stipulate whether the lattice must have both data and coordinate values (the default) or whether one or the other may be optional. For example, a module such as Interpolate is concerned only with coordinate values, not with data, so you can set the Data Structure on the output port lattice to Optional.

** 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.

** Automatic Type Coercion of Arrays

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.



Figure 2-4 Sequence of Type Coercions

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:

Double to Float
Although converting from double to single precision involves a significant loss of precision, it is used when single and double versions of internal module code are not both available.

Float to Long Int
Float to Long Int coercion clips to the range of signed long integers, using the integer representation of the host computer and rounded to the nearest representable integer.

Long Int to Short Int
The short int type stores integers between 0 and 65535. The translation maps [0,65535] to [0,65535] and clips integers outside the range to 0 or 65535.

Short Int to Byte
The byte type stores integers from 0 to 255. Bit data and encodings of other data can also use this type, but in type coercion, a byte is used in integer form. The translation from integer to byte maps [0,255] to [0,255] and clips integers outside the range to 0 or 255.


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

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.

Setting Pyramid Constraints

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.

Defining Pyramid Constraint Fields

Click on the radio buttons to make your selections.

Num Layers
You can specify a pyramid with any number of layers. For example, if you select 2 and 3, the port will accept 2-D and 3-D pyramids only.

Base Lattice
The base lattice is the lattice at the base of the pyramid. You can stipulate that the base lattice be required, optional, or absent.



Figure 2-5 The Pyramid Constraints Window
Pyramid Compression mode
You can select one or more compression modes. You can set cx_compress_none ON and the other two options OFF to prevent the module from accepting or producing any compressed pyramids. The cx_compress_unique option accepts compressed pyramids based on a single type of element in a pyramid dictionary, such as a brick, and cx_compress_multiple accepts those made up of two or more elements, such as bricks and tetrahedra.

Compression Dimension
Indicates the level at which compression takes place, for example, at the 1-D, 2-D, or 3-D level. The levels below this are specified only indirectly by reference to a pyramid dictionary.


Figure 2-5 shows these default settings for pyramid constraints:

Returning to the Port Window

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.

Defining Output Ports

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.



Figure 2-6 Output Port Window

Defining Port Characteristics

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.

** Checking the Port Status

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.

Defining Function Arguments

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.

Creating an Argument List

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.

Func Name

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.

Language

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.



Figure 2-7 Function Arguments Window for a C Function

Arg Name

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.



Figure 2-8 Function Arguments Window for a Fortran Subroutine

Type

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.

Table 2-2 Primitive Data Types in C and Fortran

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

** 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.

References

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.



(a)


(b)
Figure 2-9 Function Argument Reference Menu for (a) C and (b) Fortran

Table 2-3 Function Argument References

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?

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.

Connecting Arguments to Ports

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.



Figure 2-10 Connections Window

Understanding the Connections Menu

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.



Figure 2-11 Connections Menu for the Molecule Port

Creating a Link

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.

Highlighted Connections

Figure 2-12 shows how connection options are highlighted in the Connections window. There are two kinds of highlighting:

Passing Default Values

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.

Using Pseudo Input Ports

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).



(a)


(b)
Figure 2-13 Setting a Constant Value for a Function Argument.
(a) Bringing up the argument value window
(b) Setting the value in the window

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:

** Allocating Storage Space

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.

Mapping Rules

These are the basic rules to follow when connecting ports and arguments:

  1. The mapping to an output port must describe the contents of the output data structure fully. Every element in an output port should have a source, either a function argument or an input port; otherwise the Module Data Wrapper may not have enough information to build the data set and errors will appear (not immediately, where they can be picked up, but later, and apparently mysteriously).
  2. On Port menus, you can wire to the data type structure itself or to all its component elements. For example, instead of wiring to Pyramid Structure in Figure 2-11, you can wire to all the substructures listed as separate elements. Similarly, you can wire to Data Structure or you can wire to Num Data Variables, Primitive Data Type, and Data Array, which are subelements of the Data Structure component.
  3. Wire input ports to function arguments and/or output ports. Ports can have multiple connections (one to each part of the data structure on the Port menu). Function arguments can have only one input and only one output (but need not have any outputs).
  4. If you make more than one connection to a function argument or a Port menu element, you may duplicate and/or overwrite data as it is passed, causing errors. In particular, if you wire more than one connection to an output port element, you run the risk of leaking memory.
  5. Every function argument must be connected to an input port, a pseudo input port, or an output port. You can leave only one unconnected if you do not intend to use it.
  6. You can wire an input lattice data type to an output data type, but the values of nDim (Num Dimensions) and dims (Dimensions Array) cannot be changed. This is a mistake easily made because the data and coordinate substructures (cxData and cxCoord) each have a copy of these values.

** Checking the State of Input Ports

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.

** Copying Arrays

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.



(a)


(b)
Figure 2-14 Copy Buttons.
(a) Passing the array itself, or
(b) passing a copy of the array

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.

Creating File Prototypes

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.



Figure 2-15 The Prototypes Menu

The User Function Prototype

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.



Figure 2-16 Function Prototype Window

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.

#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 )
{
}

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.

Hook Function Prototypes

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.

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

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.

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>

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.

Building Modules

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.



Figure 2-17 The Build Menu

Selecting Build Options

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:

Write wrapper code
Lets you select an automatically generated Module Data Wrapper. The default is Yes.

MCW type
Lets you select the type of Module Control Wrapper (MCW) for linking. Most modules link against the default MCW, but a few manipulate windows in the X Window System independently of the IRIS Explorer graphical user interface (see examples in Working with Module Wrappers in Chapter 9). These modules should be linked against the X-MCW.

Link module with
Lets you link the module with either C or C++.

Alternate executable
Lets you specify whether your module has its own executable or whether it will use the same executable as some other modules in order to save disk space. For more information, refer to Sharing Module Executables in Chapter 9. The default is No.

Additional Files to Install
Lets you stipulate any script or program files you want installed with your module. Used when you are writing an interpreter such as a LatFunction-based module. For more information, see Building and Installing Modules in Chapter 2 and also Chapter 10 - Module Prototyping with Shape.

Loop controller status
Lets you determine whether or not the module is a loop controller, and if so, whether it controls a while loop or a repeat loop. For more information on loop controller modules, see Building Loop Controller Modules in Chapter 9. The default is None.

Output window
Lets you select the destination for standard output and error reporting from the module. By default, modules send printed output to the shell invoked by the Map Editor. Selecting Console Window directs the standard out and standard error streams to /dev/console in the workstation's console window. This selection saves one file descriptor per module; it may allow more modules to be invoked. Do not use Console Window output with a module using the X-MCW.
Collaborative Status
Lets you determine whether or not the module can be used as a collaborative module. For more information on collaborative modules, see the IRIS Explorer Collaborative User Guide.

Building and Installing Modules

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.

Build
Saves the module resources and uses them to generate wrapper code for the module. It then compiles and links the wrappers and the module source to create a module executable. The executable is created in the current working directory (usually the same location as the module resource file).

Build and Install
As Build, except the module is, in addition, installed in $EXPLORERUSERHOME/modules. The environment variable defines the directory that is the root of your personal installation tree. More information on this, including how to set its value, can be found in Configuring the Build Environment in Chapter 2. If this was not set when the Module Builder was started, then it tries to install the module in $EXPLORERHOME/modules.

** Some modules, particularly those that contain an interpreter that reads a script file, have auxiliary files that need to be installed when the module is installed. For instance, LatFunction-based modules have a script file (named in the Program File widget) that should be installed to go along with the module. This is automatically handled by the interpreter option when you specify an alternate executable, but you can use the Build Options control panel to install other files as well if you wish (see Selecting Build Options in Chapter 2).

Update document
Updates both the modulename.mres file and the document help file (modulename.doc). You must have write permission on both module files to do this. The document file is overwritten and the original .doc file is saved as a .bak file. If you have information in the old file that you want retained in the new file, you must edit the new .doc file accordingly. These are ASCII files, so this is easily done. The modulename.mres file is overwritten without any backup file being saved.

** Building modules outside of the Module Builder

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.

Configuring the Build Environment

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.

Defining EXPLORERUSERHOME

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:

setenv EXPLORERUSERHOME /usr/local/explorer

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:

$EXPLORERUSERHOME/
modules/
All the necessary components for modules, including the module resource (modulename.mres) file and the module executable.
types/
The description files for user-defined types. These always have the filename suffix .type.
lib/
The automatically generated API library for user-defined types.
include/cx/
The C and Fortran header files for user-defined types.
man/man1/
The manual pages for user modules.
man/man3/
The manual pages for the user-defined types (UDT) library.

Setting Environment Variables

A few environment variables control alternatives when modules are built and installed. They need not have any particular value, and they take effect:

The variables are:
CXBUILDSHARED
When modules are linked, they will be linked with the IRIS Explorer shared libraries. This results in a substantial savings in disk space and run-time memory usage. However, IRIS Explorer libraries are incompatible from release to release, so modules linked this way are not guaranteed to be compatible across releases.
CXINSTALLSYMBOLIC

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.
CXINSTALLSTRIPPED

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.
DEBUG
Modules will be compiled with the debug option, -g. IRIS Explorer modules are compiled by default with the compiler's optimizer option, -O.

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.

Creating a Makefile

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:

make Makefile

If you do not already have a Makefile, you must use the command:

cxmkmf

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:

NewType
MyType

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.

Debugging a Module

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):

setenv DEBUG 1
cxmkmf
make myModule

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:

18088 ?       0:01 myModule

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:

dbx -P myModule

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

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

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/*.

The ChannelSelect Module

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.

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

This module copies all lattice input data to the output lattice, doubling the data values if they are of type float.

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 ]
© NAG Ltd. Oxford, UK, 1999