The xtensor library#
Today’s assignment.
Introduction#
So hopefully you’ve seen from my previous linear_algebra -> C++ class that it is
possible to write quite nice looking code dealing with vectors and matrices in
C++. But you’ve probably also noticed that while it’s fairly easy to use
vector and matrix, it’s much less straightforward to implement those classes
(which I’ve only partially done).
It’s generally nice if one can use other people’s code, rather than having to write one’s own. So in this class, I will use the xtensor library going forward, which provides one- and multi-dimensional arrays in C++. It’s a pretty feature-rich library, which gives C++ python/numpy-style arrays, though we’ll probably only use some fraction of its capabilities.
[Note: xtensor is one of many libraries that add multi-d support to C++ – it’s
a bit unfortunate that there isn’t one way to do multi-d arrays in C++, but many
different ones. That’s a consequence of C++ not providing support for multi-d
arrays natively. C++23 introduced std::mdspan, which partially starts to
address this issue.]
I did the work in what ends up your assignment repo, so you should see my commits in your repo after you sign up. But I’m also linking my work here, with some additional context on what I’ve done.
In a nutshell, I’ve added the necessary cmake build stuff to get xtensor into the linear algebra example, and then used it to pretty much replace my home-grown vector and matrix classes. You can see that it allowed me to remove a bunch of code that I had written myself, to end up with something that can do the same – and much more.
Using xtensor to implement vector and matrix#
The starting point is the linear algebra code after Tuesday’s homework: b39e981
In 165dde, I turned my linear algebra library into what’s called a “header only library”. This is a common way to distribute C++ libraries, and it means that the library consists of just header files, and no separate compilation is needed. As with everything, this has its good and bad parts. It definitely avoids some hassles associated with compiled libraries, where one needs to make sure that the compiled binaries are compatible with whatever C++ compiler is later used to compile the code that is using the library. It also kinda goes against the spirit of C/C++ libraries, where part of the idea is that it’s already ready made for use (in particular, compiled), and one just needs to link to it, speedings things up as one doesn’t need to recompile the library every time. In any case, it’s frequently done in modern C++, as its more or less the only way to distribute template libraries, which need to be compiled according to how they are used.
Another maybe more minor drawback of header only libraries is that the class definition and implementation are not separated, which can make it a bit harder to read. This can be mitigated, though, of one cares for it.
43bacb8 then shows the reason why I made the library header only – I changed the
vectorclass to be templated by the value type. That is another useful feature in C++ – one may want to not only store vectors made up of double precision numbers, but also single precision, or even integers. By making the class templated, we can use the same code to create vectors of any type. However, this also means that the implementation of the class needs to be available at the time of compilation, which is why I put it all in the header. It means we know have to usevector<double>instead of justvector, but that’s not too bad.In a121c3, I did the same thing for the
matrixclass.In a4df6ec, we’re finally getting ready for using xtensor, but we’re still not quite there. I added the necessary cmake code to download and build xtensor as needed, but I haven’t yet changed the
vectorandmatrixclasses to use xtensor.As with pulling in googletest, instead of using cmake’s built-in capabilities directly to deal with downloading stuff on demand, I’ve used a little project called CPM that makes life a bit easier. It does require downloading the
cmake/CPM.cmakefile and including it within the repository, which I’ve already done.At long last, in c478cb9, I’m starting to actually use xtensor. I changed the
vectorclass to just be a type alias forxt::xtensor<T, 1>, which is the xtensor type for 1-d arrays. For the most part, the rest of the code remained unchanged, with the exception of the construction of thevectorobjects, which is a bit different with xtensor. I used to have, e.g.,vector<double> x(3);to mean construct a vector of length 3, but with xtensor, this would actually give me a vector of length 1, with its single element being 3. Instead, I now usevector<double> x(xt::shape({3}));, where I’m explicitly telling xtensor that “3” is the shape (dimension) of the vector, not its values.I then again did the same thing for the
matrixclass in fe3d3c5. While it would have been fine to do this switch ofvectorandmatrixto xtensor at the same time, I wanted to do it in two steps, so that it’s easier to see what changed for each of them.After a little clean-up, in 7985222 I took advantage of xtensor allowing me to concisely create and initialize vectors and matrices, which allowed me to remove a bunch of code that I had written myself to do the same thing. This definitely makes the testing code much more concise and readable.
Of course, there’s a lot more that xtensor can do. As a small example, I used
xt::sumin b28b69d to simplify the (vector) dot product code.
I kept my own implementations for the various dot implementations. That’s because xtensor itself is a multi-d array package, not
really a linear algebra package, and so its core doesn’t include this kind of linear algebra operations. However, I could have gone and used
xtensor-blas which adds on
linear algebra capabilities. For the most part, for the rest of the class
we’ll primarily need multi-d arrays, not necessarily much linear algebra, so
xtensor is what I do care about at this point, and I didn’t want to pull in
yet more external dependencies.
Your turn#
Code up and run the four examples from xtensor’s Getting Started.
As you do this, keep track (copy & paste) of the output and write notes into your Feedback github pull request. Also make commits for each example (each should be a separate source file / executable).
The “Getting Started” page does tell you how to build with cmake, however that assumes that xtensor and xtl are already installed on the machine you’re using (and that cmake can find it). As described above, I’ve already set up the assignment repo to download xtensor and xtl, so you can use xtensor by just linking to it, e.g.,
target_link_libraries(my_example PUBLIC
xtensor
)
So for this work, you can just add the examples from the xtensor documentation
to a new subdirectory in your repository and make your own CMakeLists.txt following the above.
Note: The xtensor instructions say to add xtensor::optimize and
xtensor::use_xsimd to link_libraries(). This doesn’t quite work the same way
if using a cmake-downloaded xtensor, but it’s not important for those tests
to be that optimized, either, so you can just disregard these options. The MSVC
specific stuff can also be skipped.
Homework#
Finish adding the four xtensor examples to the repo, test them, and write notes about how they worked and what you learned from it.
Optionally, see if you can get
xsimdand/orxtensor-blasworking in your repo and make use of them.