Chapter 10. Module Prototyping with Shape

This chapter describes how to use the Shape language and the LatFunction module to create prototypes of new modules very quickly without having to use the Module Builder. It describes the Shape language syntax in detail.

Overview

Each module in an Explorer map performs a well-defined, often complicated, function. On occasion, you will find that no module exists for the lattice-based operation you want to perform. You can place a LatFunction module in your map, then read in a program written in LatFunction's Shape language, to perform this operation. The module's interpreter carries out the actions you specify.

The LatFunction module is an interpreter for a lattice manipulation language that makes it easy to operate on lattices. It lets you interactively change the way LatFunction acts on the data it receives, which provides a quick way to prototype a new module without going through the Module Builder or writing C or Fortran code. It also gives you programmatic control beyond the range you would have with dials and sliders, and it eliminates the need to compile, link, and install a module each time you modify it.

As a language interpreter designed to work with lattices and arrays, the LatFunction module runs programs written in Shape and fed to it from LatFunction's Program File or Program Text input ports. Shape's syntax is similar to the C programming language. Its main advantages are that it allows you to work directly with arrays and to change programs dynamically.

LatFunction is structured like other modules except that it accepts only lattice and parameter data. Its ports are wired in the same way as other module ports, and LatFunction makes data on those ports available to the Shape language interpreter as internal variables. You can:

  • Perform simple arithmetic on lattices, much as the P-Func Editor lets you perform arithmetic on parameters.

  • Test an idea for a full-blown module by creating a prototype of it.

  • Change your prototype interactively and view its computational results without building or loading a new module.

  • Mimic the effects of several other modules in one to obtain a general idea of the results of the combination.

The LatFunction Module

LatFunction has two forms. The basic LatFunction module has a fixed set of ports, uses fixed variable names for its input and output data, and does not accept parameters. The second form of LatFunction is analogous to the DataScribe. You create your own module resources to describe input ports, output ports, and control panel, and you write a Shape program. The generic LatFunction executable then executes in the guise of your module, according to its Shape program. This is called a LatFunction-based module. The main differences between the two are the port names and control panels. Both interpret the Shape language identically.

The Basic LatFunction Module

The basic LatFunction module (see Figure 1-1) has three input ports, two parameters, and a lattice that controls its program execution, plus data ports.

Program File 

The name of a program file chosen with the file selector. The file selector is a text type-in widget. The filename overrides any program typed in on the Program Text parameter port. This port is required on all LatFunction modules.

Debug  

Can be “On” or “Off” (the default). When it is “On”, the value of each program statement is printed after it is executed. This widget is optional on generic LatFunction modules.

Program Text 

A Shape program in the form of a null-terminated byte lattice. This program is executed if no Program File is specified; otherwise it is ignored. This port is optional on generic LatFunction modules.

Figure 10-1. The LatFunction Module Control Panel


You can either enter programs in the Program Text input port or load them from a file by typing a filename into the Program File file selector.

LatFunction has fixed names for its data ports. On input, data ports are First In, Second In, and Third In. On output, they are First Out, Second Out, and Third Out.

The LatFunction-based Module

A LatFunction-based module can accept any number of input parameters and lattices and can output any number of lattices. It does not output parameters. The input ports are shown in Figure 1-1.

Input Ports

The LatFunction-based module uses the name of an input port, such as Pressure, to create an internal variable with the same name, Pressure, in the Shape language. If the input port contains a non-alphanumeric character (a space is considered a non-alphanumeric character), the LatFunction-based module turns that character into an underscore. For instance, an input port called Temperature Array is assigned the internal variable name Temperature_Array.

A LatFunction-based module creates internal array variables only for those input ports that have data. It does not create internal variable names for ports that do not contain data. The size and shape of the internal array depend on the port data type, which can be either parameter or lattice.

Parameters on input ports can be of two types:

  • a scalar (a single value consisting of an integer or a double-precision real number) or

  • a character string.

A scalar, such as the value of a slider widget, is sent to Shape as a scalar. A character string is sent to Shape as a 1D array of integers representing the character string in the ASCII collating sequence.

A LatFunction-based module sends a lattice to Shape in each of three forms: the data array, the coordinate array, and the combined lattice. The variable name indicates the form: the suffixes _lat and _coord signify the combined lattice and the coordinate array, respectively. For instance, a Pressure port yields data Pressure coordinates Pressure_coord, and lattice Pressure_lat.

The Shape ports themselves have names derived from the Explorer ports. Input ports take the prefix iport_ followed by the Explorer port name; output ports use the prefix oport_. For instance, the port names iport_Pressure and oport_Temperature_Array correspond to the example in the previous paragraphs.

Output Ports

A LatFunction-based module assumes it must produce data for all of its output ports. Thus, for each output port name, the LatFunction-based module uses the internal variable of the same name to create an output lattice.

Name mapping differs for input and output ports. For input ports, only the data goes into the variable; for output ports, the variable contains data and may also contain coordinates.

A LatFunction-based module distinguishes the different output possibilities as follows:

  • If a given variable is a single array, Shape assumes this array contains data and appends default, uniform coordinates to it. (See the IRIS Explorer Reference Pages for a description of cxCoordDefaultNew, which creates these coordinates.)

  • If the variable is a full lattice (containing two arrays), Shape uses those two arrays for coordinates and data in the output lattice. For instance, in the case of the Pressure input port, the LatFunction-based module creates the internal variable Pressure to contain the data array, and Pressure_coord to hold the location coordinates.

Thus, Pressure_lat can be seen as a list of two arrays, one that contains data and one that contains location coordinates. If the output array Temperature has two arrays, they are assumed to be coordinates and data (see Figure 1-2).

Figure 10-2. How Shape Assigns Variable Names


To get coordinates from input to output ports, you must build them into the Shape internal variable by using make_lat(coordinates, data). The order of the arguments is significant: coordinates come first.

For example, suppose a reactor vessel has radially symmetrical pressure coordinates with pressure data at the nodes. To compute the gradient of the pressure using LatFunction, use the following procedure:

  1. Create the module resources for a LatFunction-based module with an input lattice port named Pressure and an output port named Pressure Gradient (see “Building a LatFunction-based Module”).

  2. Create a gradient program in Shape, using Pressure and Pressure_coord to compute a gradient field called Field.

  3. Use make_lat to compose the gradient lattice (the define operator := is described in “Shape Language Particulars”):

    Pressure_Gradient := make_lat(Pressure_coord, Field);
    

When the LatFunction script finishes, the output port contains the pressure gradient based on the same coordinate system as the input pressure. An output port can use output_data_flush to send multiple data sets downstream during one module firing.

Interfacing to Explorer Data Types

The LatFunction module takes lattices as its inputs and produces lattice as its outputs, but Shape operates only on arrays. Shape cannot operate directly on lattices because each lattice contains two arrays, one for the data and one for its location coordinates, thus requiring each lattice to be split into its component parts.

Lattices can have coordinates of three types: uniform, curvilinear, and perimeter, all three of which are acceptable in Shape. For uniform and curvilinear lattice coordinates, the coordinate array passed through to Shape consists of the floating point coordinates of the lattice in whatever type was present on input. This array looks exactly like the values array of the comparable lattice coordinates structure.

For example, the program to scale an array of data by 3.0 while maintaining coordinate positions is:

First_Out := make_lat(First_In_coord, 3.0* First_In);

The program to scale a lattice's coordinate positions by 3.0 while maintaining data values is:

First_Out := make_lat(3.0* First_In_coord, First_In);


Note: This program works only for uniform and curvilinear coordinates. Perimeter coordinates are handled separately (see “List Operators”).


Building a LatFunction-based Module

You can give your LatFunction-based module any control panel you want, with any widgets and any port names. However, every LatFunction module must have a widget named “Program File”. This widget can be either a File Browser or a Text Type-in widget but it must deliver a text string. Your module may also have a radio button widget named “Debug?” This widget generates debugging printouts when its value is non-zero.

To build a custom LatFunction-based module, follow these steps:

  1. Use the Module Builder to construct a module with the desired input and output port names and control panel (see Chapter 2).

  2. In the “Program File” type-in, save the name of the file that will contain the Shape program for this module.

  3. Click on the Yes radio button for “Alternate Executable” on the Build Options panel and type in LatFunction. This tells Explorer to look for the executable under LatFunction instead of under the name of the module you have just built.

  4. Save the module resources, and build and install the module.

  5. Write the Shape program, saving it under the same filename as you gave the module in Step 1.

You can now load the module into an Explorer map.

Prototyping on the Fly

If you bring up an Explorer map with a LatFunction-based module in it, you can now change that module's execution simply by changing the contents of the program file. Pressing the <Enter> key with the cursor in the “Program File” type-in forces the LatFunction module to reread and interpret that file. This, in turn, forces the module to refire based on a new program.


Note: A refiring caused by the module receiving a different value from a control panel widget does not cause the program to be reinterpreted.

Think of a channel selector that takes a three-color RGB (red, green, blue) image as input. Based on your selection, it sends either red, green, or blue downstream. You can change the selected channel on a control panel but, by modifying the program, you can also change the representation of output data. It is clear that prototyping on the fly has definite advantages when compared to the more laborious process of creating a module from scratch.

For instance, the following channel selector has three input ports:

Multi Channels 

Lattice data

Selection 

Parameter channel

Program File 

Parameter type-in with program file name

and produces one lattice as output:

Channel Out  

Lattice data out

Its action is to select the red, green, or blue channel of the input data (Multi_Channels), depending on the value of Selection, and output a gray-scale image on the output port.

The Selection parameter can be any widget that produces an integer between 0 and 2, inclusive, including a dial, slider, option menu, radio box, or even a type-in. Values below 0 and above the number of channels of the input are clipped to the valid range. The program creates a single-channel output. Commented out at the end of the program is another way of creating the output. It creates a multi-channel output with zeros in the unselected channels for a colored, rather than a gray-scale, effect.

// Take an input channel indicator and a multi-D lattice with 
// nDataVar and select one of the data variables.
// Protect against out-of-range inputs on Selection.

// Input ports:
//       Multi_Channels   Lattice data
//       Selection        Parameter channel
//
// Output ports:
//    Channel_Out

chan = max(0,min(Selection,nChan -1));

// Allocate a single channel byte output of the right shape.
int outsz[rank] = sz;
outsz[rank-1] = 1;
Channel_Out := allocate ((byte)1, outsz, fill_zero);

// Copy the selectd channel to the output array.
Channel_Out = inside(Multi_Channels[chan]);


Note: Leading // (double slash) characters indicate a Shape comment line.


Shape Language Particulars

The instructions for working with input data are given to LatFunction in the form of a Shape program.

Resemblances to C

As an array-oriented language with a C-like syntax, Shape has many similarities to C. Shape includes all of C's math library functions (see Table 1-2), as well as some other common operations. Like C, Shape function names and variable names are case-sensitive, and Shape has zero-based indexing. Shape extends the power of C by operating on arrays rather than on single scalar values.

For instance, if lattice input ports First In and Second In are of the same size and shape, you can add them to produce a lattice output port, First Out, with

First_Out:= First_In + Second_In;


Note: Using the define operator := is analogous to assigning a pointer to a structure or to an array in C. Here it makes First_Out a reference to the temporary array created by the addition of First_In and Second_In.

To try this and the next example, wire the output of GenLat to ports “First In” and “Second In” of LatFunction and wire LatFunction's “First Out” port to IsosurfaceLat and then to Render.

Multiplication is:

First_Out:= First_In * Second_In;

You can compile the array of maximums with the max function, a Shape function analogous to the Fortran function max:

First_Out:= max(First_In, Second_In);

You can produce a double precision output lattice with:

First_Out:= (double) First_In;

Most other C arithmetic and logical operations work analogously on Shape arrays. Try:

First_Out:= sin(First_In);

Differences from C

Shape differs from C in several important ways, including the use of the define operator (:=), and the way it manipulates lattices.

Lattice Manipulations

In Shape, lattices are first class values. They behave much like arrays in C or Fortran, and they also resemble arrays in Fortran 90. For example, in this and the following sections, you can wire LatFunction's First Out port to PrintLat, then type:

First_Out:=<varname>

whenever you want to print out the variable varname at the end of a computation. The examples show the Shape syntax of the computed result with an arrow symbol (for instance, ==> [[1,2],[3,4],[5,6]]). They also show Shape arrays printed in a more conventional array layout, such as:

The array [[[1,2], [3,4]], [[5,6], [7,8]]] could be displayed equivalently as

For example, in this fragment, the vectors u, v, and r are declared and allocated, then individual values are assigned as in C. Finally, the sum of u and v is copied (with the = operator) to r.

int u[3];   int v[3];   int r[3];     
u[0] = 1;   u[1] = 5;   u[2] = 10;    
v[0] = 2;   v[1] = 3;   v[2] = -3;    
r = u + v;     
r;     
==>[3, 8, 7] 

A Shape array is the same as the data array of an Explorer lattice. Its dimensions are identical to those of the Explorer lattice, with nDataVar treated as the fastest varying dimension. nDataVar defines the number of data variables per node in a lattice. For a 2D lattice of three data variables, with dimensions 10 x 20, the shape of the Shape array is [20, 10, 3].

All nD lattices in Explorer are (n+1)D Shape arrays by default, with the nDataVar size taken as the extent of the fastest varying, n+1, dimension.

The rank of a lattice is the number of dimensions it has. The shape of a lattice is the vector whose entries are the lengths in each dimension. For example, [1, 2, 3] has rank 1 and shape [3], and [[1, 2], [3, 4], [5, 6]] has rank 2 and shape [3, 2]. Rank 1 arrays are often called vectors and rank 2 arrays are often called matrices. An array with rank 0 is a scalar.

Arithmetic with arrays is almost always done element-wise, as follows:

u := [1, 2, 3]; 
v := [4, 5, 6]; 
u * v;
==>[4, 10, 18]

Arrays are automatically promoted to higher rank as required. Promotion entails augmenting the rank of an array and copying array values to fill in the new array elements. Lattices promote on the right, in the most rapidly varying dimension. That is, arrays with the following shapes are compatible with respect to promotion: [5, 4, 3], [5, 4], and [5]. Data values are copied to consecutive array locations during promotion.

For instance, given u from the above example:

u + 2;      
==>[3, 4, 5]      

m := [[1, 2], [3, 4]];      
m * [3, 4];   
==>[[3,6],[12,16]]  


Note: The fragment above is not matrix-vector multiplication


 m + 1;      
 ==>[[2,3],[4,5]]

m = [1, 2];      
==>[[1,1],[2,2]]  

You can change the direction in which lattices promote: refer to the Inside, Outside, and InsideOut functions listed under “Miscellaneous” in Table 1-3 as well as in “Promotion of Arrays” and “Promotion of Arrays” .

It is an error to operate on arrays of differing lengths in any dimension. For example, [1, 2, 3] + [3, 4] is an error.

The define Operator (:=)

The define operator, denoted var:= value, binds a variable without requiring that the variable be previously defined. Define is not present in C, but it provides functionality similar to that of C pointers.

The := operator, unlike =, copies no data. Instead, the value is simply given a new alias, or name. The define operator is important because:

  • Define lets you name only a part of an object. For example, take a five-element vector L (a 1D lattice) with the values 0, 1, 2, 3, 4 stored in it (such as L := [0, 1, 2, 3, 4]), in which:

    a = L[1]    copies an element of L into a
    a := L[1]   defines a by reference to part of L
    

    To show the effect of these definitions, set a to 5 after each, then print L. In the first case, the result is:

    a = L[1];   
    a = 5;
    // L is [0, 1, 2, 3, 4]
    

    But, in the second case, the result is:

    a := L[1];
    a = 5;
    // L is [0, 5, 2, 3, 4]
    

  • Define (:=) is more efficient than copy (=). Define attaches a name to a piece of data rather than moving data to the name. For a single integer, the difference does not matter, but it is significant if the data is a large, multidimensional array.

    For example, you could make the statement

    a:= read(iport_Pressure).
    

    The data from the input port may be a whole lattice, and simply labelling the lattice instead of moving each piece of the lattice to the label is less work.

  • Define obviates the need for some variable declarations. It is not necessary to preallocate or type the defined variable in Shape, since the alias picks up the type and size of its source.

  • Define lets you refer to a regularly-strided portion of a larger array (see “Subshape.”)

Define is commonly used to change a small part of a lattice. For example, after:

int m[10, 10];           // a 10 by 10 array
d := diagonal(m);        // point to diagonal of m
d += 1;                  // increment the diagonal of m

m is now a 10 x 10 identity matrix. In the first line, m is declared to be a 10 x 10 matrix and is initialized with zeros. In the second line, the vector d is defined as the diagonal of the matrix. Finally, the scalar 1 is promoted to a vector and added to each entry of d. Since d is an alias for part of m (the diagonal), the result is that 1 has been added to each entry of the main diagonal of m.

Promotion of Arrays

Arrays promote on assignment and during binary operations. Arrays are promoted by padding the shape on the right, that is, in the fastest varying dimension. The promotion iterates each element of the source until the newly padded dimensions have been filled with the value, then moves on to the next source value.

int b[2,3,4];              // declare a 3D array of zeros
a := [[1,2,3],[4,5,6]];    // define a 2D array

b = a;
==> [[[1,1,1,1],[2,2,2,2],[3,3,3,3]],
    [[4,4,4,4],[5,5,5,5],[6,6,6,6]]]

b + a;
==> [[[2,2,2,2],[4,4,4,4],[6,6,6,6]],
    [[8,8,8,8],[10,10,10,10],[12,12,12,12]]]

Reduction of Arrays

Arrays are promoted by compressing the shape on the right, that is, in the fastest varying dimension. The reduction selects the first element of the source array in all of the newly reduced dimensions on assignment. On updating assignment (for example, +=) the dimensions are compressed by the reduction operator.

Reduction assignment by addition adds the data of the extra dimensions together. Using the previous values of a and b gives:

a := (long) a;
==>a

a += 
==> [[5,10,15],[20,25,30]]

Assignment reduction looks like a sum over i of inside(A = B[i]) on the correct number of dimensions. The following examples show a few updating operator reductions:

a = 
==> [[1,2,3],[4,5,6]]

long s;
==>s
s += b;
==>84

// ... bitwise conjunction of an array, ...
long s = 1;
==>1
s &= b;
==>0

long s = 1;
==>1
s &= [1, 1, 1, 1];
==>1

// ... logical conjunction of an array, ...
long s = 1;
==>1
s &= (b>0);
==>1

Other Operators

The update operators, such as += and &=, behave somewhat differently in Shape. For example, take the expression x += y.

If x has lower rank than y, then y is reduced into x, as follows:

int x;      
y := [1, 2, 3];      
x += y;     
==>6 


Note: The fragment above assigns to x the sum of the entries of y.


int v[3] = 1;    
m := [[1, 2], [3, 4], [5, 6]];     
v *= m;      
==>[2, 12, 30] // the products of each row of m

Reduction occurs along the same dimension as promotions, on the right of the array's dimension vector.

Data Types

The base data types in Shape differ from those in C. Shape lacks char and all the C-type modifiers, such as extern, static, unsigned, signed, short, and long, but it has the additional data types byte, complex and double complex.

All the base data types in Shape match the Explorer data types, except the two complex types. The complex numbers consist of a pair of floats, and the double complex numbers consist of a pair of doubles. Complex numbers must be read into Explorer as floats or doubles, converted in Shape, and read out again as floats or doubles. compares the data types in the two languages.

Table 10-1. Data Types in C and Shape

Language

Data Types

C

char, short, int[a], long, float, double

Shape

byte, short, int, long, float, double, complex, double complex

[a] In Shape, int is equivalent to long.


Function Declaration Syntax

Functions are declared differently in Shape than in C. For example, the C-style declaration

float f(float x, int y) {...; return z;}

is expressed in Shape as

function f(x, y) {...; return z;}. 

To summarize:

  • Functions and their arguments are not typed.

  • They start with the keyword “function”.

  • The keyword return signals the value to be returned from the function. In the absence of a return statement, the last computed value in the function is returned.

Table 1-3 lists the built-in functions available in Shape.

Loop Syntax

Shape's loop syntax is fundamentally identical to that of C. The following examples illustrate correct usage.

For Loop

Shape's For loop construction is identical to that of C. The three expressions in parentheses following For give the initial settings, test logic, and loop counter changing, respectively. The code block for the loop can be one statement or a sequence of statements; if the block is a sequence of statements, it is enclosed in curly braces. For instance:

int i;
int a[10];
for (i=0; i< 10; i += 1)
      a[i] = sin(i);					// single statement -- no curly braces


Caution: Because the increment operator ++ is not yet implemented, use the construction i += 1 instead of i ++ in loop iterations, as shown above. Using the ++ operator will produce a “Not Yet” error message.


While Loop

Shape's While loop construction is identical to that of C. The loop iterates while the conditional expression evaluates to true, or non-zero. For instance, the For loop of the previous example could be rewritten as:

int i;
int a[10];
i = 0
while (i < 10) {
      a[i] = sin(i);
      i += 1;
      }

Continue and Break

Shape's continue and break statements are identical to those of C. The continue statement causes execution in a For or While loop to branch to the next iteration of the loop. The break statement causes execution in a loop to branch out of the loop to the next higher loop level or to exit from a singly-nested loop.

Issues of Scope

Shape has the same concept as C of automatic variables, which are freed when the scope of their declaration is exited. This is useful for memory allocation, since it relieves you of having to worry about freeing variables. However, it can be inconvenient at times, particularly when you want to use a conditional assignment to an array of a variable size. For instance, the following program fragment is in error:

if (n==1) {
    array := index_fill([10,1]);
}
else if (n==2){
     array := index_fill([20,20,2]);
}
Out_Lattice := array

This Shape program fails to set Out_Lattice because array is freed when its scope (an if or else clause) is exited. You can circumvent this problem by creating a function that returns the array, as follows:

function ret_array(n)
{
   if (n==1) {
      return index_fill([10,1]);
   }
   else if (n==2) {
      return index_fill([20,20,2]);
   }
}
Out_Lattice := ret_array(n);

Additional Features

Shape offers some features that are not available in C. These include:

  • subarray selection and assignment, including scatter/gather operations

  • permutation of array dimensions

  • input/output to Explorer lattices

  • a “garbage collection” service incorporated into the language that automatically frees memory that has become inaccessible

See Table 1-2 and Table 1-3 in “Shape Function Tables” for lists of Shape functions.

Copy

The copy function implements a copy on write (COW) functionality for its single argument. Its action lies between that of define (:=) and assign (=) in that it creates a pointer to its argument only if no writes are done to that pointer. However, when the first write to the returned pointer occurs, the copy function copies the data values of the source. This late binding of copying is efficient for routines that access input port data; it prevents Shape programs from writing over input data, but delays making a copy until a write is attempted.

During the read call on an input port, for instance, this looks like:

val := read(iport_Pressure);

slice := val[0];      // uses only the original data
val[0] = slice + 1    // makes a copy of the data before storing 
                      // the incremented values in val

Scatter and Gather

Shape's scatter and gather functions extract data from one array and copy it to another array. The extracted data need not be arranged in a regular pattern, as is required of subshape, below. However, the values are copied rather than simply assigned a pointer, as they would be with subshape. Thus, scatter and gather are more expensive than subshape.

scatter takes three arguments: the destination (dest), index, and source arrays. scatter's functionality implements the mathematical operation:

dest (index) = source

which copies each value from source into the dest position specified by index. Source and dest must have the same type.

The shapes of index, source, and dest must conform, with the last dimension of index equalling the rank of dest and the remaining dimensions of index equalling their respective dimensions in source. For instance, if source is 2 x 3 and dest is 4 x 1 x 6, a valid index would have shape 2 x 3 x 3 and might be [[0,0,0],[0,0,2],[0,0,4]],[[2,0,0],[2,0,1],[2,0,2]]].

With a source of [[1,2,3],[4,5,6]], the dest would be [[[1,x,2,x,3,x]],[[x,x,x,x,x,x]],[[4,5,6,x,x,x]],[[x,x,x,x,x,x]]]

where x denotes the previous value from dest.

gather is a function that returns a gathered array, with two arguments, index and source, interpreted according to the mathematical operation source(index).

gather returns the array of selected values in the same shape as index.

Shape

The shape function returns a dimension vector giving the shape of its input argument. For instance, a 128 x 256 lattice with three channels of data (dims = (128,256), nDataVar = 3) would yield an array of shape [256,128,3].

The shape function is commonly used to discover the rank of an array. An array's rank is its number of dimensions—essentially, the shape of the shape. The correct syntax is:

int rank = shape(shape(val))[0];

Writing only

int rank = shape(shape(val)); 

would fail, because the returned shape would be a vector of length 1 rather than a scalar rank. The zero-ordered slice extracts the scalar from its vector position.

Allocate

The allocate function allocates an array of the given shape and type (such as long or float, as given in the example below) and fills it with values according to the fill_type. The basic syntax is:

allocate(base_type_example, shape, fill_type)

The fill_type must be one of:

fill_none 

leave uninitialized

fill_zero 

fill with zeros

fill_index  

fill with uniform coordinates (with integer indices)

fill_NaN  

fill with IEEE NaN symbols

allocate(1, [3, 1], fill_index) 

==> [[0],[1],[2]]  

allocate(1, [3, 3, 2], fill_index) 
==> [[[0,0],[0,1],[0,2]],[[1,0],[1,1],[1,2]],
    [[2,0],[2,1],[2,2]]]

The type of the first argument is used to fix the type of the allocated array. You may use a known array or value, use a casted value, such as (byte)1, or use an explicitly typed number, such as 1L for long or 1.0L for double.

Slice

Slice extracts a multidimensional slice from an array. It has two syntactical forms. The first one is x[y], where y is a scalar that selects the yth subarray of x, exactly as in C. When you use this form, the sliced array has rank one less than the source. For example, take a 2D array:

A = [[1,2,3], [2,4,6], [4,8,12], [8,16,24]];

Then A[0] = [1, 2, 3], a three-length vector derived from A in the ordinary C manner.

The second form of slice is A[y:z], where y and z are scalars. In this form an array section, or subarray, of A is extracted. The subarray has the same rank as A, but contains only slices x through y. For example, given A, the statement A[0:1] extracts two vectors from the original array, and the shape of the array is 2 x 3. The rank is 2.

In C and Shape, you can write A[0][0] to get the first element of A. In Shape, however, A[0:2][0:1] is different from A[0:2, 0:1]. The respective shapes are 2 x 3 and 3 x 2, because A[0:2][0:1] is the same as A[0:1].

A[0:2][0:1]; 
==> [[1,2,3],[2,4,6]]

Subshape

The subshape function takes three or four arguments and returns a reference to a subarray (or subsection) of its input array. It does not copy any data. The syntax is:

subshape (start, end, source)

or

subshape (start, end, stride, source)

where start, end, and stride are vectors of size shape(source) that signal the indices of the beginning and end of the array subsection and the strides between successive elements in each dimension. The default stride is 1. For example, with a 10 x 10 source, you can extract the central 2 x 2 square with subshape([4,4],[5,5], source)and extract every other row with subshape([0,0],[10,11],[1,2], source).

An ending index greater than the size of source is legal only if the actual indices used in the computation do not exceed the array bounds. In this example, rows 0, 2, 4, 6, 8, 10 would be extracted, but row 11 would not.

Subshape does not copy data; it returns a reference to the selected subarray.

Stack

The stacking operator concatenates arrays of compatible shape to yield a new array of rank one higher. The stacking syntax is:

[expr, expr, ...]

The shape of the stacked array is equal to the shape of the stacked components, with an added component in the slowest varying dimension. That component is the number of stacked arrays. For instance, let:

a:= [1,2,3];
b:= [4,5,6];
c:= [a,b];
==> [[1,2,3],[4,5,6]]

In this case, both a and b have shape [3], while c has shape [2,3]. You can change the stacking dimension with inside.

SendIndices

The send_indices function takes an index permutation vector and a lattice. The permutation controls where each coordinate ends up. As a special case, if permutation indices agree, then a diagonal is taken in those coordinates. For example:

send_indices([1, 0], [[1, 2], [3, 4]]); 

==> [[1,3],[2,4]]

The above example is a transpose of the 2 x 2 matrix because dimensions 0 and 1 were swapped by the [1,0] index vector.

send_indices([0, 0], [[1, 2], [3, 4]]); 
==> [1,4]

The above example is a diagonal of the 2 x 2 matrix because dimensions 0 and 1 were simultaneously selected by the [0,0] index vector.

Min and Max

Min and max take one or more arguments. With two or more, min does an element-wise minimum over all its arguments. With one argument, it performs a reduction by one coordinate. For example, the first statement takes the pointwise minimum of three vectors, while the second finds the minimum element of one vector:

min([1, 2, 3], [4, -2, 6], [7, 8, 9]); 
==> [1, -2, 3]

min([1, 2, -2, 3]); 
==> -2

DotProduct (+.*)

The dotproduct operator +.* may be used to form the dot product between vectors and/or arrays. This is matrix or tensor multiplication.

[1, 2, 3] +.* [4, 5, 0]; 
==> 14    

[[1, 2], [3, 4]] +.* [5, 6]; 
==> [17, 39]

[5, 6] +.* [[1, 2], [3, 4]]; 
==> [23, 34]

[[1, 2], [3, 4]] +.*[[5, 9], [10, 13]];

The form A+.*B is identical to the function dot_product(A, B). dot_product has an optional third argument, which directs some number of leading coordinates not to be involved in the product, but, rather, to indicate that several matrix multiplications of lower rank are to be performed. For instance, if A and B are [3, 3, 3] dimensional arrays, then dot_product(A, B) has shape [3, 3, 3, 3], while dot_product(A, B, 1) has shape [3, 3, 3]. The former is a single, 3D tensor product, while the latter consists of three 2D matrix products.

BitProduct (|.&)

Bitproduct resembles as dotproduct, except that it substitutes bit-wise “or” and “and” functions for addition and multiplication. The bitproduct operator is |.&.

Conditional Evaluation (?:)

The conditional evaluation operator ?: is similar to that of C. When working with scalar values, its operation is identical to that of C, so that

x ? a : b

evaluates to a if x is true (or non-zero) and to b if x is false (or zero). The operator goes beyond the C interpretation in that it can accept arrays as well. In this case, the arrays x, a, and b must be compatibly shaped. For instance, all three could have the same shape, or either a or b could be a scalar.

The array operation of x ? a : b creates an array the size of x but with values chosen from a or b, depending on the values of x. Scalars a and b are promoted to constant-filled arrays the shape of x, but no additional storage is allocated.

Changing Promotion and Reduction Order

The inside, outside and insideout forms are used.

Inside

The argument to the inside form is evaluated with different indexing rules than normal. The four array operations — indexing, concatenation (stacking), promotion, and reduction — start from the opposite end of the shape vector. For instance, A[0] selects the first slice of A in the slowest varying dimension, while inside(A[0]) selects the first slice of A in the fastest varying dimension. The former is a blocked selection, while the latter is an interleaved selection. For example:

v := [1, 2]; w := [3, 4];
[v,w];
==> [[1,2],[3,4]]

inside([v, w]); 
==> [[1,3],[2,4]]

m := [[1, 2], [3, 4]]; 
inside(m[0]); 
==> [1, 3]

v := [0, 0]; inside(v += m); 
==> [4, 6]

inside(m + [5, 6]); 
==> [[6,7],[8,9]]

Outside

The outside argument evaluates its argument with the default indexing rules. For example:

