Getting Started Documentation Glish Learn More Programming Contact Us
Version 1.9 Build 1556
News FAQ
Search Home


next up previous
Next: Relational and Logical Expressions Up: NOTE 216 - Lattice Expression Language Implementation Previous: Scalar Results

Optimizations

In the previous section, we considered an expression like

   a.copyData(b+min(c));

where the result of min(c) is a scalar. The Lattice expressions classes, normally evaluate their expressions chunk by chunk (tile by tile). However, it is clear that an expression like min(c) should only be evaluated once, and thereafter, for every chunk of the output Lattice, a, that pre-evaluated scalar result used.

Let us look at one of the eval function calls in LatticeExprNode. Recall that this is the function that the Lattice::copyData is eventually going to end up invoking and there is one for each data type depending upon the type of the output Lattice. Let us look at the Float version.

   void LatticeExprNode::eval (Array<Float>& result,
                               const PixelRegion& region) const
   {
      DebugAssert (dataType() == TpFloat, AipsError);
      if (!donePrepare_p) {
 
   // If first time, try to do optimization.
 
         LatticeExprNode* This = (LatticeExprNode*)this;
         LELInterface<Float>::replaceScalarExpr (This->pExprFloat_p);
         This->donePrepare_p = True;
      }
      if (isScalar()) {
         result = pExprFloat_p->getScalar();   
      } else {
         Array<Float> temp(result.shape());
         pExprFloat_p->eval(temp, region);   
         result = temp;
      }
   }

The only optimization we do at present is to replace expressions that result in a scalar by a scalar expression (LELUnaryConst).

So, the first time this function is entered, the optimization is attempted. Thereafter, the expression is just evaluated. Note that the caste is necessary to convert the const LatticeExprNode object to a non-const one so we can change it (yuck) !

The implementation of the static function LELInterface::replaceScalarExpr is

   template<class T>
   void LELInterface<T>::replaceScalarExpr (CountedPtr<LELInterface<T> >& expr)
   {
       expr->prepare();
       if (expr->isScalar()) {
           expr = new LELUnaryConst<T> (expr->getScalar());
       }
   }

So it takes a CountedPtr<LELInterface> and then does two things. First, the prepare function is called. Second, if the result of the input expression is a scalar, it evaluates the value of the scalar with the getScalar function and replaces the expression by a LELUnaryConst object of that scalar value and appropriate type.

Each of the LEL* classes derived from LELInterface has a prepare function. These either do nothing, or call replaceScalarExpr. Thus the process is recursive.

Let us consider a couple of simple examples. One that does no optimzation and one that does.

Consider the expression

   LatticeExprNode myExpr = a+b;

where a and b are Float Lattices. The tree is

  +
a   b

After construction of the tree, the LatticeExprNode object myExpr has one active LELInterface pointer, pExprFloat$\scriptstyle \tt p$, which points at the LELBinary object constructed to handle the + operation. The LELBinaryObject has two internal LELInterface pointers, one for each of the left and right expressions (call them pLeft_p and pRight_p). These pointers each point at a LELLattice object, one for each Lattice. The LELLattice objects maintain pointers to the actual Lattice objects. This is summarized in the following table.

Object Contains a Points at a Expressions
  LELInterface<Float> *    
myExpr pExprFloat_p LELBinary<Float> a+b
*pExprFloat_p pLeft_p LELLattice<Float> a
  pRight_p LELLattice<Float> b

Here is the sequence of events. The numbers indicate the layer of the tree that we have penetrated to. 1 is the top of the tree.

1. In LatticeExprNode::eval, replaceScalarExpr(myExpr.pExprFloat_p) is called. replaceScalarExpr renames the pointer passed to it in the argument list expr. This points at the LELBinary object

2. In LELInterface::replaceScalarExpr, the LELBinary object calls prepare with expr->prepare()

3. In LELBinary::prepare, replaceScalarExpr(pLeft_p) is called.

4. In LELInterface::replaceScalarExpr, the LELLattice object calls prepare with expr->prepare()

5. In LELLattice::prepare; this does nothing and we return to LELInterface::replaceScalarExpr

4. In LELInterface::replaceScalarExpr, the result of evaluating the LELLattice expression is seen to not be a scalar and we return to LELBinary::prepare

3. In LELBinary::prepare, replaceScalarExpr(pRight_p) is called.

4. In LELInterface::replaceScalarExpr, the LELLattice object calls prepare with expr->prepare().

5. In LELLattice::prepare; this does nothing and we return to LELInterface::replaceScalarExpr

4. In LELInterface::replaceScalarExpr, the result of evaluating the LELLattice expression is seen to not be a scalar and we return to LELBinary::prepare

3. In LELBinary::prepare, we return to LELInterface::replaceScalarExpr

2. In LELInterface::replaceScalarExpr, the result of evaluating the LELBinary expression is seen to not be a scalar and we return to LatticeExprNode::eval

1. In LatticeExprNode::eval we note that we have done the optimization and now evaluate the expression a+b.

The net result of all this was that nothing happened. This was because there were no scalar expressions to optimize.

Now let's consider an expression where the optimization will occur. Consider the expression

   LatticeExprNode myExpr = a+min(b);

where a and b are Float Lattices. The tree is

  +
a   min
     b

The min function returns a scalar - the minimum of the Lattice b which should be added to the pixels of Lattice a. We should be able to optimize it out of the iteration loop and replace the tree by

  +
a   constant

