Skip to content

Tensor views (sub tensors): slicing and broadcasting of Fastor tensors

Roman edited this page Jul 5, 2020 · 2 revisions

Tensor views

Fastor provides a convenient interface for slicing and viewing parts of a tensor without making any copies. All Pyhton/NumPy or Julia/Matlab style slicing is supported by Fastor.

Dynamic views

Dynamic views are defined by sequence based iterator seq defined as seq(first,last,step) where step is optional and defaults to 1. For example to take the upper left 2x2 block of a 3x3 matrix (2D tensor) you can do

Tensor<double,3,3> A;
// fill A

// Take the upper left 2x2 of 3x3 tensor starting from row 0 and column 0
auto B = A(seq(0,2),seq(0,2));

The behaviour of seq is just like for loops in that seq(first,last,step) is equivalent to for (int i=first; i<last; i+=step). As you can see seq is an open set i.e. the last element in the sequence is not included just like in the for loop, so the first seq(0,2) in A takes the first two rows of A and the second seq(0,2) takes the two columns of A making a 2x2 block. Note that this block is a view so if you modify the view you modify the tensor

auto B = A(seq(0,2),seq(0,2));
B += 2; // also changes A

If you want to make a copy explicity call the tensor constructor on the view of A instead of using the keyword auto

Tensor<double,2,2> B = A(seq(0,2),seq(0,2));
B += 2; // does not changes A

Now B is a separate tensor.

You can mix index slicing with sequence slicing as well. For instance, see below on taking rows.

Taking rows and columns of matrices

For instance to take the first row of a matrix (2D tensor) A you can do

A(0,seq(0,last));

this will give you the first row of A. lastis a reserved Fastor keyword that indicates end of a sequence. You can also use the shorter all keyword for sequences that span across a whole dimension (similar to NumPy's :), so for the above example you can also write

A(0,all); // take the first row of A 

Similarly for columns

A(all,1); // take the second column of A 

Taking views of high order tensors

You can use the same slicing mechanism for getting parts of any N-dimensional tensor. For instance to extract the first matrix from a 3D tensor (cube) you can do

Tensor<double,3,4,5> A;

// Extract the first 4x5 matrix 
auto B1 = A(0,all,all);

// Extract the second 4x5 matrix 
auto B2 = A(1,all,all);

// Extract the third 4x5 matrix 
auto B2 = A(2,all,all);

You can also extract arbitrary slices from high order tensors

Tensor<double,3,4,5> A;

// Extract: 
//      The first 2 elements in the first dimension
//      The first 2 elements in the second dimension with step=2
//      and all the elements in the third dimension
auto B = A(seq(0,2),seq(0,2,2),all);

Broadcasting and assignment to views

Tensor views act the same as tensors. You can operate on them, modify them and assign another view to them

Tensor<double,3> vec = {1,2,3};      // a vector
Tensor<double,2,4,3> A(0);           // 3D tensor of all zeros 

// assign a vector to the first 2D matrix, first row and all the columns of a 3D array
A(0,0,all) = vec(all);

will give you

[0,:,:]
[1, 2, 3]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[1,:,:]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]

Here is an example of operating on views

A(0,0,all) = sin(vec(all)) + 2.5*B(1,0,all);

In short, if you are familiar with NumPy or Matlab, here are the equivalence

NumPy Matlab Fastor
A[7,3] A(8,4) A(7,3)
A[:,:] A(1:end,1:end) A(all,all)
A[0:6:2,0] A(1:2:6,1) A(seq(0,6,2),0)
A[0:2,3:5,2:4,2:5] A(1:2,4:5,3:4,3:5) A(seq(0,2),seq(3,5),seq(2,4),seq(2,5))

Fixed (static) views

If you know the ranges of your views already you can create a static view using fseq<first,last,step> instead of seq(first,last,step) where fseq stands for a fixed sequence where step parameter is optional and defaults to 1. Using fixed views often result in better performance. For example, to take the upper left 2x2 block of a 3x3 matrix (2D tensor) you can do

Tensor<double,3,3> A;

// Take the upper left 2x2 of 3x3 tensor starting from row 0 and column 0
auto B = A(fseq<0,2>(),fseq<0,2>());

fseq based views behave exactly like seq and of course you can mix them together in a single expression (notice that using all with other fixed sequences results in a static view)

// Assign first row of B to A. The view on A is dynamic, the view on B is static
A(0,seq(0,last)) = B(fseq<0,1>,all);

or shorter using fix keyword

// Assign first row of B to A. The view on A is dynamic, the view on B is static
A(0,seq(0,last)) = B(fix<0>,all);

or you can mix fseq and seq in a single view

B(fseq<0,1>(),seq(0,2)) = 4.5;

In short, if you are familiar with NumPy or Matlab, here are the equivalence

NumPy Matlab Fastor
A[7,3] A(8,4) A(7,3)
A[:,:] A(1:end,1:end) A(all,all)
A[0:6:2,0] A(1:2:6,1) A(fseq<0,6,2>(),fseq<0,1>())

Random views

Fastor also provides indexing a tensor with other integral tensors for instance

Tensor<double,2,3> a   = {{10,20,30},{40,50,60}};
// create index tensor
Tensor<size_t,2,2> idx = {{0,1},{0,2}};
// extract first two rows and first and last columns as another tensor
Tensor<double,2,2> b   = a(idx);

/* will result in
[10,30]
[40,60]
/*

Filter (masked) views

Fastor also provides indexing a tensor with other boolean tensors for instance

Tensor<double,2,3> a   = {{10,20,30},{40,50,60}};
// create mask tensor
Tensor<bool,2,3> mask = {{false,false,true},{true,false,true}};
// for all elements of mask==true flip the sign of a 
a(mask) *= -1;

/* will result in
[10 , 20, -30]
[-40, 50, -60]
/*

Diagonal views

For 2D tensors (matrices) one can view the diagonal as well using diag

Tensor<int,2,2> a; a.arange(); // [0 1; 2 3]
diag(a); // will give [0 3]
diag(a) = 2; // will modify a in-place and return [2 1; 2 2]