MyraMath
Tutorial for /expression templates.

Overview

The /expression folder contains Expression templates. For the most part, the containers in MyraMath (like Matrix and Vector) are designed to behave like regular types with value semantics. A compound expression that composes Matrix's together, like Z = X + make_complex(A,B) - 0.5*Y;, will result in the construction of several temporaries for subexpressions like 0.5*Y and make_complex(A,B). All of these temporaries are RAII-managed, and do properly release their resources when they "expire at the semicolon" after Z has been populated. However these temporaries might be frustrating for the performance-minded developer, because it's trivial to write a double-for loop with the exact same semantics that would use only O(1) additional stack space (and no heap allocation). Expression templates are a mechanism to combine the best features of both approaches: high readability via compact mathematical notation (no explicit loops), but only requiring O(1) additional space (no Matrix temporaries).

Expression templates are syntactic sugar for performing entrywise mathematical operations upon numeric containers. Expression's can be composed using the usual arithmetic operations (summation, scaling, entrywise multiplication, entrywise quotient). Many of the common transcendental functions (trignometric functions, exponential functions, etc) are overloaded for Expression's. Numeric-valued Expression's can compared to one another using relational operators (greater, less) to yield boolean-valued Expression's, which can be further composed using logical operators (and, or, not, ternary). It's also straightforward to adapt end-user code into an Expression.

Eventually, Expression's get captured into a real containers (like Matrix and Vector) by evaluate()'ing them. Additionally, most containers offer constructors and assignment operators that accept Expression's as right-hand-side values.

Below are links to the sections of this page:

Basic Expression's

Conceptually, an Expression represents a mapping function from integer indices into Number values. An Expression is defined by:

Any Expression can be printed via std::cout << e, but note that you need to include expression/print.h to do so. The << operator will print the Arity/size/Number attributes, and also print the results of evaluating the Expression over all possible indices. The example program below (tutorial/expression/basic.cpp) explores some basic Expression's:

