LAST PAGE  BACK TO INDEX  NEXT PAGE

[4.0] VEE Array Programming Techniques

[4.0] VEE Array Programming Techniques

v5.0 / 4 of 23 / 01 sep 99 / gvg

* Those who just start out programming with VEE and aren't familiar with its unique programming capabilities will often resort to programming approaches they have refined with other languages: they'll write programs with loops that perform computations one at a time.

The result is a very slow implementation. VEE, however, is optimized towards operations on arrays, and a knowledge of how to use array math to attack such problems can result in programs several orders of magnitude faster. This chapter outlines VEE array math techniques.


[4.1] ARRAY PROCESSING OVERVIEW
[4.2] VEE ARRAY OPERATIONS
[4.3] ADVANCED ARRAY OPERATIONS

 BACK TO INDEX

[4.1] ARRAY PROCESSING OVERVIEW

For example, consider the simple program that follows (see xarrcos.vee for source for this example and the one after it):


  +-----------+
  | For Count |     +-----+
  |    360    +--+->| sin +----->+-----------+
  +-----+-----+  |  +-----+      | Collector +---->+-----+
        |        |           +-->+-----------+     | JCT +---->+-----------+
        |        |  +-----+  |                  +->+-----+     | Collector |
        |        +->| cos +--|-->+-----------+  |           +->+-----------+
        |           +-----+  |   | Collector +--+           |
        |                    +-->+-----------+              |
        |                    |                              |
        +--------------------+------------------------------+
 
This generates an array containing all the values of sine and cosine for each degree from 0 to 360. On my PC this takes about 2 seconds to execute.

Now let's do the same thing a different way:


   +-------------------------------------------------------+
   |                       Formula                         |
   +-------------------------------------------------------+
   | [ sin(ramp( 360, 0, 359))  cos(ramp( 360, 0, 359 )) ] |
   +-------------------------------------------------------+
 
This is about 40 times faster. However, it's not intuitively obvious what is happening here, so some explanation is required:

There is a catch to this implementation: the operations are not done on a one-by-one basis, each step in the operation works on an entire array at a time. While it replaces operations that were being done in a loop, it's not performing any loop itself, and if you have an operation that requires the results of one iteration of the loop in a later iteration of the loop, using this array approach may be difficult or impossible.

* VEE has considerable capability to perform math operations on entire arrays. For another example, suppose you want to get the area under a curve from a set of input data. There is a simple algorithm for doing this named "trapezoidal rule", in which you just take the average height between each pair of data points, multiply each average by the interval between the points, then sum all the results.

VEE's array operations make it easy to implement trapezoidal rule in a single function (if you assume a constant interval between data points). To illustrate (see xgenfunc.vee for the source):


  +------------+
  |    Real    |
  +------------+ 
  | 0          |
  | 1          |
  | 4          |
  | 8          +---+
  | 5          |   |
  | 3          |   |
  | 6          |   |   +----------------+
  | 2          |   |   |    Formula     |
  +------------+   +-->+----------------+   +-------+
                       |                +-->|  28   |
  +------------+   +-->+----------------+   +-------+
  |    Real    |   |
  +------------+   |
  | 1          +---+
  +------------+
 
The trapezoidal rule formula accepts a data array in pin A and the data interval in pin B:

   sum( B * ( A[0:totsize(A)-2] + A[1:totsize(A)-1] ) / 2 )
 
The "totsize()" function gives the length of the array, so this formula adds each one of the data values to its successive value, divides by 2 to get the average value, multiplies by the width to get the area of each slice, and then uses the "sum()" function to add up all the averages and get the total sum.

* This ability to manipulate arrays makes the VEE formula object very powerful, though the techniques for doing so are nothing most people would figure without a few clues to help. This chapter articulates some of the things that can be done as a guide; it is hardly comprehensive, however, since the number of tricks that can be played to provide a faster solution are effectively unlimited.

