Skip to content

YAP Expression Examples

Ashar edited this page Jun 25, 2019 · 5 revisions

The new expression templates work just like the old uBLAS expression templates. However, the new expression templates are much easier and give a lot of freedom to end-user (you) and to us (developers). It is therefore important to point out all the capabilities and new interfaces that the new expression templates have to offer. It is highly recommended to read What's new in new expression templates before reading this wiki. This wiki will focus more on how to exploit the new features rather than talking about the new features.

tensor_expression

tensor_expression is the structure in namespace boost::numeric::ublas::detail that is returned from all the operations that are performed on the tensor types or any operation that involves any tensor-type.

You can capture your expression like this:

auto expr = 3*tensor1 + 5*tensor2;

In the following sections, we discuss some Freedoms of the new Expression template.

Freedom to move and copy expressions

All the tensor expressions follow Boost.YAP Expression concept. Unlike the previous expression template, the new expression templates can be copied or moved. Allowing you to treat an expression as a regular object.

auto expr1 = tensor1 + tensor2;
auto expr2 = expr1;

tensor<int> val(expr1);
tensor<int> val2(expr2);

The tensor variables are captured by references and hence a new reference is made and not the internal variable are copied. However, if the operand is an r-value reference the copy of that operand is made into the new expression.

Because rvalue operands of an expression are copied. You should not create copies of expression involving rvalue operands where copying can be performance overhead.

Freedom of Scalars

When it comes to scalars, you can perform all four fundamental operations with tensors. However, we have absolutely no limit or what so ever on the scalar type that you can use with tensors as long as there is an operator overload for the operation.

struct Zero_Like{};
auto operator*(int, Zero_Like&){return 0;}

auto t = tensor<int>{shape{5,5}, 0};

auto expr = t * Zero_Like{};
decltype(t) t2 = expr;

In other words, you can take anything as scalar as long as you provide an operator overload for the operation. Like you can even take a lambda or equivalent as a scalar type.

Freedom of Data-types and Casting

Following the trend of Freedom for Scalar, We provide freedom for internal data-types of tensor or matrix or vectors. The last expression template did not allow the user to mix-match the data-types. This is no longer true with the new expression templates: You can mix an match things as long as the operations are well defined. So,

auto td = tensor<int>{shape{5,5},0};
auto tf = tensor<float>{shape{5,5}, 1.2f};

auto expr = td + tf;

tensor<int> a = expr; // Fills a with 1
tensor<float> b = expr; // Fills b with 1.2 

All such expressions follow the exact same standard C++ behaviour for type promotion and implicit casting. We also provide 3 casting operator for tensors. They are eager casting operators and eagerly cast tensor elements and return a new tensor. They are :

  • boost::numeric::ublas::static_tensor_cast<>
  • boost::numeric::ublas::dynamic_tensor_cast<>
  • boost::numeric::ublas::reinterpret_tensor_cast<>
auto td = tensor<int>{shape{5,5}, 5};

auto tf = static_tensor_cast<float>(td);
static_assert(std::is_same_v<typename tf::value_type, float>);

try{
   auto res = dynamic_tensor_cast<float>(td);
}catch(std::bad_cast& e){
    // Indeed a bad cast
}

auto tptrs = reinterpret_tensor_cast<char*>(td); // danger here :)

They behave the same as standard C++ casting operators as it is just a wrapper for tensor typecasting.

The tensor resulted from the cast will have tensor::array_type as std::vector always.

Do not pass an expression to for casting. It is a compile-time error. A tensor expression casting operator is in the way.

Operator Overloads

A Complete set of operator overloads are as follow:

