Chapter 6: Indirection




6.1: Indirection

Indirection is the ability to modify or access an array at a set of selected index values. Blitz++ provides several forms of indirection:

  • Using a list of array positions: this approach is useful if you need to modify an array at a set of scattered points.

  • Cartesian-product indirection: as an example, for a two-dimensional array you might have a list I of rows and a list J of columns, and you want to modify the array at all (i,j) positions where i is in I and j is in J. This is a cartesian product of the index sets I and J.

  • Over a set of strips: for efficiency, you can represent an arbitrarily-shaped subset of an array as a list of one-dimensional strips. This is a useful way of handling Regions Of Interest (ROIs).

  • Figure 5 is shown here.
    Figure 5: Three styles of indirection (from top to bottom): (1) using a list of array positions; (2) Cartesian-product indirection; (3) using a set of strips to represent an arbitrarily-shaped subset of an array


    In all cases, Blitz++ expects a Standard Template Library container. Some useful STL containers are list<>, vector<>, deque<> and set<>. Documentation of these classes is often provided with your compiler, or see also the good documentation at http://www.sgi.com/Technology/STL/. STL containers are used because they are widely available and provide easier manipulation of "sets" than Blitz++ arrays. For example, you can easily expand and merge sets which are stored in STL containers; doing this is not so easy with Blitz++ arrays, which are designed for numerical work.

    STL containers are generally included by writing

    #include <list>   // for list<>
    #include <vector> // for vector<>
    #include <deque>  // for deque<>
    #include <set>    // for set<>
    

    The [] operator is overloaded on arrays so that the syntax array[container] provides an indirect view of the array. So far, this indirect view may only be used as an lvalue (i.e. on the left-hand side of an assignment statement).

    The examples in the next sections are available in the Blitz++ distribution in <examples/indirect.cpp>.




    6.2: Indirection using lists of array positions

    The simplest kind of indirection uses a list of points. For one-dimensional arrays, you can just use an STL container of integers. Example:

      Array<int,1> A(5), B(5);
      A = 0;
      B = 1, 2, 3, 4, 5;
    
      vector<int> I;
      I.push_back(2);
      I.push_back(4);
      I.push_back(1);
    
      A[I] = B;
    
    After this code, the array A contains [ 0 2 3 0 5 ].

    Note that arrays on the right-hand-side of the assignment must have the same shape as the array on the left-hand-side (before indirection). In the statement "A[I]=B", A and B must have the same shape, not I and B.

    For multidimensional arrays, you can use an STL container of TinyVector<int,N_rank> objects. Example:

      Array<int,2> A(4,4), B(4,4);
      A = 0;
      B = 10*tensor::i + tensor::j;
    
      typedef TinyVector<int,2> coord;
    
      list<coord> I;
      I.push_back(coord(1,1));
      I.push_back(coord(2,2));
    
      A[I] = B;
    
    After this code, the array A contains:

      0   0   0   0
      0  11   0   0
      0   0  22   0
      0   0   0   0
    

    (The tensor::i notation is explained in the section on index placeholders 3.6).




    6.3: Cartesian-product indirection

    The Cartesian product of the sets I, J and K is the set of (i,j,k) tuples for which i is in I, j is in J, and k is in K.

    Blitz++ implements cartesian-product indirection using an adaptor which takes a set of STL containers and iterates through their Cartesian product. Note that the cartesian product is never explicitly created. You create the Cartesian-product adaptor by calling the function:

    template<class T_container>
    indexSet(T_container& c1, T_container& c2, ...)
    
    The returned adaptor can then be used in the [] operator of an array object.

    Here is a two-dimensional example:

      Array<int,2> A(6,6), B(6,6);
      A = 0;
      B = 10*tensor::i + tensor::j;
    
      vector<int> I, J;
      I.push_back(1);
      I.push_back(2);
      I.push_back(4);
    
      J.push_back(0);
      J.push_back(2);
      J.push_back(5);
    
      A[indexSet(I,J)] = B;
    
    After this code, the A array contains:
     0   0   0   0   0   0
    10   0  12   0   0  15
    20   0  22   0   0  25
     0   0   0   0   0   0
    40   0  42   0   0  45
     0   0   0   0   0   0
    
    All the containers used in a cartesian product must be the same type (e.g. all vector<int> or all set<TinyVector<int,2> >), but they may be different sizes. Singleton containers (containers containing a single value) are fine.




    6.4: Indirection with lists of strips

    You can also do indirection with a container of one-dimensional strips. This is useful when you want to manipulate some arbitrarily-shaped, well-connected subdomain of an array. By representing the subdomain as a list of strips, you allow Blitz++ to operate on vectors, rather than scattered points; this is much more efficient.

    Strips are represented by objects of type RectDomain<N>, where N is the dimensionality of the array. The RectDomain<N> class can be used to represent any rectangular subdomain, but for indirection it is only used to represent strips.

    You create a strip by using this function:

    RectDomain<N> strip(TinyVector<int,N> start,
        int stripDimension, int ubound);
    
    The start parameter is where the strip starts; stripDimension is the dimension in which the strip runs; ubound is the last index value for the strip. For example, to create a 2-dimensional strip from (2,5) to (2,9), one would write:
    TinyVector<int,2> start(2,5);
    RectDomain<2> myStrip = strip(start,secondDim,9);
    
    Here is a more substantial example which creates a list of strips representing a circle subset of an array:
      const int N = 7;
      Array<int,2> A(N,N), B(N,N);
      typedef TinyVector<int,2> coord;
    
      A = 0;
      B = 1;
    
      double centre_i = (N-1)/2.0;
      double centre_j = (N-1)/2.0;
      double radius = 0.8 * N/2.0;
    
      // circle will contain a list of strips which represent a circular
      // subdomain.
    
      list<RectDomain<2> > circle;
      for (int i=0; i < N; ++i)
      {
        double jdist2 = pow2(radius) - pow2(i-centre_i);
        if (jdist2 < 0.0)
          continue;
    
        int jdist = int(sqrt(jdist2));
        coord startPos(i, int(centre_j - jdist));
        circle.push_back(strip(startPos, secondDim, int(centre_j + jdist)));
      }
    
      // Set only those points in the circle subdomain to 1
      A[circle] = B;
    
    After this code, the A array contains:
      0  0  0  0  0  0  0
      0  0  1  1  1  0  0
      0  1  1  1  1  1  0
      0  1  1  1  1  1  0
      0  1  1  1  1  1  0
      0  0  1  1  1  0  0
      0  0  0  0  0  0  0