I have heard that the old APL language had a similar capability to manipulate arrays and do powerful things with one or two lines of code. This information, unfortunately, came along with the caution that APL is notoriously obscure. Some called it a "write-only" language. If you want to perform sophisticated formula manipulations in VEE, I would suggest you exercise care and document your path thoroughly lest you end up with something nobody else (or even yourself, a year down the road) can figure out.

 TOP OF PAGE

[4.2] VEE ARRAY OPERATIONS

* The first element in advanced VEE programming is learning how to handle arrays. VEE has a highly flexible scheme for accessing arrays and parts of arrays. For example, consider a one-dimensional array accessed with the name "A"; you can select element 1 with:


   A[1]
 
-- and elements 0 through 5 with:

   A[0:5]
 
You can select all the array elements from 5 to the end of the array with:

   A[5:*]
 
If the array has multiple dimensions, you can use the "*" to select entire rows or columns:

   A[1,*]
 
* Math on arrays follows another set of simple rules. Elementary scalar arithmetic operations on arrays simply perform their operations on each element in the array:

   A*2
 
-- multiplies each element in the array by 2; and:

   A-4
 
-- subtracts 4 from each element in the array. Such arithmetic operations between two arrays that have the same size and dimensions perform the operation between corresponding elements of the arrays. For example, if A and B are two arrays, then:

   A*B
 
-- multiplies each element of the array by the corresponding element of the other. This does not perform a "matrix multiply", which is a relatively complicated multiplication of rows times columns and summation that results in a scalar (VEE has a function to do that).

Most elementary VEE math functions, such as "log()", "sin()", "cos()", and so on, can accept an array as a parameter and return an array. Of more specialized functions, some will not perform array math and some (a few of which we will discuss in the following text) are particularly handy for it. For examples of functions that are useful in array operations:


   ramp()        As shown earlier, can be used to generate "loop" counts.
   concat()      Concatenates two arrays & returns a 1-dimensional array.
   totSize()     Gives total number of elements in an array.
   signof()      Sign of value ( -1 / 0 / 1 if <0 / 0 / >0).
   abs()         Absolute value.
   rotate()      Rotates elements in array.
   sum()         Sums all elements in an array.
   sort()        Sorts an array.
   randomize()   Generates array of random numbers.
   min()         Minimum value of data set.
   max()         Maximum value of data set.
   clipUpper()   Clips below maximum given value.
   clipLower()   Clips above minimum given value.
 
* A very useful feature in Formula boxes is the ability to define expressions as arrays, as illustrated in the example in the first section:

   [ 2*B 1/B B*B log(B) ]
 
This expression generates an array containing the double, reciprocal, square, and natural log of the input named "B". Note that if you have an expression with a minus sign in it, you have to enclose it in parentheses:

   [ 2*B 1/B (-B*B) log(B) ]
 
-- since otherwise VEE will interpret this as "1/B - B*B".

For operating examples of VEE array math, please check the examples xarrmth1.vee and xarrmth2.vee.

* As an example of what can be done with VEE array manipulations, an HP field person had a VEE user who wanted to read a 2-dimensional array from a file, take one column of the array, perform some math on the data, and put back the column in the array.

All that had to be done in this case was use a single Formula box with the formula:


      +-----------------------------+
      |          Formula            |
      +---+---------------------+---+
   -->| A | [ A[0,*] 4*A[1,*] ] | R +-->
      +---+---------------------+---+
 
This Formula box essentially dismantles the input array, multiplies the second column by 4, then glues it back together.

* For a second example, a user sent in a request to us that involved displaying the elements of a 2048-element array as 16 sets of 128 elements each. While the question focused on the display of the data, rather than its generation, I got to thinking about the task of breaking up the array.

It was really quite simple. The following program (see xarrchop.vee for the source) shows how it's done:


   +----------------------+
   | ramp( 2048, 0, 2047) +--+   +----------------------------+
   +-----------+----------+  |   |           Formula          |
               |             |   +---+------------------------+
      +--------+-------+     +-->| A | A[B*128:(128*(B+1)-1)] +-->
      | For Count: 16  +-------->| B |                        |
      +----------------+         +---+------------------------+
 
The "ramp()" function simply generates a 2048-element array with values from 0 to 2047 for test purposes. The For Count object ticks off each of the 16 individual arrays to be generated, while the Formula box selects the appropriate sub-array using indexes generated from the count:

    A[0:127], A[128:255], A[256:384], ... , A[1920:2047]
 
Of course this assumes a fixed array size, number of subarrays, and size of subarrays. Get any of them wrong and you get an error box.

* A third example involved a user wishing to concatenate multidimensional arrays. VEE has a Concatenator object, but it can only generate a one-dimensional output. However, a Formula box can be used to do this function as long as the number of rows (or columns) is fixed (a constraint that is usually met in practice). The following Formula box performs this operation on a pair of arrays that have two rows:


      +---------------------------------------------------------+
      |                         Formula                         |
      +---+-------------------------------------------------+---+
   -->| A | [ concat(A[0,*],C[0,*]) concat(A[1,*],B[1,*]) ] | R +-->
      | B |                                                 |   |
      +---+-------------------------------------------------+---+
 
The key to this is the "concat()" function, which concatenates two arrays and produces a one-dimensional array. This Formula box strips out the rows of each of the arrays, concatenates them, and then joins them back together. To access a program demonstrating this function, check the xarrcat.vee file.

* For a fourth, more sophisticated example, consider the problem that one of our European field representatives handed to us. His user wanted to multiply a vector:


   [ X1  X2  X3  X4 ]
 
-- times a matrix:

   [ y11  y12  y13  y14 ]
   [ y21  y22  y23  y24 ]
           ...
   [ y21  y22  y23  y24 ]
 
-- to get the result:

   [ X1*y11  X2*y12  X3*y13  X4*y14 ]
   [ X1*y21  X2*y22  X3*y23  X4*y24 ]
                   ...
   [ X1*y21  X2*y22  X3*y23  X4*y24 ]
 
While VEE can easily multiply a scalar times a vector, and can perform matrix multiplications, this operation was neither fish nor fowl. As noted, a scalar multiplication multiplies every element in a matrix by a scalar to give a result matrix of the same size as the original, while a matrix multiplication is an operation between an MxN matrix and an NxM matrix that yields a scalar.

The operation here is effectively a scalar multiplication of each row of the matrix by each element of the vector, and with that known the implementation is very simple, using the same techniques outlined so far.

To demonstrate, consider a data set consisting of a vector of the form:


   [ 1  2  3  4 ]
 
-- and a matrix of the form:

   [ 1     2     3     4     5     6     7     8    ]
   [ 10    20    30    40    50    60    70    80   ]
   [ 100   200   300   400   500   600   700   800  ]
   [ 1000  2000  3000  4000  5000  6000  7000  8000 ]
 
The desired result was:

   [ 1     2     3     4     5     6     7     8    ]
   [ 20    40    60    80    100   120   140   160  ]
   [ 300   600   900   12K   15K   18K   21K   24K  ]
   [ 4000  8000  12K   16K   20K   24K   28K   32K  ]
 
The multiplication could be performed in a Formula box (see xarrvcml.vee for a developed example):

             +---------------------------------------------------------+
             |                      Formula                            |
             +---+-------------------------------------------------+---+
   matrix -->| M |                                                 |   |
             |   |[M[0,*]*V[0] M[1,*]*V[1] M[2,*]*V[2] M[3,*]*V[3]]| R +-->
   vector -->| V |                                                 |   |
             +---+-------------------------------------------------+---+
 
Tests indicated that this was only about 50% slower than a scalar multiplication of the same array.

