Getting Started | Documentation | Glish | Learn More | Programming | Contact Us |
Version 1.9 Build 1556 |
|
There are a few other specialized classes because not all possible data types can be handled by some functions. For example, LELFunctionReal1D is a specialized version of LELFunction1D. The former exists to handle functions such as asin, acos etc which only function with real data.
Similarly, the classes LELFunction{Float,Double,Complex,DComplex} exist to handle functions with an arbitrary number of arguments for each data type. Probably some of these could be combined into a templated class in the same way as the 1-argument LELFunction*1D classes, but there is enough difference between them to make this worthwhile.
Deserving of special mention for their cunning implementation are the functions, nelements, ntrue, and nfalse. These are implemented in LELFunctionDouble, which is not templated and it inherits from LELInterface<Double>.
Consider the expression
Lattice<Bool> b; Lattice<Double> a; a.copyData(nelements(b));
Function nelements operates on a Lattice of any data type, and returns the number of elements in the Lattice in a Double. LELFunctionDouble is not templated, and yet this function handles Lattices of any type. It is implemented directly in LatticeExprNode
LatticeExprNode nelements(const LatticeExprNode& expr) { Block<LatticeExprNode> arg(1, expr); return new LELFunctionDouble (LELFunctionEnums::NELEM, arg); }
The new statement creates a pointer to a LELFunctionDouble object, which inherits from LELInterface<Double>. This is then automatically converted to a LatticeExprNode by the constructor
LatticeExprNode(LELInterface<Double>* expr);
Now, LELFunctionDouble knows that the result of function nelements is a scalar, so it is only implemented in getScalar. The implementation in LELFunctionDouble::getScalar is
case LELFunctionEnums::NELEM : if (arg_p[0].isScalar()) { return 1; } return arg_p[0].shape().product();
arg_p[0] is the first element in a Block<LatticeExprNode>. In our example, it is a LatticeExprNode housing the LELLattice object that is needed to access the Lattice<Bool> (b). Now recall that LELLattice is fully templated, so it can of course handle any type of Lattice. But LELFunctionDouble doesn't know anything at all about the type of this Lattice in the path that is followed for this function; all type checking is bypassed. The statement arg_p[0].shape().product() invokes the appropriate LatticeExprNode function to return the shape attribute.
These functions only work on Bool Lattices and count up the number of True or False values. Like nelements they are implemented directly from LatticeExprNode. E.g.
LatticeExprNode ntrue (const LatticeExprNode& expr) { AlwaysAssert (expr.dataType() == TpBool, AipsError); Block<LatticeExprNode> arg(1, expr); return new LELFunctionDouble(LELFunctionEnums::NTRUE, arg); }
Immediately though a test is made for the type of the expression that is having the function applied to it. If it's not a Bool, an exception is thrown. Otherwise we proceed into LELFunctionDouble again. Since the result is a scalar they are only implemented in LELFunctionDouble::getScalar For example, for ntrue
switch (function_p) { case LELFunctionEnums::NTRUE : { uInt ntrue = 0; Bool deleteIt; LatticeExpr<Bool> latExpr(arg_p[0], 0); RO_LatticeIterator<Bool> iter(latExpr, latExpr.niceCursorShape()); while (! iter.atEnd()) { const Array<Bool>& array = iter.cursor(); const Bool* data = array.getStorage (deleteIt); uInt n = array.nelements(); for (uInt i=0; i<n; i++) { if (data[i]) { ntrue++; } } array.freeStorage (data, deleteIt); iter++; } return ntrue; }A LatticeExpr<Bool> (which is a Lattice) is explicitly created from the LatticeExprNode via the constructor. This is then iterated through to get implement the function.