Commit 7fab3b64 authored by Ben Huber's avatar Ben Huber

finished Tensor tutorial (relates #193)

parent 6760be36
Pipeline #714 failed with stages
in 7 minutes and 30 seconds
......@@ -21,12 +21,14 @@ dimensions as input create (sparse) tensors that are equal to 0 everywhere
~~~.cpp
// creates a 1x1x1 tensor with entry 0
B = xerus::Tensor(3);
// creates a sparse 2x2x2 tensor without any entries
C = xerus::Tensor({2,2,2});
~~~
~~~.py
# creates a 1x1x1 tensor with entry 0
B = xerus.Tensor(3)
# creates a sparse 2x2x2 tensor without any entries
C = xerus.Tensor([2,2,2])
# equivalently: xerus.Tensor(dim=[2,2,2])
......@@ -35,12 +37,14 @@ The latter of these can be forced to create a dense tensor instead which can eit
~~~.cpp
// creates a dense 2x2x2 tensor with all entries set to 0
D = xerus::Tensor({2,2,2}, xerus::Tensor::Representation::Dense);
// creates a dense 2x2x2 tensor with uninitialized entries
E = xerus::Tensor({2,2,2}, xerus::Tensor::Representation::Dense, xerus::Tensor::Initialisation::None);
~~~
~~~.py
# creates a dense 2x2x2 tensor with all entries set to 0
D = xerus.Tensor(dim=[2,2,2], repr=xerus.Tensor.Representation.Dense)
# creates a dense 2x2x2 tensor with uninitialized entries
E = xerus.Tensor(dim=[2,2,2], repr=xerus.Tensor.Representation.Dense, init=xerus.Tensor.Initialisation.None)
~~~
......@@ -49,35 +53,47 @@ Other commonly used tensors (apart from the 0 tensor) are available through name
~~~.cpp
// a 2x3x4 tensor with all entries = 1
xerus::Tensor::ones({2,3,4});
// an (3x4) x (3x4) identity operator
xerus::Tensor::identity({3,4,3,4});
// a 3x4x3x4 tensor with superdiagonal = 1 (where all 4 indices coincide) and = 0 otherwise
xerus::Tensor::kronecker({3,4,3,4});
// a 2x2x2 tensor with a 1 in position {1,1,1} and 0 everywhere else
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 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
xerus.Tensor.ones([2,3,4])
# an (3x4) x (3x4) identity operator
xerus.Tensor.identity([3,4,3,4])
# a 3x4x3x4 tensor with superdiagonal = 1 (where all 4 indices coincide) and = 0 otherwise
xerus.Tensor.kronecker([3,4,3,4])
# a 2x2x2 tensor with a 1 in position {1,1,1} and 0 everywhere else
xerus.Tensor.dirac([2,2,2], [1,1,1])
# equivalently xerus.Tensor.dirac(dim=[2,2,2], pos=[1,1,1])
# a 4x4x4 tensor with i.i.d. Gaussian random values
xerus.Tensor.random([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])
~~~
......@@ -120,6 +136,7 @@ In python raw data structures are not directly compatible to those used in `xeru
~~~.py
# convert a numpy tensor (identity matrix) to a xerus.Tensor object
T = xerus.Tensor.from_ndarray(numpy.eye(2))
# alternatively the function also accepts pythons native arrays
U = xerus.Tensor.from_ndarray([[1,0], [0,1]])
~~~
......@@ -175,7 +192,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);
......@@ -210,6 +227,90 @@ print(W.sparsity(), W.count_non_zero_entries())
~~~
## Output and Storing
Probably the most common queries to the Tensor class are its degree with [.degree()](\ref xerus::Tensor::degree())
as well as its precise dimensions by accessing [.dimensions](\ref xerus::Tensor::dimensions).
~~~.cpp
// construct a random 3x4x5x6 tensor
A = xerus::Tensor::random({3, 4, 5, 6});
// use xerus' pipe operator to be able to print vectors to std::cout
using xerus::misc::operator<<;
// query its degree and dimensions
std::cout << "degree: " << A.degree() << " dim: " << A.dimensions << std::endl;
// expected output: "degree: 4 dim: {3, 4, 5, 6}"
~~~
~~~.py
# construct a random 3x4x5x6 tensor
A = xerus.Tensor.random([3, 4, 5, 6])
# query its degree and dimensions
print("degree:", A.degree(), "dim:", A.dimensions())
# expected output: "degree: 4 dim: [3, 4, 5, 6]"
~~~
Another useful and commonly used query is for the norm of a tensor. At the moment `xerus` provides member functions for the
two most commonly used norms: [.frob_norm()](\ref xerus::Tensor::frob_norm()) (or equivalently
`frob_norm(const Tensor&)`) to obtain the Frobenius norm and [.one_norm()](\ref xerus::Tensor::one_norm()) (or equivalently
`one_norm(const Tensor&)`) to obtain the p=1 norm of the tensor.
~~~.cpp
A = xerus::Tensor::identity({100,100});
// query the tensor for its p=1 and p=2 norm
std::cout << one_norm(A) << ' ' << frob_norm(A) << std::endl;
// expected output: "10000 100"
~~~
~~~.py
A = xerus.Tensor.identity([100,100])
# query the tensor for its p=1 and p=2 norm
print(xerus.one_norm(A), xerus.frob_norm(A))
# expected output: "10000 100"
~~~
To obtain a human readable string representation of the tensor, [.to_string()](\ref xerus::Tensor::to_string()) can be used.
Note that it is meant purely for debugging purposes, in particular of smaller objects, and it is not adequately possible to
reconstruct the original tensor from this output.
Storing Tensors to files such that they can be reconstructed exactly from those is instead possible with [save_to_file()](\ref xerus::misc::save_to_file())
and respectively [load_from_file()](\ref xerus::misc::load_from_file()).
~~~.cpp
// construct a random 3x3 tensor
A = xerus::Tensor::random({3, 3});
// store the Tensor to the file "tensor.dat"
xerus::misc::save_to_file(A, "tensor.dat");
// load the Tensor from the file
xerus::Tensor B = xerus::misc::load_from_file<xerus::Tensor>("tensor.dat");
// check for correct reconstruction
std::cout << "original tensor: " << A.to_string() << std::endl
<< "loaded tensor: " << B.to_string() << std::endl
<< "error: " << frob_norm(B-A) << std::endl;
~~~
~~~.py
# construct a random 3x3 tensor
A = xerus.Tensor.random([3, 3])
# store the Tensor to the file "tensor.dat"
xerus.misc.save_to_file(A, "tensor.dat")
# load the Tensor from the file
B = xerus.misc.load_from_file("tensor.dat")
# check for correct reconstruction
print("original tensor:", A)
print("loaded tensor:", B)
print("error:", xerus.frob_norm(B-A))
~~~
## 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
......@@ -222,29 +323,38 @@ isomorphism to operators that matrices have. As such the matrix multiplication c
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
......@@ -252,11 +362,13 @@ that the computer uses internally does not need not be reordered, such a reinter
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])
~~~
......@@ -271,20 +383,26 @@ 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)
~~~
......@@ -307,20 +425,44 @@ A = xerus.entrywise_product(A, A)
~~~
## Output and Storing
In the above examples we have already seen two kind of queries to the `Tensor` objects: [.to_string()](\ref xerus::Tensor::to_string())
to obtain a human readable string representation of the Tensor and [.frob_norm()](\ref xerus::Tensor::frob_norm()) or equivalently
[frob_norm(Tensor))(\ref xerus::frob_norm(Tensor)) to obtain the Frobenius norm of a Tensor. Note for the former, that it is
meant purely for debugging purposes, in particular of smaller objects, and it is not adequately possible to reconstruct the original
Tensor from this output.
Storing Tensors to files such that they can be reconstructed exactly from those is instead possible with [save_to_file()](\ref xerus::misc::save_to_file())
and respectively [load_from_file()](\ref xerus::misc::load_from_file()).
## Ownership of Data and Advanced Usage
To reduce the number of unnecessary copies of tensors, `xerus` can share the underlying data arrays among several tensors and
even evaluate multiplication with scalars lazyly (or include them in the next contraction or other modfication).
~~~.cpp
A = xerus::Tensor::random({100,100});
// after the copy, the underlying data array is shared among A and B
xerus::Tensor B(A);
// the array is still shared, even after multiplication with scalars
B *= 3.141;
// as well as after dimension reinterpretation
B.reinterpret_dimensions({10, 10, 10, 10});
// any change in the stored data of A or B will then copy the data to ensure that the other is not changed
B[{2,2,2,2}] = 0.0;
// A remains unchanged and does not share the data with B anymore
~~~
~~~.py
A = xerus.Tensor.random([100,100])
# after the copy, the underlying data array is shared among A and B
B = xerus.Tensor(A)
# the array is still shared, even after multiplication with scalars
B *= 3.141
# as well as after dimension reinterpretation
B.reinterpret_dimensions([10, 10, 10, 10])
# any change in the stored data of A or B will then copy the data to ensure that the other is not changed
B[[2,2,2,2]] = 0.0
# A remains unchanged and does not share the data with B anymore
~~~
## Advanced Use and Ownership of Data
shared_ptr, factor, rest c++ only: accessing internal representations
The average user of `xerus` does not need to worry about this internal mechanism. This changes though as soon as you need access
to the underlying data structues e.g. to call `blas` or `lapack` routines not supported by `xerus` or to convert objects from
other libraries to and from xerus::Tensor objects (only possible in c++). If you do, make sure to check out the documentation
for the following functions:
* [.has_factor()](\ref xerus::Tensor::has_factor()) and [.apply_factor()](\ref xerus::Tensor::apply_factor())
* [.get_dense_data()](\ref xerus::Tensor::get_dense_data()); [.get_unsanitized_dense_data()](\ref xerus::Tensor::get_unsanitized_dense_data()); [.get_internal_dense_data()](\ref xerus::Tensor::get_internal_dense_data())
* [.get_sparse_data()](\ref xerus::Tensor::get_sparse_data()); [.get_unsanitized_sparse_data()](\ref xerus::Tensor::get_unsanitized_sparse_data()); [.get_internal_sparse_data()](\ref xerus::Tensor::get_internal_sparse_data())
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