SYB LHS RHS Return type Arity
- tensor/tensor_expression N/A tensor_expression Unary
+ tensor/tensor_expression N/A tensor_expression Unary
+ tensor/tensor_expression Anything tensor_expression Binary
+ Anything tensor/tensor_expression tensor_expression Binary
- tensor/tensor_expression Anything tensor_expression Binary
- Anything tensor/tensor_expression tensor_expression Binary
* tensor/tensor_expression Anything tensor_expression Binary
* Anything tensor/tensor_expression tensor_expression Binary
/ tensor/tensor_expression Anything tensor_expression Binary
/ Anything tensor/tensor_expression tensor_expression Binary
> tensor/tensor_expression Anything tensor_expression Binary
> Anything tensor/tensor_expression tensor_expression Binary
< tensor/tensor_expression Anything tensor_expression Binary
< Anything tensor/tensor_expression tensor_expression Binary
!= tensor/tensor_expression Anything tensor_expression Binary
!= Anything tensor/tensor_expression tensor_expression Binary
== tensor/tensor_expression Anything tensor_expression Binary
== Anything tensor/tensor_expression tensor_expression Binary
>= tensor/tensor_expression Anything tensor_expression Binary
>= Anything tensor/tensor_expression tensor_expression Binary
<= tensor/tensor_expression Anything tensor_expression Binary
<= Anything tensor/tensor_expression tensor_expression Binary
+= tensor tensor/tensor_expression tensor Binary
-= tensor tensor/tensor_expression tensor Binary
*= tensor tensor/tensor_expression tensor Binary
/= tensor tensor/tensor_expression tensor Binary

Assignment of expression to any tensor type or conversion of expression to boolean (in case it involves relational operators) causes the lazy evaluation of the expression. All operators including relational operators are lazy

Anything in the above table means anything that has an overload of that operator for tensor. It could include matrix expression, vector expression or scalars.

All of the above operators are lazy except for assignment operators. Also, You will not find the code for the above overloads as they are done using Boost.YAP macros . More information about the behaviour of the lazy relational operator could be found below.

Behaviour of Lazy Relational Operator

The new expression template makes even relational operators lazy. Here are some benefits of this approach and some point to be cautious about when using this new feature. Let's see what happens when we write a relational expression.

tensor<int> td1 = tensor<int>{shape{5,5},2};
tensor<int> td2 = tensor<int> {shape{5,5},3};

auto expr = td1 + 1 == td2;
boost::yap::print(std::cout, expr);

Outputs

expr<==>
	expr<+>
		term<tensor<int> &>
		term<int>=[1]
	term<tensor<int> &>

So, we built expression and did not evaluate the result. So, how do we evaluate the result?

bool b = expr; // implictly evaluates to a bool

if(expr){} // implictly evaluates to a bool

BOOST_CHECK(expr); // Error at compile time. 
// BOOST_CHECK() is a macro so implicit casting cannot be performed.

BOOST_CHECK((bool)(expr)); // This works

WARNING Casting is required when implicit casting is not performed by the compiler but bool is expected

The result is evaluated by expanding the expression element-wise. The result is evaluated as in this case (pseudo code):

if lhs.extent != rhs.extent:
	return false
else:
    for i in 0..lhs.extent.prouct():
        if ! (td1[i] + 1 == td2[i]): # <== Notice expression is expanded to a predicate
            return false
    return true

Due to the same reason, If an expression has more than one relational operators or does not have any relational operator a runtime error is thrown if you try to convert in into a bool.

We allow you to evaluate a relational expression into a tensors. In such case the evaluation is element-wise predicate result. Consider this example:

auto a_val = {1,1,1,2,2,2};
auto b_val = {2,2,2,2,2,2};

tensor<int> a = tensor<int>{shape{3,2}, std::vector<int>(a_val)};
tensor<int> b = tensor<int>{shape{3,2}, std::vector<int>(b_val)};

tensor<bool> result = a == b;
std::cout<<std::boolalpha<<result;
std::cout<<"\n"<<(bool)(a==b); 

Outputs

[ false, false, false
  true,  true,  true ]

false  

See this produces an element-wise compared and resulted tensor also, casting it to a bool produces a false result as expected. This behaviour is required for some application and also it can be used to find index at which predicate are failing.

This was not possible in the older implementation of the expression templates of the tensor.

Clone this wiki locally