Commit 4112646f authored by Ben Huber's avatar Ben Huber

Tensor tutorial: Operators and Modification

parent dec265fa
Pipeline #712 failed with stages
in 4 minutes and 12 seconds
# Indices and Equations
## Blockwise Construction of Tensors
......@@ -3,7 +3,7 @@
The basic building stone of this library and all Tensor Network methods is the Tensor, represented in the class `xerus::Tensor`.
To simplify the work with these objects, `xerus` contains a number of helper functions that allow the quick creation and
modification of sparse and dense tensors. In the following we will list the most important ones but advise you to also read
the tutorials on [indices and equations]() and [decompositions]() to have the full toolset with which to work on individual
the tutorials on [indices and equations](\ref md_indices) and [decompositions]() to have the full toolset with which to work on individual
tensors.
## Creation of Tensors
......@@ -58,10 +58,10 @@ xerus::Tensor::dirac({2,2,2}, {1,1,1});
// a 4x4x4 tensor with i.i.d. Gaussian random values
xerus::Tensor::random({4,4,4});
// a (4x4) x (4x4) random orthogonal operator drawn according to the Haar measure
xerus::Tensor::random_orthogonal({4,4},{4,4});
// a 4x4x4 sparse tensor with 10 random entries in uniformly distributed random positions
xerus::Tensor::random({4,4,4}, 10);
// a (4x4) x (4x4) random orthogonal operator drawn according to the Haar measure
xerus::Tensor::random_orthogonal({4,4},{4,4});
~~~
~~~.py
# a 2x3x4 tensor with all entries = 1
......@@ -76,10 +76,10 @@ xerus.Tensor.dirac([2,2,2], [1,1,1])
# a 4x4x4 tensor with i.i.d. Gaussian random values
xerus.Tensor.random([4,4,4])
# a (4x4) x (4x4) random orthogonal operator drawn according to the Haar measure
xerus.Tensor.random_orthogonal([4,4],[4,4])
# a 4x4x4 sparse tensor with 10 random entries in uniformly distributed random positions
xerus.Tensor.random([4,4,4], n=10)
# a (4x4) x (4x4) random orthogonal operator drawn according to the Haar measure
xerus.Tensor.random_orthogonal([4,4],[4,4])
~~~
If the entries of the tensor should be calculated externally, it is possible in c++ to either pass the raw data directly (as
......@@ -91,14 +91,14 @@ std::unique_ptr<double> ptr = foo();
// NOTE: make sure that the dimensions are correct as there is no way for xerus to check this!
F = xerus::Tensor({2,2,2}, ptr);
// create a dense 2x2x2 tensor with every entry populated by a callback (lambda) function
G = xerus::Tensor({2,2,2}, [](const std::vector<size_t> &_idx) -> double {
// create a dense 3x3x3 tensor with every entry populated by a callback (lambda) function
G = xerus::Tensor({3,3,3}, [](const std::vector<size_t> &_idx) -> double {
// somehow derive the entry from the index positions in _idx
double result = double( _idx[0] * _idx[1] * _idx[2] );
return result;
});
// create a sparse 16x16x16 tensor with 5 entries determined by a callback (lambda) function
// create a sparse 16x16x16 tensor with 16 entries determined by a callback (lambda) function
H = xerus::Tensor({16,16,16}, 16, [](size_t num, size_t max) -> std::pair<size_t, double> {
// insert number 1/5, 2/5, 3/5, 4/5, 5/5
// at positions 0 (= {0,0,0}), 17 (= {0,1,1}), 34 (= {0,2,2}) ... respectively
......@@ -106,8 +106,13 @@ H = xerus::Tensor({16,16,16}, 16, [](size_t num, size_t max) -> std::pair<size_t
});
~~~
~~~.py
# create a dense 2x2x2 tensor with every entry populated by a callback (lambda) function
G = xerus.Tensor.from_function([2,2,2], lambda idx: idx[0]*idx[1]*idx[2])
# Transfering ownership of raw data directly is not possible from within python.
# create a dense 3x3x3 tensor with every entry populated by a callback (lambda) function
G = xerus.Tensor.from_function([3,3,3], lambda idx: idx[0]*idx[1]*idx[2])
# Creating a sparse tensor from a callback function in python is not supported by xerus.
# This behaviour can easily be achieved by setting the correspoding values as seen below though.
~~~
In python raw data structures are not directly compatible to those used in `xerus` internally. Tensors can be constructed from
......@@ -134,6 +139,7 @@ V = xerus.Tensor([2,2])
V[[0,0]] = 1.0 # equivalently: V[0] = 1.0
V[[1,1]] = 1.0 # equivalently: V[3] = 1.0
~~~
Explicitely constructing a tensor similarly in a blockwise fashion will be covered in the tutorial on [indices and equations](\ref md_indices)
## Sparse and Dense Representations
......@@ -150,7 +156,7 @@ xerus::Tensor::sparsityFactor = 4;
# tell xerus to convert sparse tensors to dense if 1 in 4 entries are non-zero
xerus.Tensor.sparsityFactor = 4
~~~
in particular, setting the [`sparsityFactor`](\ref xerus::Tensor::sparsityFactor) to 0 will disable this feature.
in particular, setting the [sparsityFactor](\ref xerus::Tensor::sparsityFactor) to 0 will disable this feature.
~~~.cpp
// stop xerus from automatically converting sparse tensors to dense
xerus::Tensor::sparsityFactor = 0;
......@@ -169,7 +175,7 @@ member functions [.use_dense_representation()](\ref xerus::Tensor::use_dense_rep
[.sparse_copy()](\ref xerus::Tensor::sparse_copy()) to obtain new tensor objects with the desired representation.
To make more informed decisions about whether a conversion might be useful the tensor objects can be queried for the number of
defined entries with [.sparsity()](\ref xerus::Tensor::sparsity()) or for the number of non-zero entries with [.count_non_zero_entries()](\ref xerus::Tensor::count_non_zero_entries()).
defined entries with [.sparsity()](\ref xerus::Tensor::sparsity()) or for the number of non-zero entries with [.count_non_zero_entries()](\ref xerus::Tensor::count_non_zero_entries()) ().
~~~.cpp
// create a sparse tensor with 100 random entries
W = xerus::Tensor::random({100,100}, 100);
......@@ -205,5 +211,104 @@ print(W.sparsity(), W.count_non_zero_entries())
## Operators and Modifications
We have already seen the most basic method of modifying a tensor via the [operator[]](\ref xerus::Tensor::operator[]()). With it
and the index notation presented in the [indices and equations](\ref md_indices) tutorial, most desired manipulations can be
represented. Some of them would still be cumbersome though, so `xerus` includes several helper functions to make your life easier.
The purpose of this section is to present the most important ones.
Naturally tensor objects can be used in arithmetic expressions whereever this is well defined: addition and subtraction of equally
sized tensors as well as multiplication and division by scalars. Note, that tensors in general do not have the cannonical
isomorphism to operators that matrices have. As such the matrix multiplication can not be generalized trivially to tensors. Instead
you will have to use indexed equations to express such contractions in xerus.
~~~.cpp
xerus::Tensor A = xerus::Tensor::random({100});
// normalizing the vector
A = A / frob_norm(A);
// adding a small pertubation
xerus::Tensor B = A + 1e-5 * xerus::Tensor::random({100});
// normalizing B
B /= frob_norm(B);
// determining the change from A to B
B -= A;
std::cout << "Distance on the unit sphere: " << B.frob_norm() << std::endl;
~~~
~~~.py
A = xerus.Tensor.random([100])
# normalizing the vector
A = A / xerus.frob_norm(A)
# adding a small pertubation
B = A + 1e-5 * xerus.Tensor.random([100])
# normalizing B
B /= B.frob_norm()
# determining the change from A to B
B -= A
print("Distance on the unit sphere: ", B.frob_norm())
~~~
Where we have already used [.frob_norm()](\ref xerus::Tensor::frob_norm()) to calculate the Frobenius norm of a Tensor.
Many theoretical results use flattenings or expansions (vectorizations or tensorizations) to reduce tensor problems to ones with
vectors and matrices. While this is not as common in practical applications it can still be useful at times. Because the data
that the computer uses internally does not need not be reordered, such a reinterpretation of the tensor objects are constant in
time.
~~~.cpp
A = xerus::Tensor::identity({2,2});
// flatten the identity matrix to the vector (1, 0, 0, 1)
A.reinterpret_dimensions({4});
~~~
~~~.py
A = xerus.Tensor.identity([2,2])
# flatten the identity matrix to the vector (1, 0, 0, 1)
A.reinterpret_dimensions([4])
~~~
This operation is obviously only possible when the total number of entries remains unchanged.
If you want to change the dimensions of a tensor such that the total size changes, you have to specify how to do this. `xerus`
provides three functions to help you in such a case: [.resize_mode()](\ref xerus::Tensor::resize_mode()) changes the dimension
of a single mode by adding zero slates or removing existing slates at a given position; [.fix_mode()](\ref xerus::Tensor::fix_mode())
reduces the tensor to an object of degree d-1 that corresponds to the slate, selected in the call to the function; finally
[.remove_slate()](\ref xerus::Tensor::remove_slate()) is a simplified version of `.resize_mode()` that removes a single slate
from the tensor, reducing the dimension of the specified mode by one.
~~~.cpp
// start with a 2x2 matrix filled with ones
A = xerus::Tensor::ones({2,2});
// insert another row (consisting of zeros) i.e. increase dimension of mode 0 to 3
A.resize_mode(0, 3, 1);
// select the first column i.e. reduce to fixed value 0 for mode 1
A.fix_mode(1, 0);
// expected output: "1.0 0.0 1.0"
std::cout << A.to_string() << std::endl;
~~~
~~~.py
# start with a 2x2 matrix filled with ones
A = xerus.Tensor.ones([2,2])
# insert another row (consisting of zeros) i.e. increase dimension of mode 0 to 3
A.resize_mode(mode=0, newDim=3, cutPos=1)
# select the first column i.e. reduce to fixed value 0 for mode 1
A.fix_mode(mode=1, value=0)
# expected output: "1.0 0.0 1.0"
print(A)
~~~
At the moment the Hadamard product is not available in a indexed notation (due to a lack of overloadable operators). Its
behaviour can instead be achieved with [entrywise_product()](\ref xerus::entrywise_product()).
~~~.cpp
// constructing a tensor with i.i.d. entries sampled from a Chi-Squared distribution by (Note: this is not the most efficient way to achieve this!)
// 1. constructing a tensor with Gaussian i.i.d. entries
A = xerus::Tensor::random({10,10,10,10});
// 2. performing an entrywise product of this tensor with itself
A = entrywise_product(A, A);
~~~
~~~.py
# constructing a tensor with i.i.d. entries sampled from a Chi-Squared distribution by (Note: this is not the most efficient way to achieve this!)
# 1. constructing a tensor with Gaussian i.i.d. entries
A = xerus.Tensor.random([10,10,10,10])
# 2. performing an entrywise product of this tensor with itself
A = xerus.entrywise_product(A, A)
~~~
## Output and Storing
## Advanced Use and Ownership of Data
shared_ptr, factor, rest c++ only: accessing internal representations
......@@ -737,11 +737,11 @@ namespace xerus {
/**
* @brief Performs the trace over the given indices
* @param _firstIndex the first index involved in the trace.
* @param _secondIndex the second index involved in the trace.
* @brief Performs the trace over the given modes
* @param _firstMode the first mode involved in the trace.
* @param _secondMode the second mode involved in the trace.
*/
void perform_trace(size_t _firstIndex, size_t _secondIndex);
void perform_trace(size_t _firstMode, size_t _secondMode);
/**
......
......@@ -463,12 +463,35 @@ BOOST_PYTHON_MODULE(xerus) {
.def("sparsity", &Tensor::sparsity)
.def("all_entries_valid", &Tensor::all_entries_valid)
.def("reorder_cost", &Tensor::reorder_cost)
.def("reinterpret_dimensions", &Tensor::reinterpret_dimensions)
.def("reinterpret_dimensions", &Tensor::reinterpret_dimensions,
arg("dim"),
"Reinterprets the dimensions of the tensor."
parametersDocstr
"dim : list or tuple of int"
)
.def("resize_mode", &Tensor::resize_mode,
(arg("mode"), arg("newDimension"), arg("cutPosition")=~0ul)
(arg("mode"), arg("newDim"), arg("cutPos")=~0ul),
"Resizes a specific mode of the Tensor."
parametersDocstr
"mode : int\n"
"newDim : int\n"
"cutPos : int, optional (default: infinity)\n"
" The position within the selected mode in front of which slates are inserted or removed."
)
.def("fix_mode", &Tensor::fix_mode,
(arg("mode"), arg("value")),
"Fixes a specific mode to a specific value, effectively reducing the order by one."
parametersDocstr
"mode : int\n"
"value : int"
)
.def("remove_slate", &Tensor::remove_slate,
((arg("mode"), arg("pos")),
"Removes a single slate from the Tensor, reducing dimension[mode] by one."
parametersDocstr
"mode : int\n"
"pos : int"
)
.def("fix_mode", &Tensor::fix_mode)
.def("remove_slate", &Tensor::remove_slate)
.def("perform_trace", &Tensor::perform_trace)
.def("offset_add", &Tensor::offset_add)
.def("use_dense_representation", &Tensor::use_dense_representation)
......
......@@ -768,18 +768,18 @@ namespace xerus {
}
void Tensor::perform_trace(size_t _firstIndex, size_t _secondIndex) {
REQUIRE(_firstIndex != _secondIndex, "Given indices must not coincide");
REQUIRE(dimensions[_firstIndex] == dimensions[_secondIndex], "Dimensions of trace indices must coincide.");
void Tensor::perform_trace(size_t _firstMode, size_t _secondMode) {
REQUIRE(_firstMode != _secondMode, "Given indices must not coincide");
REQUIRE(dimensions[_firstMode] == dimensions[_secondMode], "Dimensions of trace indices must coincide.");
if(_firstIndex > _secondIndex) { std::swap(_firstIndex, _secondIndex); }
if(_firstMode > _secondMode) { std::swap(_firstMode, _secondMode); }
const size_t front = misc::product(dimensions, 0, _firstIndex);
const size_t mid = misc::product(dimensions, _firstIndex+1, _secondIndex);
const size_t back = misc::product(dimensions, _secondIndex+1, degree());
const size_t traceDim = dimensions[_firstIndex];
const size_t front = misc::product(dimensions, 0, _firstMode);
const size_t mid = misc::product(dimensions, _firstMode+1, _secondMode);
const size_t back = misc::product(dimensions, _secondMode+1, degree());
const size_t traceDim = dimensions[_firstMode];
const size_t frontStepSize = traceDim*mid*traceDim*back;
const size_t traceStepSize = mid*traceDim*back+back;
const size_t midStepSize = traceDim*back;
......@@ -822,8 +822,8 @@ namespace xerus {
sparseData.reset(newData.release());
}
dimensions.erase(dimensions.begin()+_secondIndex);
dimensions.erase(dimensions.begin()+_firstIndex);
dimensions.erase(dimensions.begin()+_secondMode);
dimensions.erase(dimensions.begin()+_firstMode);
factor = 1.0;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment