Expressions and functions

The concepts

Expressions are numerical operations on collections. The result of an expression can be another collection or can be a scalar. For example, when v and w are a dense_vector:

The way expressions are evaluated depends on the collections that participate. For example, the norm_2 function of a vector can be computed in various ways. We can use the GLAS implementation or we can use a BLAS call. In order to choose between those we attach attributes to a collection in order to make this decision. Similarly, the implementation of the dot product depends on whether the arguments are dense, sparse or one sparse and one dense.

In order to illustrate the mechanism of the expression evaluation system, suppose we want to apply a unary function op(v) to a Collection v.

Attributes

Each collection takes an attribute. For a model C of Collection, the attribute can be computed with the meta function attribute<C>::type. An attribute must be a model of Attribute.

The GLAS Collections have an attribute of the form attribute_list. For example, a dense_vector that is supposed to use BLAS, has the attribute attribute_list<dense_collection_type,column_orientation,blas_implementation_type>. The dispatcher of expressions recognizes this attribute and calls the appropriate function.

Evaluation of the function op

The evaluation of functions uses two structs: one that extracts the information needed for the dispatching, and one that computes the function, using this dispatching information.

For the operation op(v), we define a UnaryFunction struct op_function<Collection> {};. The function op is defined as

typename op_function<Collection>::result_type op( Collection const& collection ) {
  return op_function<Collection>::apply( collection ) ;
}

The UnaryFunction op_function<Collection> is derived from

template <class Collection, class SomeAttributeInformation>
struct op_function_impl
{};
This struct is specialized for the different choices of SomeAttributeInformation.

For SomeAttributeInformation, we can use attribute<Collection>::type, but we do not use this choice.

Instead, we define the MetaFunction op_attribute<Attribute> that maps the Collection's attribute to SomeAttributeInformation that we need in op_function_impl. For the specialization of op depending on the attribute, we thus use

template <class Collection>
struct op_function
: op_function_impl<Collection, typename op_attribute< typename attribute<Collection>::type>::type >
{};

Why do we need the op_attribute Meta function? Suppose the user extends GLAS with some functionalities, e.g. distributed collections. Then he may wish to use the same function names, op but with his own implementation for some of his own Collections. In this case, he can create his own Attribute. If the attribute is a model of Attribute, GLAS interprets op in the way other GLAS collections are interpreted. Only when op_attribute is specialized for the user's attribute, the behaviour is going to be different. Without this class, the user would have to specialize all GLAS functions using attributes for the user defined attributes, even for those the user does not want to change. This is overkill. The proposed mechanism allows to specialize only those functions that need to be specialized. It is the user's responsibility not to forget any to be specialized.

Combining different value_type's of collections

The value_type of a function usually is the value_type of the arguments. If the arguments, e.g. of a binary function, have the same value_type's, the result_type depends on this common value_type.

If the value_type is different, the resulting value_type usually depends on the value_type of x+y or x*y. The function operator_plus_function<X,Y>::result_type is the type of the result of x+y where x and y have types X and Y respectively. The function operator_times_function<X,Y>::result_type is the type of the result of x*y where x and y have types X and Y respectively.

The computation of the result_type uses typeof.hpp for integral and float types. For other types, the user must provide the specializations. For, std::complex types, the specialization is included by complex.hpp