v := [1, 2]; w := [3, 4];
outside([v, w]); 
==> [[1,2],[3,4]]

m := [[1, 2], [3, 4]]; 
outside(m[0]); 
==> [1, 2]

v := [0, 0]; outside(v += m); 
==> [3, 7]

outside(m + [5, 6]); 
==>

InsideOut

The insideout argument evaluates its argument with the opposite of the current indexing rules. Thus, insideout(inside(expr)); and outside(expr); are equivalent.

Data Output from Shape

Shape provides some input and output capabilities in addition to creating array variables from input ports automatically and sending data out on output ports. For instance, it lets you flush multiple arrays to output lattices during one computation, and you can also write files.

Before writing a file, you must open it. The file opening commands return a port identifier as a Shape variable. For instance,

globe := open_output_file (“earthData.rgb”);

would open an output file.


Note: LatFunction automatically creates port identifiers, with names such as iport_<portname> and oport_<portname>, for all the input and output Explorer ports on LatFunction or a LatFunction-based module. You can treat LatFunction file port identifiers interchangeably with Explorer port identifiers.

You can write an array data to a port identified by portID using the write command:

write(data, portID);

If you want this data to enter the Explorer map immediately, rather than waiting until module firing finishes, you can use the flush_output_port command, as in

flush_output_port(portID);

In the absence of a flush, the lattice propagates to the map after the LatFunction module finishes firing.

You can also write an array to standard output, which can be useful for debugging, with

write(data);

Also useful for debugging is the dump command, which prints the shape, type, contents, and other information for an array.

You can write characters on ports with the write_char function, which transmits a single character. write_char is similar to the write function in that it writes to standard output if no port identifier is given.

Scalar_lattice_in and Scalar_lattice_out

Because Explorer lattices use their nDataVar length as one of their array dimensions when they are converted to and from Shape arrays, it can be inconvenient to work with scalar lattices (nDataVar = 1) in LatFunction. For instance, a 2D scalar lattice of size 10 x 20 in Explorer would have shape [20,10,1] and be 3D in Shape. The scalar_lattice_in function removes the last dimension of its input argument. In fact, scalar_lattice_in removes the last dimension even if its input is not a scalar lattice; in this case, it returns the first data variable at each node.

The function scalar_lattice_out adds a trailing unit dimension to an array. It can be used to convert a 2D Shape array into a 2D Explorer data array (with nDataVar = 1).

List Operators

Shape can work with lists of arrays, as well as handling arrays. A list is an ordered collection of items, each of which can be an array or another list. For instance, a lattice is represented in Shape as a list of two arrays:

(coord, data)


Note: Lists are delimited by parentheses, not by square brackets.

The perimeter coordinates of a lattice are stored as individual vectors in a list. For a three-dimensional lattice, this would be:

(Xcoord, Ycoord, Zcoord)

Thus the entire lattice would be:

((Xcoord, Ycoord, Zcoord), data)

Most Shape commands do not work on lists. Instead, you must either use one of the special list processing commands or extract an array from the list and work with it directly. The list processing commands are:

list 

creates a list

list_first 

returns the first list element

list_rest 

returns the tail of the list, that is, all but what is returned by list_first.

map 

operates on list elements

apply 

operates on list elements

pair_p 

is this a pair (that is, a list of two elements)?

null_p 

is this a null list?

The empty list has its own predefined symbol, nil.

List

The list function is used to concatenate two or more arrays in Shape. This can be useful when passing several arguments to a user function, or when assembling arguments for use in a map or apply call. The syntax for this function is:

list(array [, array] ...)

list uses parentheses (), not square brackets []. It can build arbitrarily complex, heterogenous structures. list(1,2,3) is not equal to [1,2,3]. For example, to create and then concatenate two scalars and a two-vector:

x := 1;
==>x

y := 2;
==>y

z := [3,4];
==>z

list(x,y,z);
==>(1 2 [3 4])

List_first and List_rest

The two functions list_first and list_rest decompose lists, extracting the first and all but the first list elements, respectively. For instance, if a is:

(1, 2, 3), then 
list_first(a);
==> 1
list_rest(a);
==> (2,3)

Handling Perimeter Lattice Coordinates

For perimeter coordinates, the Shape array structure is a list of vectors, one for each coordinate dimension of the lattice. For instance, 3D data with perimeter coordinates has three vectors in a list: the first vector represents the X dimension, and the second and third represent the Y and Z dimensions, respectively.

You can access the list of vectors with the list processing commands, list_first and list_rest, which return the first element of a list and the remainder of the list, respectively. For instance, to access the X, Y, and Z vectors of a perimeter lattice, you could use:

X_vector := list_first(Perim_Data_coord);
Y_vector := list_first(list_rest(Perim_Data_coord));
Z_vector := list_first(list_rest(list_rest(Perim_Data_coord)));

On output, such a list is interpreted as perimeter coordinates for a lattice. You can construct a list using the list function and the nil token. You will need either to write LatFunction routines to handle all coordinate types or else restrict inputs to a known type, using the Module Builder.

Map and Apply

The functions map and apply are used to run a Shape function on a set of inputs. The syntax for these functions is:

map(func, list...)
apply(func, list...)

The effects of the calls are different in that map runs the named function on each item in the list of inputs, whereas apply runs the named function on the concatenated list as a single input:

map(sin, list(0, 1, 2, 3));
==>(0 0.841471 0.909297 0.14112)

This is equivalent to concatenating several calls to sin.

sin(0);
==>0

sin(1);
==>0.841471

sin(2);
==>0.909297

sin(3);
==>0.14112

When the list has several arrays in it, the sin function works on each input:

map(sin, list([1, 2, 3], [4, 5], [1.1]));
==>([0.841471, 0.909297, 0.14112],
   [-0.756802, -0.958924],[0.8912007])

This is equivalent to concatenating several calls to sin.

sin([1, 2, 3]);
==>[0.841471 0.909297 0.14112]

sin([4, 5]);
==>[-0.756802 -0.958924]

sin([1.1]);
==>[0.891207]

The apply function treats the list as the list of inputs to a single call of the named function.

apply(min, list(3, 4));
==>3

This is equivalent to the call:

min(3, 4);
==>3

but is different from the concatenation of calls:

min(3);
==>3

min(4);
==>4

Pair_p and Null_p

The functions pair_p and null_p are Boolean predicate functions, each of which indicates the list status of its argument. Thus, pair_p returns true (non-zero) if its argument is a list of at least two elements, while null_p returns true if its argument is the empty list of no elements.