* For a fifth example, a particularly useful function to perform is to insert one or more data elements in an existing array. It is simple to build a UserFunction to do this job (see the xgenfunc.vee library for the source):


   +-------------------------------------------+
   |           UserFunction: InsData           |
   +----------+----------------------------+---+
   |          |                            |   |
   |    N     +--+                         |   |
   |          |  |   +---+------------+    |   |
   |          |  +-->| A |            |    |   |
   | NewData  +----->| B |  Formula   +--->| X |
   |          |  +-->| C |            |    |   |
   |          |  |   +---+------------+    |   |
   | OldArray +--+                         |   |
   |          |                            |   |
   +----------+----------------------------+---+
 
The Formula contains (expanded to a multiline format for clarity):

   ( A <= 0 ? 
      concat( B, C ) :
     ( a >= totSize(C) ?
         concat ( C, B ) :
         concat ( C[0:(A-1)], concat( B, C[A:(totSize(C)-1)] ) )
     )
   )
 
You give the UserFunction an index value N to indicate what the starting index of the new data should be. If N is 0 or less, the new data is concatenated to the start of the UserFunction. If N is the length of the array of old data or greater, the new data is concatenated to the end of the array. If it is some value in between, the old array is broken in segments around the index value and the new data is spliced into it.

* A related (if simpler) example is building a data queue with array operations. A queue is essentially an array of fixed length, where new elements are added at one end, with the numbers shifting down to the other end, where numbers fall off and are lost.

This can be done with surprising directness in VEE. Consider the following program (see xarrcue.vee for source):


                      +--------------------------------------+
                      |                                      |
                      +-->+-----+                            |
   +---------------+      | JCT +--+                         |
   | ramp(10,0,0)  +----->+-----+  |   +------------------+  |
   +-------+-------+               |   |   Call Function  |  |
           |                       |   +---+--------------+  |
     +-----+-----+                 +-->| A |              |  |
     | On        |                     |   |[   Queue    ]+--+-->
     | Cycle: 1  +---+             +-->| B |              |
     +-----------+   |             |   +---+--------------+
                     |             |
                +----+----+        |
                | Random  |        |
                | Number  +--------+
                +---------+
 
The program simple allocates an initial empty array of ten elements to act as a queue. It then goes into a loop, adding a random numbers to the head of the queue every second. The UserFunction "Queue" takes the last nine elements of the input array (on pin "A") and concatenates it with a new random number (on pin "B"), using the Formula:

   concat( A[1:(totSize(A)-1)],B )
 
* This assumes that you are adding values one at a time, however, and that the size of the initial array pushed into the Formula will define the length of the queue from then on.

But suppose you can add data arrays of any length and the first array doesn't give the size of the queue? Then you can update the UserFunction as follows:


   +---+----------------------------------------------------------+---+
   |   |                                                          |   |
   |   |      +---------------------------+                       |   |
   |   |      |          Formula          |                       |   |
   |   |      +-----+---------------------+                       |   |
   | A +----->| Old |[ concat(Old, New) ] +--+                    |   |
   |   |  +-->| New |                     |  |                    |   |
   |   |  |   +-----+---------------------+  |                    |   |
   |   |  |                                  |                    |   |
   |   |  |      +----------------+----------+                    |   |
   |   |  |      |                |                               |   |
   |   |  |      |                |   +-----------------------+   |   |
   |   |  |      |                |   |        Formula        |   |   |
   |   |  |      |                |   +---------+-------------+   |   |
   |   |  |      |   +---------+  +-->| Queue   |    | Result +-->| X |
   |   |  |      +-->| totSize +----->| Size    |    |        |   |   |
   |   |  |          +---------+  +-->| MaxSize |    |        |   |   |
   |   |  |                       |   +---------+----+--------+   |   |
   | B +--+          +---------+  |           see below           |   |
   |   |             | Integer |  |                               |   |
   |   |             +---------+  |                               |   |
   |   |             | 20      +--+                               |   |
   |   |             +---------+                                  |   |
   |   |                                                          |   |
   +---+----------------------------------------------------------+---+
 
The Formula on the output truncates the queue with the expression:

   (Size <= MaxSize ? Queue : Queue[Size-MaxSize:Size-1])
 
