-
Notifications
You must be signed in to change notification settings - Fork 77
Tensor views (sub tensors): slicing and broadcasting of Fastor tensors
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 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.
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. last
is 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
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);
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)) |
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>()) |
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]
/*
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]
/*
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]