The Module Prelude and Postlude

The module prelude is a set of predefined operations that runs when the LatFunction module begins firing. They include defining certain mathematical constants, such as p; predefined functions for operations that occur every time the module fires; and defining useful constants and functional associations

The postlude is a set of predefined functions that runs when the LatFunction module has finished firing. These functions assemble data from known array names and send it to the output ports. They convert Shape data types back into Explorer data types.

Prelude Set-up Functions

These prelude functions run only once, when the LatFunction module is first launched. They are:

  • Mathematical constants:

    pi       := 3.14159265358979323844; 
    e        := 2.71828182845904523536; 
    i        := make_complex(0, 1); 
    NaN      := sqrt(-1);
    

  • Allocation fill mode indicators:

    fill_NaN 
    fill_none 
    fill_index 
    fill_zero 
    

  • Extract the data portion of a lattice:

    function lat_data(lat)
    

  • Extract the coordinate portion of a lattice:

    function lat_coord(lat)
    

  • Combine two arrays into coordinate data portions and return a lattice:

    function make_lat(coord, data)
    

Predefined Shape functions include.

  • Fill an array with 0, NaN, or indices:

    function index_fill(s) {allocate(1L, s, fill_index);}
    

  • Return the transpose or diagonal of a 2D array:

    function transpose(m) {send_indices([1, 0], m);} 
    function diagonal(m) {send_indices([0, 0], m);}
    

  • Convert a scalar nD lattice from or to a Shape nD array:

    function scalar_lattice_out(x) {inside([x]);} 
    function scalar_lattice_in(x) {inside(x[0]);}
    

All nD lattices in Explorer are (n+1)D Shape arrays by default, with the nDataVar size taken as the extent of the fastest varying, n+1, dimension.

Prelude Execution Functions

These prelude functions run every time the module fires, to organize Explorer lattice data into Shape arrays. The example shows prelude functions for the LatFunction ports “First In” and “First Out”. Similar operations occur for the other two ports and for user-defined lattice ports in a LatFunction-based module.

  • Define names for the input and output ports, as well as for their input lattices, their data parts, and their coordinates:

    iport_First_In := open_input_lattice("First In"); 
    oport_First_Out := open_output_lattice("First Out"); 
    First_In_lat := read(iport_First_In); 
    First_In := lat_data(First_In_lat); 
    First_In_coord := lat_coord(First_In_lat); 
    

Postlude Functions

The postlude functions run as the module completes firing. They convert Shape arrays back into Explorer lattice data for each output lattice port.

  • Send the output data onto a known port, for instance:

    write(First_Out, oport_First_Out); 
    

The implication of the prelude section is that First_In, Second_In, and Third_In are Shape variables that contain the input port data for the module. On output, variables First_Out, Second_Out, and Third_Out automatically have their data flushed to output ports.

Shape Function Tables

Table 1-2 summarizes the Shape functions that resemble C language functions.

Table 10-2. C-like Functions in Shape

Operation

Function Name/Syntax

Notes

Arithmetic

 

 

Addition

array + array

 

Subtraction

array – array

 

Division

array / array

 

Tensor

 

 

Boolean Matrix Multiplication

array |.& array

See Matrix Multiplication

Matrix Multiplication

array +.* array

Performs a tensor product

Casting

 

 

To Byte

(byte) array

For example,((byte)1.1)

To Short

(short) array

 

To Long

(long) array

int is a synonym for long

To Single

(complex) array

 

To Double Complex

(double complex) array

 

Integer

 

 

Modulo

array % array

 

Bitwise And

array & array

 

Bitwise Or

array | array

 

Bitwise Exclusive Or

array ^ array

 

Left Shift

array << array

 

Right Shift

array >> array

 

Logical And

scalar && scalar

Scalar only

Logical Or

scalar || scalar

Scalar only

Logical Not

! array

 

Preincrement

++ array

Not yet implemented

Postincrement

array ++

Not yet implemented

Predecrement

–– array

Not yet implemented

Postdecrement

array ––

Not yet implemented

Relational

 

 

Equal To

array == array

 

Not Equal To

array != array

 

Greater Than

array > array

 

Less Than

array < array

 

Greater Than or Equal

array >= array

 

Less Than or Equal

array <= array

 

Complex

 

 

Real Part

re(array)

 

Imaginary Part

im (array)

 

Make Complex

make_complex(arra y, array)

re(make_complex (a, b))
==> a
im(make_complex (a, b))
==> b

Language Statements

 

 

Conditional statement

if( expr) Block else

else clause is optional

Block

Block statement ; or
{ statement; statement; ...}

 

Function Declaration

function ( array, array,...) Block

 

For Loop

for (expr; expr; expr) Block

 

While Loop

while (expr) Block

 

Define

array := expr

 

Assign

array = expr

 

Conditional array value

expr? expr: expr

Evaluates its arguments completely, unlike the C function with same syntax

Return

return expr; return;

 

Continue

continue;

 

Break

break;

 

Comment

// Comment text