The queue length is given by the Integer constant, but you could also put it in a Global constant if you wanted to set it elsewhere.

* For yet another example, a VEE user wanted to find a particular value in an array. The obvious answer was to use a Comparator, but another clever user also point out that if he simply wanted to see if the value existed, he could use an If/Then/Else object as follows:


                 +------------------------------------+
                 |            If/Then/Else            |
                 +---+------------------------+-------+
        array -->| A |[ product(A - B) == 0 ] | True  |
   test value -->| B |                        | False |
                 +---+------------------------+-------+
 
The idea is that if there is any value in array A that exactly equals the test value B, subtracting A - B will give an array with a zero in it, and taking the product of all those elements in the array will give a zero result as well.

Of course, this same trick can be used in a formula box:


                 +-------------------------------------------------+
                 |                   If/Then/Else                  |
                 +---+---------------------------------------------+
        array -->| A |[ (product(A - B) == 0 ? "True" : "False") ] +-->
   test value -->| B |                                             |
                 +---+---------------------------------------------+
 
* For a final example in this section, here's one that is extremely devious and leverages off the operation of transaction objects, rather than array functions as such.

Suppose you want to convert a vector (one-dimensional array) into a matrix (two-dimensional array). All you have to do is run the vector through a From String and specify the array format you like as an output. For example (see xarrange.vee for the source):


                         +--------------------------------+
                         |           From String          |
   +-----------------+   +----------------------------+---+
   | ramp( 9, 0, 8 ) +-->| READ TEXT x REAL ARRAY:*,3 | x +-->
   +-----------------+   +----------------------------+---+
 
This takes a vector of the format:

   0,1,2,3,4,5,6,7,8
 
-- and converts it to a matrix of the format:

   0,1,2
   3,4,5
   6,7,8
 
If you don't like this row-ordered format, you can convert it to a column-ordered format with the "transpose()" (matrix transpose) function:

   0,3,6
   1,4,7
   2,5,8
 
VEE's automatic data conversion ability makes this possible.

 TOP OF PAGE

[4.3] ADVANCED ARRAY OPERATIONS

* One of the trickier scenarios in VEE array operations that it can nonetheless often deal with are operations where comparisons must be made on entire arrays of data.

For example, consider a scenario where a VEE user wants to take several data sets from a device and get a resulting data set that consists of the maximum values from all the individual data sets.

The following demo program -- the program source is in the file xarrgtmx.vee -- shows off the method required:


   +-----------+    +-------------+
   |   Noise   |    | Set Global  |
   | Generator +--->+-------------+
   +-----------+    |[  sumdata  ]|
                    +------+------+
                           |
         +-----------------+                 +------+ OK Button
         |                                   | Quit +--+
    +----+----+                              +------+  |
    |  Until  |                                        |
    |  Break  +--+                                 +---+---+
    +---------+  |                                 | Stop  |
		 |                                 +-------+
           +-----+-----+
           | Get Data  +--+
           +-----------+  |
             OK Button    |             +---------+   +------------+
                    +-----+-----+       | Formula |   | Set Global |
                    |   Noise   |       +---+-+---+   +------------+
                    | Generator +------>| A | | R +-->|[ sumdata  ]|
                    +-----+-----+   +-->| B | |   |   +------------+
                          |         |   +---+-+---+           
                   +------+------+  |
                   | Get Global  |  |
                   +-------------+  |
                   |[  sumdata  ]+--+
                   +-------------+
 
This program simulates input data by using a Noise Generator. It gets an initial data set and puts it in a global variable named "sumdata", then goes into a loop where to obtain new data.

Hitting the Get Data button gets a new waveform from a Noise Generator, then recovers the data in "sumdata" with a Get Global object. These two waveforms are summed into a Formula box, which processes them and shoves the result back into "sumdata" with a Set Global.

The Formula box is the key. It accepts the new data on pin A and "sumdata" on pin B, and all it has in it is:


   clipLower(A, B)
 