After construction of the tree, the LatticeExprNode object myExpr has one active LELInterface pointer, pExprFloat_p, which points at the LELBinary object constructed to handle the + operation. The LELBinaryObject has two internal LELInterface pointers, one for each of the left and right expressions (call them pLeft_p and pRight_p). pLeft_p points at a LELLattice object, for Lattice a. pRight_p points at a LELFunction1D object to handle the min function. This LELFunction1D object has a LELInterface pointer called pExpr_p which points at a LELLattice object, for Lattice b. This is summarized in the following table.

Object Contains a Points at a Expressions
  LELInterface<Float> *    
myExpr pExprFloat_p LELBinary<Float> a+min(b)
*pExprFloat_p pLeft_p LELLattice<Float> a
  pRight_p LELFunction1D<Float> min(b)
*pRight_p pExpr_p LELLattice<Float> b

Here is the sequence of events. The numbers indicate the layer of the tree that we have penetrated to. 1 is the top of the tree.

1. In LatticeExprNode::eval, replaceScalarExpr(myExpr.pExprFloat_p) is called. replaceScalarExpr calls the pointer passed to it in the argument list "expr" This points at the LELBinary object 2. In LELInterface::replaceScalarExpr, the LELBinary object calls prepare with expr->prepare()

3. In LELBinary::prepare, replaceScalarExpr(pLeft_p) is called. pLeft_p points at a LELLattice object

4. In LELInterface::replaceScalarExpr, the LELLattice object calls prepare with expr->prepare().

5. In LELLattice::prepare; this does nothing and we return to LELInterface::replaceScalarExpr

4. In LELInterface::replaceScalarExpr, the result of evaluating the LELLattice expression is seen to not be a scalar and we return to LELBinary::prepare

3. In LELBinary::prepare, replaceScalarExpr(pRight_p) is called; pRight_p points to a LELFunction1D object this time.

4. In LELInterface::replaceScalarExpr, the LELFunction1D object calls prepare with expr->prepare().

5. In LELFunction1D::prepare, replaceScalarExpr(pExpr_p) is called

6. In LELInterface::replaceScalarExpr, the LELLattice object calls prepare with expr->prepare().

7. In LELLattice::prepare; this does nothing and we return to LELInterface::replaceScalarExpr

6. In LELInterface::replaceScalarExpr, the result of evaluating the LELLattice expression is seen to not be a scalar and we return to LELFunction1D::prepare

5. In LELFunction1D::prepare, we return to LELInterface::replaceScalarExpr

4. In LELInterface::replaceScalarExpr, the result of evaluating the LELFunction1D expression IS seen to be a scalar. We evaluate that scalar value (another recursive chain) with the getScalar function via the call expr->getScalar(). We replace the object pointed at by expr (in this case, expr is pointing at a LELFunction1D object) by a LELUnaryConst object constructed with the result of the getScalar call

We return to LELBinary::prepare

3. From LELBinary::prepare, we return to LELInterface::replaceScalarExpr.

2. In LELInterface::replaceScalarExpr, the result of evaluating the LELBinary expression is seen to not be a scalar and we return to LatticeExprNode::eval

1. In LatticeExprNode::eval we note that we have done the optimization and now evaluate the expression a+constant

Note that the call to getScalar by the LELFunction1D object invokes a recursive chain as well, although it doesn't go far in this case. Let's look inside the LELFunction1D::getScalar function and see what happens there. The relevant piece is implemented according to

   template <class T>
   T LELFunction1D<T>::getScalar() const
   {
      switch(function_p) {
      case LELFunctionEnums::MIN1D :
      {
         if (pExpr_p->isScalar()) {
            return pExpr_p->getScalar();
         }   
         Bool firstTime = True;
         T minVal = T();
         LatticeExpr<T> latExpr(pExpr_p, 0);
         RO_LatticeIterator<T> iter(latExpr, latExpr.niceCursorShape());
         while (! iter.atEnd()) {
            T minv = min(iter.cursor());
            if (firstTime  ||  minv < minVal) {
               firstTime = False;
               minVal = minv;
            }
            iter++;
         }
         return minVal;
      }

The LELInterface pointer pExpr_p, is pointing at a LELLattice object, which was constructed from the actual Lattice, b. First it looks to see whether the expression in hand, the LELLattice expression, is a scalar or not. If it is, it finds the value and returns. For example, if we had asked for min(2.0) this would happen. If it isn't, then we continue on. Now again, pExpr_p is pointing at a LELLattice object (derived from LELInterface). But that is not a Lattice; we need to get at the Lattice from which it was constructed. Since we are inside class LELFunction1D, we can't get at the pointer inside the LELLattice class which does point at the Lattice. Thus, instead, we construct a LatticeExpr<T> object (which does inherit from Lattice) from the LELLattice.

This happens with the constructor

   LatticeExprNode(LELInterface<Float>* expr);

which makes a LatticeExprNode. From there it is converted to a LatticeExpr via the casting operators described in section 2.1 Then it is a simple matter to create a Lattice iterator, iterate through the Lattice (via the LatticeExpr) and work out the minimum value.


next up previous
Next: Relational and Logical Expressions Up: NOTE 216 - Lattice Expression Language Implementation Previous: Scalar Results
Please send questions or comments about AIPS++ to aips2-request@nrao.edu.
Copyright © 1995-2000 Associated Universities Inc., Washington, D.C.

Return to AIPS++ Home Page
2006-10-15