tensor-0.1.0
|
A Tensor is a multidimensional array of numbers; in practice, they are constrained to real or complex double precision numbers. Their behavior is similar to Matlab's arrays in that they can store only numbers, be accessed with one or more indices using the () or [] syntaxes, reshaped, sliced, and all that with an automated memory management.
Internally, a Tensor consists of two data items: A Vector containing the dimensions, and a Vector containing the raw data. The Tensor class (or rather the underlying Vector class) makes excessive use of copy-on-write. Therefore, modifying the dimensions of a tensor is usually a cheap operation, while modifying the data may be expensive.
To simplify the interfacing to BLAS/LAPACK libraries, the data is aligned in row-major ordering (as in Fortran, and in contrast to C++).
For all functions that require indices of the tensor (e.g., accessing elements, creating a tensor), explicit functions for up to six-dimensional tensors are provided. For cases with more than six dimensions, or if the dimensions should not be required, there is an additional function that works with an Indices class. The setup is explained further below.
To create a new tensor with uninitialized content, you can use the standard constructor
For convenience, there exist a couple of other static functions that produce standard tensors
Tensors can also be created statically inside the code; this is explained further below. Finally, there is also some basic functionality to write Tensors to files and read them afterwards; for this, see the sdf namespace.
There are two different access mechanisms if you want to retrieve a single entry in the Tensor. You can view the data as a linear sequence of numbers and read a given index, or you can use the dimension information of the tensor. For each access mechanism, a reading and a writing function is provided.
To access the data sequentially, you can use the square brackets, and the function at_seq().
To access the data using the Tensor's dimensions, you can use brackets or the function at(). Note that indices can be positive or negative, where the latter means "count from the end". For example, -1 means the last element along a dimension. This convention is by the way also used whenever a dimension is requested, a -1 would refer to the last dimension.
The various write functions return a reference to the corresponding element. To disallow these functions, you can always declare a tensor as constant.
To access more than one element of a tensor, you can use the range() function together with ordinary brackets to get slices. This works similar to Matlab's slice notation. The range() function can take a number of integer arguments, or an Indices vector (see the section on using Indices) to access various items. As with single entries, negative values are accepted, and count from the end of the dimension. Note that all parameters either have to be numbers, thus retrieving a single item, or slices.
To assign data, you can use again the at() function. You can assign three things: Either a single value or another tensor or slice with the same dimensions.
Note that the return value is in both cases an internal data structure that is either transparently cast to a tensor, or accepts the assigned data. If you keep this data without casting to another Tensor, you will effectively create a loophole in the copy-on-write mechanism. For this reason, you must never use the C++-11 auto feature, unless you know exactly what you are doing (you do not).
To query the dimensions, the tensor class offers a couple of functions:
The function dimensions() is handy to produce an Indices vector that can later be used for convenient manipulations if you want to abstract away the exact rank of the tensor. For two-dimensional tensors, and only for those, the class also offers the rows() and columns() functions, which are equivalent to dimension(0) and dimension(1);
If you want to modify the shape of a tensor, you can use the reshape() function
Typically, the library provides functions that are overloaded for up to six dimensional tensors. Sometimes, however, you might have larger objects, or you want to write a function or routine that can deal with tensors of varying dimensions. In this case, you have to work with Indices.
At its heart, an Indices object is just a fixed-sized vector of integer values. It uses the same copy-on-write mechanism as the Tensor class (actually, the data of tensors is implemented with the same vector class). You can use again the square brackets and the at() function with an index parameter for read / write access to the content.
There are various ways to construct an Indices object. Either you get it from somewhere else (like Tensor::dimensions()), or you create it by claiming some memory or static initialization.
The functions all_equal() and some_unequal() can be used to compare Indices. Furthermore, all comparison operators (==, <= etc.) are overloaded, and will return a vector of booleans that gives the elementwise result of the comparison.
It is possible to create a tensor or vector using compile-time expressions. The syntax always follows the same pattern: first the generator, then the content separated by the operator <<:
A special generator is "xgen", which will convert automatically to the type of the first object that is fed into it. Internally, these expressions are evaluated using recursive templates; consequently they are evaluated at compile time and will slow down compilation if used excessively (e.g., if you feed 1000 elements in this way).
Note that the direct use of the generators always results in one-dimensional tensors of appropriate size. If you want to generate a tensor of a given size, you have to supply the dimensions as Indices vector. Typically, you will create this vector also statically. Note that the data is as usual interpreted in row-major form.