This gives a result array with the value of A if A > B, and the value of B otherwise. If you used "clipUpper" instead, you would obtain minimum values.

* As another (somewhat trickier) example, a user wanted (as a benchmark) to compare two arrays of random numbers and determine how many numbers in the first array were greater than those in the second array.

Comparing arrays would seem difficult, since the relational operators ("==", "!=", "<=", and so on) and the triadic operator ("A < B ? C : D") will only return scalar values (the original lab team decided that the meaning of these functions for arrays was ambiguous), but there are ways to fake it. The test was performed with the following program -- see xarrrncp.vee for the source:


           +-------+
           | Label |
           +---+---+                            +--------------+
               |                                |    Timer     |
	       +------------------------------->+--------------+
               |                                |              |
       +-------+-------+                    +-->+--------------+
       | randomSeed()  |                    |
       +-------+-------+                    |
               |                            |   +--------------+
       +-------+-------+                    |   | AlphaNumeric |
       |  randomize()  +------>+---------+  |   +--------------+
       +-------+-------+       | Formula +--|-->|      510     |
               |          +--->+----+----+  |   +--------------+
       +-------+-------+  |         |       |
       |  randomize()  +--+         +-------+
       +---------------+
 
The "randomSeed()" function seeds the random-number generator with a seed that varies rapidly with time:

   randomSeed((10^9)*fracPart(now()/100))
 
This ensures that the data varies between different runs of the program. The two "randomize()" functions each contain the expression:

  randomize(ramp(1000,0,999), 0, 1)
 
This generates an array of 1000 random numbers in the range 0 to 1. Finally, the summation is performed by the following expression in the Formula box:

   sum(clipLower(0,signof(A-B)))
 
You might think at first that you could simply use the relational operators ("A > B") or the triadic operator ("( A > B? 1 : 0 )") to do this, but although they don't give an error message when provided with arrays as parameters, they only give a scalar result. So you have to "fake" the relational operations by other means that yield an array result. Let's see how this is done by unraveling the expression above part-by-part:

Of course, "sum()" then can add up the 1s and give the number of comparisons where A > B.

* By extension, all the conventional relational operators can be implemented using techniques demonstrated above:


   A == B:    (1-abs(signof(A-B)))
   A != B:    abs(signof(A-B))
   A >  B:    clipLower(0,signof(A-B))
   A <  B:    clipLower(0,-signof(A-B))
   A >= B:    (1-clipLower(0,-signof(A-B)))
   A <= B:    (1-clipLower(0,signof(A-B)))
 
Note how subtracting an array of 1s and 0s from 1 performs a NOT operation on the array. Similar tricks can be used for comparison with scalar values, rather than other arrays. You can also perform Boolean operations on the resulting arrays of 1s and 0s. For example, suppose that A1 and A2 are two such arrays; then the following logic operations hold:

   NOT A1:      1 - A1
   A1 AND A2:   A1 * A2
   A1 OR  A2:   signof(A1 + A2)
   A1 XOR A2:   1 - abs(signof((A1 + A2) - 1))
 
You can use the results of these computations to perform "masking" on arrays of the original values through multiplication. Those values that match to 0 are then wiped out, and those that match to 1 are retained.

For examples, check the program file xarrrelt.vee.

* However, you don't necessarily want to try to construct huge logic operations inside a formula box. The idea is mainly to eliminate or reduce loops, not come up with a minimum number of objects, and using multiple objects or implementing some of these functions in User Functions is preferable to building something that cannot be maintained. Furthermore, a good understanding of array-manipulation techniques can allow you to bypass formal logic operations and come up with a more direct solution.

For example, we had a user who had received an array of 8-bit unsigned data back from an I/O device. It had been converted by VEE into 32-bit signed integer data and he wanted to get the real values back.

This could be done by adding 256 to each value of the return array; a single formula did the task -- check the program file xarrcvt.vee for an operational example:


   (A + (clipLower(-1,clipUpper(0,A))) * (-256))
 