3 #include <myramath/dense/expr.h>
4 
10 
11 #include <tests/myratest.h>
12 
13 #include <iostream>
14 
15 using namespace myra;
16 
17 ADD_TEST("expression_basic","[expression]") // int main()
18  {
19  // Expressions that return constant values.
20  auto E1 = make_ConstantExpression(3,2,1.0);
21  std::cout << "make_ConstantExpression(3,2,1.0) = " << E1 << std::endl;
22  // An Expression that generates random complex values.
23  auto E2 = make_RandomExpression<std::complex<double> >(2,4);
24  std::cout << "make_RandomExpression<std::complex<double> >(2,4) = " << E2 << std::endl;
25  // An Expression that interpolates linearly between two endpoints.
26  auto E3 = make_LinspaceExpression(1.0f,2.0f,11);
27  std::cout << "make_LinspaceExpression(1.0f,2.0f,11) = " << E3 << std::endl;
28  // Can evaluate() an Expression into a container.
29  auto x = Vector<float>::evaluate(E3);
30  std::cout << "Vector<float>::evaluate() = " << x << std::endl;
31  // Adapting a container into an Expression.
32  auto A = Matrix<double>::fill_cmajor(3,3,{1,4,2, 8,5,7, 3,9,6});
33  std::cout << "A = " << A << std::endl;
34  std::cout << "expr(A) = " << expr(A) << std::endl;
35  // Expressions can be further manipulated without change to underlying A.
36  std::cout << "reshape(expr(A),1,9) = " << reshape(expr(A),1,9) << std::endl;
37  std::cout << "A = " << A << std::endl;
38  // Expressions can be built from std::vector's and std::initializer_list's
39  std::cout << "expr({3.0f,1.0f,2.0f}) = " << expr({3.0f,1.0f,2.0f}) << std::endl;
40  }
Reshapes an Expression (for instance, reinterpret an arity-1 Expression of size 25 into an arity-2 Ex...
Overloads expr() for std::vector<Number> and (C++11 only) std::initializer_list<Number> ...
Generators for basic Expression&#39;s (constant, random, linspace, etc).
An interface used to fill containers from Expression&#39;s (see Matrix::evaluate(), for example)...
Definition: syntax.dox:1
Overloads expr() for Matrix<Number>, LowerMatrix<Number>, Vector<Number> and DiagonalMatrix<Number> ...
General purpose dense matrix container, O(i*j) storage.
Container for either a column vector or row vector (depends upon the usage context) ...
static Matrix< Number > fill_cmajor(int I, int J, std::initializer_list< Number > list)
Generates a Matrix of specified size, filled from a std::initializer_list.
Definition: Matrix.cpp:370
static Vector< Number > evaluate(const Expression< 1, Number > &e)
Generates a Vector by evaluating an arity-1 Expression of Number.
Definition: Vector.cpp:313
make_ConstantExpression(3,2,1.0) = size 3 by 2 arity-2 Expression of double:
[ 1 1 ]
[ 1 1 ]
[ 1 1 ]
make_RandomExpression<std::complex<double> >(2,4) = size 2 by 4 arity-2 Expression of std::complex<double>:
[ (0.380002,0.0108368) (0.182981,0.10957) (-0.243142,-0.484536) (-0.585236,0.252524) ]
[ (-0.319746,0.687704) (-0.862444,-0.180186) (0.759988,-0.361039) (0.961136,-0.82999) ]
make_LinspaceExpression(1.0f,2.0f,11) = size 11 arity-1 Expression of float:
[ 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2 ]
Vector<float>::evaluate() = size 11 Vector of float:
[ 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2 ]
A = size 3 by 3 Matrix of double:
[ 1 8 3 ]
[ 4 5 9 ]
[ 2 7 6 ]
expr(A) = size 3 by 3 arity-2 Expression of double:
[ 1 8 3 ]
[ 4 5 9 ]
[ 2 7 6 ]
reshape(expr(A),1,9) = size 1 by 9 arity-2 Expression of double:
[ 1 4 2 8 5 7 3 9 6 ]
A = size 3 by 3 Matrix of double:
[ 1 8 3 ]
[ 4 5 9 ]
[ 2 7 6 ]
expr({3.0f,1.0f,2.0f}) = size 3 arity-1 Expression of float:
[ 3 1 2 ]

Some explanatory remarks follow:

Expression arithmetic

Expression's can be composed with the usual arithmetic operators (+,-,*,/), but bear in mind that they always operate entrywise upon their underlying operands. For example, the product of two Expression's E1*E2 behaves like the Matlab operation E1.*E2, not like gemm(). The program below (tutorial/expression/arithmetic.cpp) shows how Expression's can be combined entrywise using the (+,-,*,/) operators:

5 
6 #include <tests/myratest.h>
7 
8 #include <iostream>
9 #include <typeinfo>
10 
11 using namespace myra;
12 
13 ADD_TEST("expression_arithmetic","[expression]") // int main()
14  {
15  // Make two arbitrary Expression's by reshaping std::initializer_list's.
16  auto E1 = reshape(expr({1.0,3.0,4.0,9.0,2.0,6.0}),2,3);
17  auto E2 = reshape(expr({7.0,4.0,2.0,8.0,5.0,9.0}),2,3);
18  std::cout << "E1 = " << E1 << std::endl;
19  std::cout << "E2 = " << E2 << std::endl;
20  // Examine various arithmetic compositions of E1 and E2.
21  std::cout << "E1+E2 = " << E1+E2 << std::endl;
22  std::cout << "E1-E2 = " << E1-E2 << std::endl;
23  std::cout << "E1*E2 = " << E1*E2 << std::endl;
24  std::cout << "E1/E2 = " << E1/E2 << std::endl;
25  std::cout << "-E1 = " << -E1 << std::endl;
26  std::cout << "1/E2 = " << 1/E2 << std::endl;
27  // Can also compose an Expression with a scalar constant.
28  std::cout << "2*E1 = " << 2*E1 << std::endl;
29  std::cout << "E2/2 = " << E2/2 << std::endl;
30  std::cout << "E1+3 = " << E1+3 << std::endl;
31  std::cout << "3+E1 = " << 3+E1 << std::endl;
32  std::cout << "E2-4 = " << E2-4 << std::endl;
33  std::cout << "4-E2 = " << 4-E2 << std::endl;
34  // Can build up arbitrary Expression's, normal operator precedence rules apply.
35  std::cout << "(10-2*E1+3*E2)/4 = " << (10-2*E1+3*E2)/4 << std::endl;
36  }
Reshapes an Expression (for instance, reinterpret an arity-1 Expression of size 25 into an arity-2 Ex...
Overloads expr() for std::vector<Number> and (C++11 only) std::initializer_list<Number> ...
Definition: syntax.dox:1
Arithmetic operators (+,-,*,/) for Expression&#39;s.
E1 = size 2 by 3 arity-2 Expression of double:
[ 1 4 2 ]
[ 3 9 6 ]
E2 = size 2 by 3 arity-2 Expression of double:
[ 7 2 5 ]
[ 4 8 9 ]
E1+E2 = size 2 by 3 arity-2 Expression of double:
[ 8 6 7 ]
[ 7 17 15 ]
E1-E2 = size 2 by 3 arity-2 Expression of double:
[ -6 2 -3 ]
[ -1 1 -3 ]
E1*E2 = size 2 by 3 arity-2 Expression of double:
[ 7 8 10 ]
[ 12 72 54 ]
E1/E2 = size 2 by 3 arity-2 Expression of double:
[ 0.142857 2 0.4 ]
[ 0.75 1.125 0.666667 ]
-E1 = size 2 by 3 arity-2 Expression of double:
[ -1 -4 -2 ]
[ -3 -9 -6 ]
1/E2 = size 2 by 3 arity-2 Expression of double:
[ 0.142857 0.5 0.2 ]
[ 0.25 0.125 0.111111 ]
2*E1 = size 2 by 3 arity-2 Expression of double:
[ 2 8 4 ]
[ 6 18 12 ]
E2/2 = size 2 by 3 arity-2 Expression of double:
[ 3.5 1 2.5 ]
[ 2 4 4.5 ]
E1+3 = size 2 by 3 arity-2 Expression of double:
[ 4 7 5 ]
[ 6 12 9 ]
3+E1 = size 2 by 3 arity-2 Expression of double:
[ 4 7 5 ]
[ 6 12 9 ]
E2-4 = size 2 by 3 arity-2 Expression of double:
[ 3 -2 1 ]
[ 0 4 5 ]
4-E2 = size 2 by 3 arity-2 Expression of double:
[ -3 2 -1 ]
[ 0 -4 -5 ]
(10-2*E1+3*E2)/4 = size 2 by 3 arity-2 Expression of double:
[ 7.25 2 5.25 ]
[ 4 4 6.25 ]

Note that Expression's can also be combined (added, subtracted, etc) with scalar constants too.

Transcendental functions

Within /expression/functions.h are routines to apply transcendental functions to an Expression in an entrywise fashion. Most of them delegate to standard <cmath> functions of the same name. The most noteworthy functions are:

The program below (tutorial/expression/functions.cpp) demonstrates how these functions behave. Remember that anything dealing with an Expression always operates in an entrywise fashion:

3 #include <myramath/dense/expr.h>
4 
10 
11 #include <tests/myratest.h>
12 
13 #include <iostream>
14 
15 using namespace myra;
16 
17 ADD_TEST("expression_functions","[expression]") // int main()
18  {
19  // Fill Matrix A with random double's over [-pi,pi].
20  double pi = std::acos(-1.0);
21  auto A = pi*Matrix<double>::random(2,4);
22  std::cout << "A = " << A << std::endl;
23  // Fill Matrix Z with unit length complex numbers, whose phases are drawn from A.
24  auto E = make_complex( cos(expr(A)), sin(expr(A)) );
25  auto Z = Matrix< std::complex<double> >::evaluate(E);
26  std::cout << "Z = " << Z << std::endl;
27  std::cout << "abs(Z) = " << abs(expr(Z)) << std::endl;
28  std::cout << "argument(Z) = " << argument(expr(Z)) << std::endl;
29  // Fill vector x with perfect squares of integers.
30  auto x = Vector<double>::evaluate(pow(expr({1.0,2.0,3.0,4.0,5.0}),2.0));
31  std::cout << "x = " << x << std::endl;
32  std::cout << "sqrt(x) = " << sqrt(expr(x)) << std::endl;
33  // Fill Vector y with the powers of 10
34  auto y = Vector<double>::evaluate(pow(10.0,expr({-2.0,-1.0,0.0,+1.0,+2.0})));
35  std::cout << "y = " << y << std::endl;
36  std::cout << "log10(y) = " << log10(expr(y)) << std::endl;
37  }
Expression< 1, NumberS > abs(const Expression< 1, NumberS > &A)
Returns the std::abs() of an Expression.
Definition: functions_measure.cpp:29
Overloads expr() for std::vector<Number> and (C++11 only) std::initializer_list<Number> ...
Expression< 1, NumberS > pow(const Expression< 1, NumberS > &A, const Expression< 1, NumberS > &B)
Returns A^B, an Expression base raised to an Expression power.
Definition: functions_power.cpp:53
Expression< 1, NumberS > sin(const Expression< 1, NumberS > &A)
Returns the std::sin() of an Expression.
Definition: functions_trig.cpp:30
Tabulates an IxJ matrix. Allows random access, has column major layout to be compatible with BLAS/LAP...
Definition: bdsqr.h:20
static Matrix< Number > random(int I, int J)
Generates a random Matrix of specified size.
Definition: Matrix.cpp:353
An interface used to fill containers from Expression&#39;s (see Matrix::evaluate(), for example)...
Definition: syntax.dox:1
Overloads expr() for Matrix<Number>, LowerMatrix<Number>, Vector<Number> and DiagonalMatrix<Number> ...
Expression< 1, NumberS > cos(const Expression< 1, NumberS > &A)
Returns the std::cos() of an Expression.
Definition: functions_trig.cpp:60
Arithmetic operators (+,-,*,/) for Expression&#39;s.
General purpose dense matrix container, O(i*j) storage.
Container for either a column vector or row vector (depends upon the usage context) ...
Expression< 1, NumberS > sqrt(const Expression< 1, NumberS > &A)
Returns the std::sqrt() of an Expression.
Definition: functions_power.cpp:30
Function overloads (sin, exp, etc) for Expression&#39;s.
Expression< 1, NumberS > log10(const Expression< 1, NumberS > &A)
Returns the std::log10() of an Expression.
Definition: functions_exponential.cpp:89
static Vector< Number > evaluate(const Expression< 1, Number > &e)
Generates a Vector by evaluating an arity-1 Expression of Number.
Definition: Vector.cpp:313
Expression< 1, NumberC > make_complex(const Expression< 1, NumberS > &A)
Promotes a real Expression into a complex one.
Definition: functions_complex.cpp:122
A = size 2 by 4 Matrix of double:
[ 1.19381 0.574852 -0.763854 -1.83857 ]
[ 0.0340448 0.344224 -1.52221 0.793327 ]
Z = size 2 by 4 Matrix of std::complex<double>:
[ (0.368119,0.929779) (0.839273,0.543711) (0.722175,-0.69171) (-0.264587,-0.964362) ]
[ (0.999421,0.0340382) (0.941338,0.337466) (0.0485632,-0.99882) (0.701478,0.712691) ]
abs(Z) = size 2 by 4 arity-2 Expression of double:
[ 1 1 1 1 ]
[ 1 1 1 1 ]
argument(Z) = size 2 by 4 arity-2 Expression of double:
[ 1.19381 0.574852 -0.763854 -1.83857 ]
[ 0.0340448 0.344224 -1.52221 0.793327 ]
x = size 5 Vector of double:
[ 1 4 9 16 25 ]
sqrt(x) = size 5 arity-1 Expression of double:
[ 1 2 3 4 5 ]
y = size 5 Vector of double:
[ 0.01 0.1 1 10 100 ]
log10(y) = size 5 arity-1 Expression of double:
[ -2 -1 0 1 2 ]

Note it's relatively easy to add new functions of Expression's, so just ask if you find one missing that you would like added.

Comparisons and boolean Expression's

One Expression can be compared to another (or to a constant value) using relational operators like > or <, yielding an Expression of bool values. Boolean expressions can be further composed using operators like && (logical and), || (logical or) or ! (logical not). Typically the ternary operator is used to turn bool-valued C++ expressions back into numeric values, using a syntax like bool ? trueValue : falseValue. However, the ternary operator cannot be overloaded. Instead a ternary(bool's,trueValue's,falseValue's) function is provided, where all three arguments can be Expression's of the same Arity and size.

The example program below (tutorial/expression/logical.cpp) explores logical expressions (comparison, boolean composition, and ternary operations):

6 
7 #include <tests/myratest.h>
8 
10 
11 
12 #include <iostream>
13 
14 using namespace myra;
15 
16 ADD_TEST("expression_logical","[expression]") // int main()
17  {
18  // Compare expressions E1 and E2.
19  auto E1 = expr({8.0,1.0,7.0,4.0,9.0});
20  auto E2 = expr({5.0,0.0,2.0,6.0,3.0});
21  std::cout << "E1 = " << E1 << std::endl;
22  std::cout << "E2 = " << E2 << std::endl;
23  std::cout << "E1 > E2 = " << (E1 > E2) << std::endl;
24  // Compare a function of E1 and E2.
25  std::cout << "sin(E1) = " << sin(E1) << std::endl;
26  std::cout << "sin(E2) = " << sin(E2) << std::endl;
27  std::cout << "sin(E1) > sin(E2) = " << (sin(E1) > sin(E2)) << std::endl;
28  // Compose logical expressions using && and ||.
29  std::cout << "(E1 > E2) && (sin(E1) > sin(E2)) = "
30  << ((E1>E2) && (sin(E1)>sin(E2))) << std::endl;
31  std::cout << "(E1 > E2) || (sin(E1) > sin(E2)) = "
32  << ((E1>E2) || (sin(E1)>sin(E2))) << std::endl;
33  // Examine the ternary operator.
34  std::cout << "E1>E2 ? E1 : E2 = " << ternary(E1>E2, E1, E2) << std::endl;
35  // Note, E1>E2 ? E1 : E2 is equivalent to max(E1,E2), so double check.
36  std::cout << "max(E1,E2) = " << max(E1,E2) << std::endl;
37  }
Overloads expr() for std::vector<Number> and (C++11 only) std::initializer_list<Number> ...
Expression< 1, NumberS > sin(const Expression< 1, NumberS > &A)
Returns the std::sin() of an Expression.
Definition: functions_trig.cpp:30
Expression< 1, NumberS > max(const Expression< 1, NumberS > &A, const Expression< 1, NumberS > &B)
Returns the larger of two real Expression&#39;s, max(A(i),B(i))
Definition: functions_measure.cpp:83
An interface used to fill containers from Expression&#39;s (see Matrix::evaluate(), for example)...
Definition: syntax.dox:1
Comparison operators (<,>) and logical operators (&&,||,!) for Expression&#39;s.
Container for either a column vector or row vector (depends upon the usage context) ...
Function overloads (sin, exp, etc) for Expression&#39;s.
E1 = size 5 arity-1 Expression of double:
[ 8 1 7 4 9 ]
E2 = size 5 arity-1 Expression of double:
[ 5 0 2 6 3 ]
E1 > E2 = size 5 arity-1 Expression of bool:
[ 1 1 1 0 1 ]
sin(E1) = size 5 arity-1 Expression of double:
[ 0.989358 0.841471 0.656987 -0.756802 0.412118 ]
sin(E2) = size 5 arity-1 Expression of double:
[ -0.958924 0 0.909297 -0.279415 0.14112 ]
sin(E1) > sin(E2) = size 5 arity-1 Expression of bool:
[ 1 1 0 0 1 ]
(E1 > E2) && (sin(E1) > sin(E2)) = size 5 arity-1 Expression of bool:
[ 1 1 0 0 1 ]
(E1 > E2) || (sin(E1) > sin(E2)) = size 5 arity-1 Expression of bool:
[ 1 1 1 0 1 ]
E1>E2 ? E1 : E2 = size 5 arity-1 Expression of double:
[ 8 1 7 6 9 ]
max(E1,E2) = size 5 arity-1 Expression of double:
[ 8 1 7 6 9 ]

Boolean expressions and ternary() can be used to implement similar functionality to binary masking in Matlab.

User-defined Expression's

Advanced users may discover a need to adapt their own code or data structure as an Expression, see expression/UserExpression.h. For example, the excerpt below (tutorial/expression/user.cpp) adapts a user-defined matrix-like container into an Expression, which can be further composed using any of the routines enumerated above, or captured into a Matrix to feed into downstream algorithms.

2 
8 
9 #include <tests/myratest.h>
10 
11 #include <iostream>
12 
13 using namespace myra;
14 
15 namespace {
16 
17 // A strawman 3x3 Matrix class, to be adapted into an Expression.
18 class MyMatrix
19  {
20  public:
21 
22  // Constructor, initializes with zeros.
23  MyMatrix()
24  {
25  for (int m = 0; m < 3; ++m)
26  for (int n = 0; n < 3; ++n)
27  contents[m][n] = 0.0;
28  }
29 
30  // Accessing/mutating the (m,n)'th entry.
31  const double& operator() (int m, int n) const
32  { return contents[m][n]; }
33  double& operator()(int m, int n)
34  { return contents[m][n]; }
35 
36  private:
37 
38  // Internal storage.
39  double contents[3][3];
40 
41  };
42 
43 // For adapting a MyMatrix instance into an Expression.
44 class MyMatrixExpression
45  {
46  public:
47 
48  // User expressions must provide an Arity constant and Number typedef.
49  const static int Arity = 2;
50  typedef double Number;
51 
52  // User expressions must report their size. This one is 3x3.
53  Index<2> size() const
54  { return make_Index(3,3); }
55 
56  // User expressions must supply an evaluate() function that returns a Number.
57  Number evaluate(Index<2> ij) const
58  { return (*m)(ij[0],ij[1]); }
59 
60  // User expressions should capture any needed environmental references via constructor arguments.
61  MyMatrixExpression(const MyMatrix& in_m)
62  : m(&in_m) { }
63 
64  private:
65 
66  // User routines can carry member data, but they must be copy-constructible.
67  const MyMatrix* m;
68 
69  };
70 
71 } // namespace
72 
73 ADD_TEST("expression_user","[expression]") // int main()
74  {
75  // Initialize a MyMatrix instance.
76  MyMatrix M;
77  M(0,0) = 1.0; M(0,1) = 2.0; M(0,2) = 9.0;
78  M(1,0) = 4.0; M(1,1) = 6.0; M(1,2) = 5.0;
79  M(2,0) = 7.0; M(2,1) = 3.0; M(2,2) = 8.0;
80  // Adapt it using make_UserExpression()
81  auto E = make_UserExpression( MyMatrixExpression(M) );
82  std::cout << "E = " << E << std::endl;
83  // Expression E can be used within furthers compositions, and evaluate()'d
84  std::cout << "E > 4.0 = " << (E > 4.0) << std::endl;
85  auto A = Matrix<double>::evaluate( pow(E,2.0) );
86  std::cout << "Matrix::evaluate(pow(E,2)) = " << A << std::endl;
87  }
Adapts user code (encapsulated in a class) into an Expression.
Expression< UserClass::Arity, typename UserClass::Number > make_UserExpression(const UserClass &u)
Helper function to adapt some user code (encapsulated in a class) into an Expression.
Definition: UserExpression.h:58
Expression< 1, NumberS > pow(const Expression< 1, NumberS > &A, const Expression< 1, NumberS > &B)
Returns A^B, an Expression base raised to an Expression power.
Definition: functions_power.cpp:53
An interface used to fill containers from Expression&#39;s (see Matrix::evaluate(), for example)...
Definition: syntax.dox:1
Comparison operators (<,>) and logical operators (&&,||,!) for Expression&#39;s.
General purpose dense matrix container, O(i*j) storage.
static Matrix< Number > evaluate(const Expression< 2, Number > &e)
Generates a Matrix by evaluating an arity-2 Expression of Number.
Definition: Matrix.cpp:378
Function overloads (sin, exp, etc) for Expression&#39;s.
Essentially a std::array<int,N>, provided for backwards compatibility.
Definition: Index.h:23
E = size 3 by 3 arity-2 Expression of double:
[ 1 2 9 ]
[ 4 6 5 ]
[ 7 3 8 ]
E > 4.0 = size 3 by 3 arity-2 Expression of bool:
[ 0 0 1 ]
[ 0 1 1 ]
[ 1 0 1 ]
Matrix::evaluate(pow(E,2)) = size 3 by 3 Matrix of double:
[ 1 4 81 ]
[ 16 36 25 ]
[ 49 9 64 ]

User expressions should be encapsulated within a class with the following signatures:

Appendix: Regarding /sparse Expression's

This tutorial has only discussed Expression's for /dense containers. Some of the containers in /sparse can also be adapted into Expression's using expr(). For example, a SparseMatrix can be adapted into an Arity-2 Expression of Number's, and a PatternBuilder can be adapted into an Arity-2 Expression of bool's. Any structural zeros in these containers become explicit zero/false values. Such Expression's composed using the usual Expression arithmetic, and can be captured back into /dense containers using evaluate().

But to be truthful, Expression's are not particularly useful for populating /sparse containers. The naive implementation of A = SparseMatrix::evaluate(E), which would loop over all indices in E.size(), would be asymptotically inefficient. Note this algorithm would require work proportional to the size of E, instead of work proportional to the actual number of nonzeros in E. Recalling back to the /sparse tutorial, explicit looping over all (i,j) indices was discouraged in favor of SparseMatrixIterator, for essentially the same reason. The bottom line is that SparseMatrix does not provide an evaluate(E) method. Instead, it provides an evaluate(P,E) method, where P prescribes the Pattern where E should be sampled. This tends to spoil the "temporary-free" rationale behind Expression's to begin with (because the user is forced to introduce a temporary to track the Pattern of E, whenever E does anything nontrivial like compare or sum two other sparse Expression's). Often, using a SparseMatrixBuilder is the better choice.

That said, the example below (tutorial/expression/sparse.cpp) shows how to use Expression's for /sparse containers.

2 
5 #include <myramath/sparse/expr.h>
6 
10 
11 #include <tests/myratest.h>
12 
13 #include <iostream>
14 
15 using namespace myra;
16 
17 ADD_TEST("expression_sparse","[expression]") // int main()
18  {
19  // Populate a SparseMatrix from coordinate format, (i,j,value) triplets.
21  { 0 , 5 , 1 , 2 , 0 , 4 , 1 , 3 }, // i's
22  { 0 , 5 , 3 , 4 , 1 , 3 , 2 , 2 }, // j's
23  {1.0, 1.7, 2.4, 3.1, 0.8, 9.2, 1.4, 4.6} ); // value's
24  std::cout << "A = " << A << std::endl;
25  auto P = A.pattern();
26  // Examine expr(A)
27  std::cout << "expr(A) = " << expr(A) << std::endl;
28  std::cout << "expr(P) = " << expr(P) << std::endl;
29  // Populate B with the squares of A's values.
30  auto B = SparseMatrix<double>::evaluate(P, pow(expr(A),2.0) );
31  std::cout << "B = SparseMatrix::evaluate(pow(expr(A),2.0)) = " << B << std::endl;
32  std::cout << "expr(B) = " << expr(B) << std::endl;
33  }
Expression< 1, NumberS > pow(const Expression< 1, NumberS > &A, const Expression< 1, NumberS > &B)
Returns A^B, an Expression base raised to an Expression power.
Definition: functions_power.cpp:53
static SparseMatrix< Number > fill(const PatternRange &P, Number c)
Generates a SparseMatrix with Pattern P, filled with constant c.
Definition: SparseMatrix.cpp:521
An interface used to fill containers from Expression&#39;s (see Matrix::evaluate(), for example)...
General purpose compressed-sparse-column (CSC) container.
Definition: syntax.dox:1
static SparseMatrix< Number > evaluate(const PatternRange &P, const Expression< 2, Number > &e)
Generates a SparseMatrix with Pattern P, by evaluating an arity-2 Expression of Number at every nonze...
Definition: SparseMatrix.cpp:564
Range/Iterator types associated with Pattern.
Function overloads (sin, exp, etc) for Expression&#39;s.
Overloads expr() for SparseMatrix<Number> and Pattern.
Interface class for representing subranges of contiguous int&#39;s.
A = size 6 by 6 SparseMatrix of double (8 nz):
A(:,j0) = 1 nz [ i0,1 ]
A(:,j1) = 1 nz [ i0,0.8 ]
A(:,j2) = 2 nz [ i1,1.4 i3,4.6 ]
A(:,j3) = 2 nz [ i1,2.4 i4,9.2 ]
A(:,j4) = 1 nz [ i2,3.1 ]
A(:,j5) = 1 nz [ i5,1.7 ]
expr(A) = size 6 by 6 arity-2 Expression of double:
[ 1 0.8 0 0 0 0 ]
[ 0 0 1.4 2.4 0 0 ]
[ 0 0 0 0 3.1 0 ]
[ 0 0 4.6 0 0 0 ]
[ 0 0 0 9.2 0 0 ]
[ 0 0 0 0 0 1.7 ]
expr(P) = size 6 by 6 arity-2 Expression of bool:
[ 1 1 0 0 0 0 ]
[ 0 0 1 1 0 0 ]
[ 0 0 0 0 1 0 ]
[ 0 0 1 0 0 0 ]
[ 0 0 0 1 0 0 ]
[ 0 0 0 0 0 1 ]
B = SparseMatrix::evaluate(pow(expr(A),2.0)) = size 6 by 6 SparseMatrix of double (8 nz):
A(:,j0) = 1 nz [ i0,1 ]
A(:,j1) = 1 nz [ i0,0.64 ]
A(:,j2) = 2 nz [ i1,1.96 i3,21.16 ]
A(:,j3) = 2 nz [ i1,5.76 i4,84.64 ]
A(:,j4) = 1 nz [ i2,9.61 ]
A(:,j5) = 1 nz [ i5,2.89 ]
expr(B) = size 6 by 6 arity-2 Expression of double:
[ 1 0.64 0 0 0 0 ]
[ 0 0 1.96 5.76 0 0 ]
[ 0 0 0 0 9.61 0 ]
[ 0 0 21.16 0 0 0 ]
[ 0 0 0 84.64 0 0 ]
[ 0 0 0 0 0 2.89 ]

Appendix: Design philosophy, "always-on" or "opt-in"?

MyraMath's expression templates (ET's) are relatively weak when compared to those of other C++ linear algebra libraries (such as Eigen, MTL4, Blaze, Armadillo, or FLENS, to name a few). Within an ET framework, all kinds of performance optimizations can be embedded (loop unrolling, vectorization, algorithmic blocking). Often, such optimizations lead to C++ code that can credibly compete with vendor-tuned BLAS implementations in common benchmarks (like axpy(), gemv(), gemm()), especially at small problem sizes. MyraMath ET's do none of this, if you want to achieve vendor-tuned performance then you should call BLAS primitives (like axpy(), gemv(), gemm()) explicitly.

Note that pervasive/"always-on" use of ET's does carry drawbacks, in particular they interfere with "value-semantics". Within an ET framework a subexpression like B+C might not yield a "regular type", but rather represent a deferred calculation that is only triggered upon assignment. In particular, a statement like auto A = B+C might not do what one expects! (ET's interact poorly with auto). MyraMath places very high importance on maintaining "value-semantics" and compatibility with auto, so its Expression templates are not "always-on" but rather an "opt-in" feature accessible only by calling expr(). If B and C are explicit containers then auto A = B+C is always an explicit container too, you only get an Expression if you ask for it via auto A = expr(B)+expr(C).

MyraMath's ET's really serve just one purpose: "opt-in" syntactic sugar for populating a /dense container via evaluate() without triggering the construction of extra temporaries.

Go back to API Tutorials