Precedes a comment (terminated by end-of-line) (does not use /* as in C)

List

(array) or (list, list, ...)

c ordered list of arrays and/or lists (s-expression)

Math Library

 

 

Square Root

sqrt

(array)

Sine

sin

(array)

Cosine

cos

(array)

Tangent

tan

(array )no complex

Arc Sine

asin

(array )no complex

Arc Cosine

acos

(array )no complex

Arc Tangent

atan

(array )no complex

Hyperbolic Sine

sinh

(array)

Hyperbolic Cosine

cosh

(array)

Hyperbolic Tangent

tanh

(array)no complex

Exponentiation

exp

(array)

Logarithm

log

(array)

Arc Tangent 2

atan2

(array, array )no complex

Power

pow

(array ) no complex, also infix(**)

Modulo (floating)

fmod

(array, array )no complex

Remainder

drem

(array, array )no complex

Conjugate

conj

(array)

Ceiling

ceil

(array)no complex

Absolute value

abs

(array)

Truncation

floor

(array )no complex

Rounding

nint

(array)no complex: nint (a) returns nearest integer rounding

Sign

sign

(array )no complex, -1, 0, or 1

Minimum

min

(array, array, ...)takes 1 or more arguments

Maximum

max

(array, array, ...) see Min

Table 1-3 lists the functions specific to Shape.

Table 10-3. Functions Specific to Shape

Operation

Function Name/Syntax

Notes

Lattice

 

 

Copy

copy(array)

 

Scatter

scatter (dest array, index array, source array)

 

Gather

gather (index array, source array)

 

ShapeOf

shape (array)

 

Allocate

allocate(base type array, shape array, fill type token)

 

Slice

array [index scalar]
array [scalar:scalar]

 

SubShape

subshape (start array, end array, source array)
subshape(start array, end array, stride array, source array)

 

Stack

[expr, expr, ...]

 

SendIndices

send_indices (perm utation array, source array

 

List Operators

 

 

List

list_first (list)
list_rest(list)

First element of a list
List consisting of all but the first element of a list

Map

map(function, array, ...)

 

Apply

apply(function, array, ...)

 

List

list(array, array, ...)

Creates a list

PairP

pair_p(list)

A pair or a single array?

NullP

null_p (list)

The nil array?

Miscellaneous

 

 

Inside

inside (expr)

 

Outside

outside (expr)

 

InsideOut

insideout (expr )

 

Port IO

 

 

OpenOutputFile

portname := open_output_file(f ilename)

 

FlushOutputPort

flush_output_port (portname)

Calls cxOutputDataFlush

Write

write(expr, portname)

Sends ASCII to port

Dump

dump( array )

Prints array debugging information

WriteChar

write_char(portnam e)

Writes one binary byte

Module Prelude

 

 

Constants

pi
e
i
NaN

3.14
2.71
make_complex(0,1)
IEEE constant

Values from Predefined

Operations

iport_<portname>
iport_<portname><portname>_lat
<portname>_coord
<portname>
nil

iport_First_In
oport1_First_In
First_In_lat full lattice
First_In_coord coord part
First_In data part

List terminator value

Predefined Functions

lat_data (lattice)
lat_coord(lattice)
make_lat(lattice)
index_fill(shape)


transpose(expr)
diagonal(expr)
scalar_lattice_in (expr)


scalar_lattice_ou tconform(expr, expr)

Gets data part of lattice
Gets coord part of lattice
Data + coord = lattice
Allocates a long array using fill_index
Works on a matrix
Works on a matrix
Removes an inner coordinate of length 1
Adds an inner

coordinate of length 1
Takes a lattice and a shape and returns a lattice of the passed shape, but whose corner has the values of the passed lattice. Pads with zero the first output to Shape.


Sample Programs for LatFunction

Here are three sample programs for the LatFunction module.

To Create a Test Volume

You can send this program into VolumeToGeom and IsosurfaceLat, which both feed Render, to get an interesting data set; otherwise, look at the map testVol.

function chebychev(x) {    
 xx := x * x;     
 return xx * xx - 2 * xx + 1;    
}      
n := 20;    
// Allocate vol to be a cube, then fill with 0..n-1 uniform 
// coordinate indices in 3D.
vol := (short)index_fill([n, n, n, 3]);   
float res[n, n, n];    

// Scale vol to lie in the range -2..2 and apply the 
// chebychev() function pointwise throughout vol. 

// Perform a sum-reduction over the XYZ vector to compute
// the result, which is returned as a scalar lattice.
res += chebychev(vol * (4.0/n) - 2);    
First_Out := scalar_lattice_out(res);

Figure 1-3 shows a set of contours displaced from a volume in the Render module. The program script above produces this volume.

Figure 10-3. LatFunction in the testVol Map


To Carry Out a Color Transform

This script allows direct manipulation of the coloring of an RGB image. Use the map color-xform to view the image.

// Create a gray-scale image by taking a linear
// combination of the RGB values of the input image 
// as a gray-level intensity.
// 
// Port First_In: 2D Image to transform
// Port Second_In: 4x4 matrix to provide colormap transform 
//
// Port First_Out: Transformed 2D byte Image 
//
// Extract input transform matrix    
mat := scalar_lattice_in(Second_In);

// Take linear combination of input matrix channels using 
// tensor product.  
v := (double) [mat[0][0], mat[0][1], mat[0][2]]; 
// Should be mat[0,0:2];   
gray := First_In +.* v;
// Scales the range to 0..255    
mx := max(max(gray));   
mn := min(min(gray));    
if (mx == mn)
   mn -= 1;
gray = (gray - mn) * (255 / (mx - mn));
// Assign to known output name    
First_Out := (byte) inside([gray, gray, gray]);

Figure 1-4 shows the results of the color manipulation in the color-xform map. The two images shown in DisplayImg and DisplayImg<2> can be compared.

Figure 10-4. Output from the color-xform Map


To Generate a Heat Flux

Use the heat-flux map to view this explicit time-stepping simulation of a heat flow problem.

// Animate a heat flow equation for 100 time steps and
// produce an output lattice each 5 time steps. The
// equation iterated at each timestep is 
// T += 4 h^2 (1-k) Laplacian(T); 
// where h is the grid spacing and k is a relaxation
// parameter. T is the temperature. 
// 
// No input ports 
// Port First_Out: Temperatures on square grid every 5
// iterations 
n := 50;      
float temps[n, n]; // temperature values
float next[n, n];  // space for next iteration  
// current temperatures starting at 0
// Alias a heat source and sink in the grid   
source := subshape([20, 30], [29, 44], temps);   
sink   := subshape([30, 20], [39, 24], temps);   
// Alias neighboring cells in the four directions plus
/ center and the current temperatures.   
M := subshape([1, 1], [n-2, n-2], temps); // interior of mesh
// all but edge of temps     
N := subshape([0, 1], [n-3, n-2], next);// north    
S := subshape([2, 1], [n-1, n-2], next); // south  
E := subshape([1, 0], [n-2, n-3], next); // east   
W := subshape([1, 2], [n-2, n-1], next);// west   
C := subshape([1, 1], [n-2, n-2], next);// center   

// The iteration is T += 4 h^2 (1-k) Laplacian(T);    
k := (float) 0.3;  

// weight of C   
int i;   
for (i = 0; i < 100; i+=1) {    
     source += (float) 10;      
     sink   -= (float) 10;      
     next = 0;      
     N += M; S += M; E += M; W += M;      
     next *= (float) 0.25 * (1 - k);   
    C += M * k; 
     temps = next;      
     if (i % 5 == 0) { 
        First_Out := scalar_lattice_out(temps);
        write(First_Out, oport_First_Out);       
        flush_output_port(oport_First_Out);     
      }    
   } 

Figure 1-5 shows what the output of the heat-flux program looks like in the Render module.

Figure 10-5. Output from the heat-flux Map