Introduction to Glas

Abstract: Glas is a generic library that provides easy to use operations on dense and sparse vectors and matrices. Like many numerical libraries, glas uses expression templates for performance reasons. Additionally glas provides a mechanism to automatically dispatch expressions to vendor-tuned third-party libraries (backends) like BLAS, VSIPL++ etc. To make these expressions as intuitive as possible in a wide range of domains (linear-algebra, DSP, ...) toolboxes are built on top of the core-library that implement domain-specific conventions.

The GLAS core contains expressions (these are objects), functions to create and evaluate expressions, backends for the implementation of the evaluation of expressions, views, and containers. Each backend may have additional concepts for efficient evaluation.

Expressions

The standard defines an expression as: a sequence of operators and operands that specifies a computation. In expressions that contain glas-objects, the operands are scalars, vectors or matrices and the operators can be a matrix-product or an elementwise-vector-addition etc. Depending on the expression's result, expressions can be further categorized in ScalarExpression, VectorExpression and MatrixExpression. Depending on the expression's number of arguments, we also categorize expressions in UnaryExpression's and BinaryExpression's.

Through the use of expression templates, expressions are prevented from being evaluated pairwise. Every expression induces the creation of an object that allows to compute the whole expression without any (non-scalar) temporaries. For example:

  dense_vector<double> v(5), w(5) ;
  scalar_vector_mult( vector_norm_2(v), w ) ;
The second line in the code-snippet above creates the object
  scalar_vector_mult_expression< vector_norm_2_expression< dense_vector<double> >, dense_vector<double> >

There are 2 ways to trigger the object to evaluate the expression:

  1. The object representing the expression can be assigned as a whole to a collection:
      dense_vector<double> y(5), v(10), w(5) ;
      assign(y, scalar_vector_mult( norm_1(v), w ) ) ;
    
  2. or the user can access each element of the expression individually:
      dense_vector<double> v(5),w(5) ;
      std::cout << scalar_vector_mult( norm_1(v), v )[3] << '\n' ;
    
For scalar expressions both cases above are identical. For vector- and matrix-expressions the first case might be implemented using the element access operator, looping over all elements of the righ-hand-side (RHS) and assigning each of these to the corresponding element on the left-hand-side (LHS) but this is not necessarily so.

Backends

There exist already many vendor-tuned optimised numerical libraries that obtain close to peak performance. Instead of trying to reinvent the wheel, GLAS wants to build on the performance provided by these third-party libraries (backends). Through generics the glas-expressions are a superset of the expressions supported by each backend. When assigning an expression, a preferred backend can be specified. Glas then tries and dispatches the expression to the specified backend. If the expression is not supported, GLAS can fallback on its default generic implementation.

For instance the assignment in following code snippet

  dense_vector<double> v(10), w(10) ;
  plus_assign<blas>(v, scalar_vector_mult( 3.0, w ) ) ;
dispatches the evaluation to the BLAS daxpy function call. Likewise
  dense_vector<double> v(10) ;
  mult_assign<vsiplpp>(v, 3.0 ) ;
dispatches the evaluation to a VSIPL++ mul call. However not all expressions are supported by all backends. Adding a vector of integers to a vector of doubles is not supported by BLAS
  dense_vector<double> v(10) ;
  dense_vector<int>    s(10) ;
  plus_assign<blas>(v, s ) ;
and thus the glas-expression-evaluation will be used as a fallback. However adding two sparse matrices is neither supported. A more intelligent BLAS-backend might however rely on BLAS-calls to perform the requested operation.

Toolbox

Note that in neither of the examples above we have used operators. The interpretation of operators is one of those things that strongly depends on the application-domain glas is being used for. For instance the operation v * w, where v and w are both vectors, can be interpreted as a inner-product (or dot product), as an elementwise product or an outer product. Therefore glas is split in a core library and multiple toolboxes.

Toolboxes are intendent to improve the convenience and ease-of-use while the syntax of the core-library interface is less intuitive but more explicit.

Relying on an assign function for instance instead of on the assignment operator allows to specify an assignment-policy. For instance when assigning a sparse matrix to another sparse matrix the assignment-policy can allow the structure of the LHS to be modified or not.

Further reading