This expression performs the following operations:

This example could have been done using relational operations as constructed previously, but it would have been much more complicated. A little understanding of what needs to be done can lead to the more direct solution.

* Using these cute Formula box operations can lead to a myopic obsession with one's own cleverness and hideous and unnecessary complications. The Formula box can do quite a bit but it should not be regarded as the ultimate answer, and other tools should be used or added as the need arises.

A particularly useful tool for array computations is the Comparator object, which allows extraction of such array elements as meet a specific criteria. For example, one user wanted to determine the "transitions" in a stream of data. Given an example array of data as follows:


   0     0     0     0     1     1     0     0     1     1     0
 
-- what is the easiest way to find the indexes of the array elements where the value makes a transition from 0 to 1, or the reverse?

The solution is as follows -- see xarrtnst.vee for the source:


    +---------+
    | Integer |
    +---------+
    | 0000: 0 |          
    | 0001: 0 |   +-----------------------------------------------------+
    | 0002: 0 |   |                      Formula                        |
    | 0003: 0 |   +---+---------------------------------------------+---+
    | 0004: 1 +-->| A |[ A[1:(totSize(A)-1)] - A[0:(totSize(A)-2)] ]| R +--+
    | 0005: 1 |   +---+---------------------------------------------+---+  |
    | 0006: 0 |                                                            |
    | 0007: 0 |                   +----------------------------------------+
    | 0008: 1 |                   |
    | 0009: 1 |                   |    +-------------------------------+
    | 0010: 0 |                   |    |          Comparator           |
    +---------+           +---+   |    +------+-------------+----------+
                          | 0 +---|--->| Ref  |             | Passed   |
                          +---+   |    |      | Test == Ref | Failed   |
                                  +--->| Test |             | Failures +--+
                                       +------+-------------+----------+  |
                                                                          |
    +---------------------------------------------------------------------+
    |
    |                                             +----------------+
    |   +---------------+   +-----------------+   |    Indexes     |
    |   | UnBuild Coord |   |     Formula     |   +----------------+
    |   +-------+---+---+   +---+---------+---+   | 4              |
    +-->| Coord |   | X +-->| A |[ A + 1 ]| R +-->| 6              |
        | Data  |   | Y |   +---+---------+---+   | 8              |
        +-------+---+---+                         | 10             |
                                                  |                |
                                                  +----------------+
 
The key to this program is the Formula box containing the formula:

   A[1:(totSize(A)-1)] - A[0:(totSize(a)-2)]
 
To see how this works, add array indexes to the sample array listed at the start of this discussion:

   B0:0   1:0   2:0   3:0   4:1   5:1   6:0   7:0   8:1   9:1  10:0
                        ^           ^           ^           ^
 
The array indexes where a transition occurs are marked. The formula above basically performs a substraction of the input array from itself, staggered by one index, to yield a new array as follows:

   0:0   1:0   2:0   3:0   4:1   5:1   6:0   7:0   8:1   9:1  10:0
    -    0:0   1:0   2:0   3:0   4:1   5:1   6:0   7:0   8:1   9:1  10:0
         ---------------------------------------------------------
         0:0   1:0   2:0   3:1   4:0  5:-1   6:0   7:1   8:0  9:-1  
                             ^           ^           ^           ^
 
The Comparator then checks the result array to see which elements are not equal to 0. Its "Failures" pin returns an array of X-Y coordinates giving the index and value of the failure. Since we only care about the index, we use Unbuild Coord to get rid of the Y values, and then increment the resulting indexes by 1 to eliminate the effects of the staggered subtraction.

Note that the data obtained in the subtraction not only indicates the index of the transaction, but its direction -- 1 for a positive transition, -1 for a negative. Math freaks will recognize this operation as basically a difference-equation approach to performing a differentiation.

 TOP OF PAGE


 LAST PAGE  BACK TO INDEX  NEXT PAGE