Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RTensor.hxx
Go to the documentation of this file.
1 #ifndef TMVA_RTENSOR
2 #define TMVA_RTENSOR
3 
4 #include <vector>
5 #include <cstddef> // std::size_t
6 #include <stdexcept> // std::runtime_error
7 #include <sstream> // std::stringstream
8 #include <memory> // std::shared_ptr
9 #include <type_traits> // std::is_convertible
10 #include <algorithm> // std::reverse
11 #include <iterator> // std::random_access_iterator_tag
12 
13 namespace TMVA {
14 namespace Experimental {
15 
16 /// Memory layout type
17 enum class MemoryLayout : uint8_t {
18  RowMajor = 0x01,
19  ColumnMajor = 0x02
20 };
21 
22 namespace Internal {
23 
24 /// \brief Get size of tensor from shape vector
25 /// \param[in] shape Shape vector
26 /// \return Size of contiguous memory
27 template <typename T>
28 inline std::size_t GetSizeFromShape(const T &shape)
29 {
30  if (shape.size() == 0)
31  return 0;
32  std::size_t size = 1;
33  for (auto &s : shape)
34  size *= s;
35  return size;
36 }
37 
38 /// \brief Compute strides from shape vector.
39 /// \param[in] shape Shape vector
40 /// \param[in] layout Memory layout
41 /// \return Size of contiguous memory
42 ///
43 /// This information is needed for the multi-dimensional indexing. See here:
44 /// https://en.wikipedia.org/wiki/Row-_and_column-major_order
45 /// https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.strides.html
46 template <typename T>
47 inline std::vector<std::size_t> ComputeStridesFromShape(const T &shape, MemoryLayout layout)
48 {
49  const auto size = shape.size();
50  T strides(size);
51  if (layout == MemoryLayout::RowMajor) {
52  for (std::size_t i = 0; i < size; i++) {
53  if (i == 0) {
54  strides[size - 1 - i] = 1;
55  } else {
56  strides[size - 1 - i] = strides[size - 1 - i + 1] * shape[size - 1 - i + 1];
57  }
58  }
59  } else if (layout == MemoryLayout::ColumnMajor) {
60  for (std::size_t i = 0; i < size; i++) {
61  if (i == 0) {
62  strides[i] = 1;
63  } else {
64  strides[i] = strides[i - 1] * shape[i - 1];
65  }
66  }
67  } else {
68  std::stringstream ss;
69  ss << "Memory layout type is not valid for calculating strides.";
70  throw std::runtime_error(ss.str());
71  }
72  return strides;
73 }
74 
75 /// \brief Compute indices from global index
76 /// \param[in] Shape vector
77 /// \param[in] idx Global index
78 /// \param[in] layout Memory layout
79 /// \return Indice vector
80 template <typename T>
81 inline T ComputeIndicesFromGlobalIndex(const T& shape, MemoryLayout layout, const typename T::value_type idx)
82 {
83  const auto size = shape.size();
84  auto strides = ComputeStridesFromShape(shape, layout);
85  T indices(size);
86  auto r = idx;
87  for (std::size_t i = 0; i < size; i++) {
88  indices[i] = int(r / strides[i]);
89  r = r % strides[i];
90  }
91  return indices;
92 }
93 
94 /// \brief Compute global index from indices
95 /// \param[in] strides Strides vector
96 /// \param[in] idx Indice vector
97 /// \return Global index
98 template <typename U, typename V>
99 inline std::size_t ComputeGlobalIndex(const U& strides, const V& idx)
100 {
101  std::size_t globalIndex = 0;
102  const auto size = idx.size();
103  for (std::size_t i = 0; i < size; i++) {
104  globalIndex += strides[size - 1 - i] * idx[size - 1 - i];
105  }
106  return globalIndex;
107 }
108 
109 /// \brief Type checking for all types of a parameter pack, e.g., used in combination with std::is_convertible
110 template <class... Ts>
111 struct and_types : std::true_type {
112 };
113 
114 template <class T0, class... Ts>
115 struct and_types<T0, Ts...> : std::integral_constant<bool, T0{} && and_types<Ts...>{}> {
116 };
117 
118 /// \brief Copy slice of a tensor recursively from here to there
119 /// \param[in] here Source tensor
120 /// \param[in] there Target tensor (slice of source tensor)
121 /// \param[in] mins Minimum of indices for each dimension
122 /// \param[in] maxs Maximum of indices for each dimension
123 /// \param[in] idx Current indices
124 /// \param[in] active Active index needed to stop the recursion
125 ///
126 /// Copy the content of a slice of a tensor from source to target. This is done
127 /// by recursively iterating over the ranges of the slice for each dimension.
128 template <typename T>
129 void RecursiveCopy(T &here, T &there,
130  const std::vector<std::size_t> &mins, const std::vector<std::size_t> &maxs,
131  std::vector<std::size_t> idx, std::size_t active)
132 {
133  const auto size = idx.size();
134  for (std::size_t i = mins[active]; i < maxs[active]; i++) {
135  idx[active] = i;
136  if (active == size - 1) {
137  auto idxThere = idx;
138  for (std::size_t j = 0; j < size; j++) {
139  idxThere[j] -= mins[j];
140  }
141  there(idxThere) = here(idx);
142  } else {
143  Internal::RecursiveCopy(here, there, mins, maxs, idx, active + 1);
144  }
145  }
146 }
147 
148 } // namespace TMVA::Experimental::Internal
149 
150 /// \class TMVA::Experimental::RTensor
151 /// \brief RTensor is a container with contiguous memory and shape information.
152 /// \tparam T Data-type of the tensor
153 ///
154 /// An RTensor is a vector-like container, which has additional shape information.
155 /// The elements of the multi-dimensional container can be accessed by their
156 /// indices in a coherent way without taking care about the one-dimensional memory
157 /// layout of the contiguous storage. This also allows to manipulate the shape
158 /// of the container without moving the actual elements in memory. Another feature
159 /// is that an RTensor can own the underlying contiguous memory but can also represent
160 /// only a view on existing data without owning it.
161 template <typename V, typename C = std::vector<V>>
162 class RTensor {
163 public:
164  // Typedefs
165  using Value_t = V;
166  using Shape_t = std::vector<std::size_t>;
167  using Index_t = Shape_t;
168  using Slice_t = std::vector<Shape_t>;
169  using Container_t = C;
170 
171 private:
172  Shape_t fShape;
173  Shape_t fStrides;
174  std::size_t fSize;
175  MemoryLayout fLayout;
176  Value_t *fData;
177  std::shared_ptr<Container_t> fContainer;
178 
179 protected:
180  void ReshapeInplace(const Shape_t &shape);
181 
182 public:
183  // Constructors
184 
185  /// \brief Construct a tensor as view on data
186  /// \param[in] data Pointer to data contiguous in memory
187  /// \param[in] shape Shape vector
188  /// \param[in] layout Memory layout
189  RTensor(Value_t *data, Shape_t shape, MemoryLayout layout = MemoryLayout::RowMajor)
190  : fShape(shape), fLayout(layout), fData(data), fContainer(NULL)
191  {
192  fSize = Internal::GetSizeFromShape(shape);
193  fStrides = Internal::ComputeStridesFromShape(shape, layout);
194  }
195 
196  /// \brief Construct a tensor as view on data
197  /// \param[in] data Pointer to data contiguous in memory
198  /// \param[in] shape Shape vector
199  /// \param[in] strides Strides vector
200  /// \param[in] layout Memory layout
201  RTensor(Value_t *data, Shape_t shape, Shape_t strides, MemoryLayout layout = MemoryLayout::RowMajor)
202  : fShape(shape), fStrides(strides), fLayout(layout), fData(data), fContainer(NULL)
203  {
204  fSize = Internal::GetSizeFromShape(shape);
205  }
206 
207  /// \brief Construct a tensor owning externally provided data
208  /// \param[in] container Shared pointer to data container
209  /// \param[in] shape Shape vector
210  /// \param[in] layout Memory layout
211  RTensor(std::shared_ptr<Container_t> container, Shape_t shape,
212  MemoryLayout layout = MemoryLayout::RowMajor)
213  : fShape(shape), fLayout(layout), fContainer(container)
214  {
215  fSize = Internal::GetSizeFromShape(shape);
216  fStrides = Internal::ComputeStridesFromShape(shape, layout);
217  fData = &(*container->begin());
218  }
219 
220  /// \brief Construct a tensor owning data initialized with new container
221  /// \param[in] shape Shape vector
222  /// \param[in] layout Memory layout
223  RTensor(Shape_t shape, MemoryLayout layout = MemoryLayout::RowMajor)
224  : fShape(shape), fLayout(layout)
225  {
226  // TODO: Document how data pointer is determined using STL iterator interface.
227  // TODO: Sanitize given container type with type traits
228  fSize = Internal::GetSizeFromShape(shape);
229  fStrides = Internal::ComputeStridesFromShape(shape, layout);
230  fContainer = std::make_shared<Container_t>(fSize);
231  fData = &(*fContainer->begin());
232  }
233 
234  // Access elements
235  Value_t &operator()(const Index_t &idx);
236  const Value_t &operator() (const Index_t &idx) const;
237  template <typename... Idx> Value_t &operator()(Idx... idx);
238  template <typename... Idx> const Value_t &operator() (Idx... idx) const;
239 
240  // Access properties
241  std::size_t GetSize() const { return fSize; }
242  const Shape_t &GetShape() const { return fShape; }
243  const Shape_t &GetStrides() const { return fStrides; }
244  Value_t *GetData() { return fData; }
245  const Value_t *GetData() const { return fData; }
246  std::shared_ptr<Container_t> GetContainer() { return fContainer; }
247  const std::shared_ptr<Container_t> GetContainer() const { return fContainer; }
248  MemoryLayout GetMemoryLayout() const { return fLayout; }
249  bool IsView() const { return fContainer == NULL; }
250  bool IsOwner() const { return !IsView(); }
251 
252  // Copy
253  RTensor<Value_t, Container_t> Copy(MemoryLayout layout = MemoryLayout::RowMajor);
254 
255  // Transformations
256  RTensor<Value_t, Container_t> Transpose();
257  RTensor<Value_t, Container_t> Squeeze();
258  RTensor<Value_t, Container_t> ExpandDims(int idx);
259  RTensor<Value_t, Container_t> Reshape(const Shape_t &shape);
260  RTensor<Value_t, Container_t> Slice(const Slice_t &slice);
261 
262  // Iterator class
263  class Iterator : public std::iterator<std::random_access_iterator_tag, Value_t> {
264  private:
265  RTensor<Value_t, Container_t>& fTensor;
266  Index_t::value_type fGlobalIndex;
267  public:
268  using difference_type = typename std::iterator<std::random_access_iterator_tag, Value_t>::difference_type;
269 
270  Iterator(RTensor<Value_t, Container_t>& x, typename Index_t::value_type idx) : fTensor(x), fGlobalIndex(idx) {}
271  Iterator& operator++() { fGlobalIndex++; return *this; }
272  Iterator operator++(int) { auto tmp = *this; operator++(); return tmp; }
273  Iterator& operator--() { fGlobalIndex--; return *this; }
274  Iterator operator--(int) { auto tmp = *this; operator--(); return tmp; }
275  Iterator operator+(difference_type rhs) const { return Iterator(fTensor, fGlobalIndex + rhs); }
276  Iterator operator-(difference_type rhs) const { return Iterator(fTensor, fGlobalIndex - rhs); }
277  difference_type operator-(const Iterator& rhs) { return fGlobalIndex - rhs.GetGlobalIndex(); }
278  Iterator& operator+=(difference_type rhs) { fGlobalIndex += rhs; return *this; }
279  Iterator& operator-=(difference_type rhs) { fGlobalIndex -= rhs; return *this; }
280  Value_t& operator*()
281  {
282  auto idx = Internal::ComputeIndicesFromGlobalIndex(fTensor.GetShape(), fTensor.GetMemoryLayout(), fGlobalIndex);
283  return fTensor(idx);
284  }
285  bool operator==(const Iterator& rhs) const
286  {
287  if (fGlobalIndex == rhs.GetGlobalIndex()) return true;
288  return false;
289  }
290  bool operator!=(const Iterator& rhs) const { return !operator==(rhs); };
291  bool operator>(const Iterator& rhs) const { return fGlobalIndex > rhs.GetGlobalIndex(); }
292  bool operator<(const Iterator& rhs) const { return fGlobalIndex < rhs.GetGlobalIndex(); }
293  bool operator>=(const Iterator& rhs) const { return fGlobalIndex >= rhs.GetGlobalIndex(); }
294  bool operator<=(const Iterator& rhs) const { return fGlobalIndex <= rhs.GetGlobalIndex(); }
295  typename Index_t::value_type GetGlobalIndex() const { return fGlobalIndex; };
296  };
297 
298  // Iterator interface
299  // TODO: Document that the iterator always iterates following the physical memory layout.
300  Iterator begin() noexcept {
301  return Iterator(*this, 0);
302  }
303  Iterator end() noexcept {
304  return Iterator(*this, fSize);
305  }
306 };
307 
308 /// \brief Reshape tensor in place
309 /// \param[in] shape Shape vector
310 /// Reshape tensor without changing the overall size
311 template <typename Value_t, typename Container_t>
312 inline void RTensor<Value_t, Container_t>::ReshapeInplace(const Shape_t &shape)
313 {
314  const auto size = Internal::GetSizeFromShape(shape);
315  if (size != fSize) {
316  std::stringstream ss;
317  ss << "Cannot reshape tensor with size " << fSize << " into shape { ";
318  for (std::size_t i = 0; i < shape.size(); i++) {
319  if (i != shape.size() - 1) {
320  ss << shape[i] << ", ";
321  } else {
322  ss << shape[i] << " }.";
323  }
324  }
325  throw std::runtime_error(ss.str());
326  }
327 
328  // Compute new strides from shape
329  auto strides = Internal::ComputeStridesFromShape(shape, fLayout);
330  fShape = shape;
331  fStrides = strides;
332 }
333 
334 
335 /// \brief Access elements
336 /// \param[in] idx Index vector
337 /// \return Reference to element
338 template <typename Value_t, typename Container_t>
339 inline Value_t &RTensor<Value_t, Container_t>::operator()(const Index_t &idx)
340 {
341  const auto globalIndex = Internal::ComputeGlobalIndex(fStrides, idx);
342  return fData[globalIndex];
343 }
344 
345 /// \brief Access elements
346 /// \param[in] idx Index vector
347 /// \return Reference to element
348 template <typename Value_t, typename Container_t>
349 inline const Value_t &RTensor<Value_t, Container_t>::operator() (const Index_t &idx) const
350 {
351  const auto globalIndex = Internal::ComputeGlobalIndex(fStrides, idx);
352  return fData[globalIndex];
353 }
354 
355 /// \brief Access elements
356 /// \param[in] idx Indices
357 /// \return Reference to element
358 template <typename Value_t, typename Container_t>
359 template <typename... Idx>
360 Value_t &RTensor<Value_t, Container_t>::operator()(Idx... idx)
361 {
362  static_assert(Internal::and_types<std::is_convertible<Idx, std::size_t>...>{},
363  "Indices are not convertible to std::size_t.");
364  return operator()({static_cast<std::size_t>(idx)...});
365 }
366 
367 /// \brief Access elements
368 /// \param[in] idx Indices
369 /// \return Reference to element
370 template <typename Value_t, typename Container_t>
371 template <typename... Idx>
372 const Value_t &RTensor<Value_t, Container_t>::operator() (Idx... idx) const
373 {
374  static_assert(Internal::and_types<std::is_convertible<Idx, std::size_t>...>{},
375  "Indices are not convertible to std::size_t.");
376  return operator()({static_cast<std::size_t>(idx)...});
377 }
378 
379 /// \brief Transpose
380 /// \returns New RTensor
381 /// The tensor is transposed by inverting the associated memory layout from row-
382 /// major to column-major and vice versa. Therefore, the underlying data is not
383 /// touched.
384 template <typename Value_t, typename Container_t>
385 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Transpose()
386 {
387  // Transpose by inverting memory layout
388  if (fLayout == MemoryLayout::RowMajor) {
389  fLayout = MemoryLayout::ColumnMajor;
390  } else if (fLayout == MemoryLayout::ColumnMajor) {
391  fLayout = MemoryLayout::RowMajor;
392  } else {
393  throw std::runtime_error("Memory layout is not known.");
394  }
395 
396  // Create copy of container
397  RTensor<Value_t, Container_t> x(*this);
398 
399  // Reverse shape
400  std::reverse(x.fShape.begin(), x.fShape.end());
401 
402  // Reverse strides
403  std::reverse(x.fStrides.begin(), x.fStrides.end());
404 
405  return x;
406 }
407 
408 /// \brief Squeeze dimensions
409 /// \returns New RTensor
410 /// Squeeze removes the dimensions of size one from the shape.
411 template <typename Value_t, typename Container_t>
412 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Squeeze()
413 {
414  // Remove dimensions of one and associated strides
415  Shape_t shape;
416  Shape_t strides;
417  for (std::size_t i = 0; i < fShape.size(); i++) {
418  if (fShape[i] != 1) {
419  shape.emplace_back(fShape[i]);
420  strides.emplace_back(fStrides[i]);
421  }
422  }
423 
424  // If all dimensions are 1, we need to keep one.
425  // This does not apply if the inital shape is already empty. Then, return
426  // the empty shape.
427  if (shape.size() == 0 && fShape.size() != 0) {
428  shape.emplace_back(1);
429  strides.emplace_back(1);
430  }
431 
432  // Create copy, attach new shape and strides and return
433  RTensor<Value_t, Container_t> x(*this);
434  x.fShape = shape;
435  x.fStrides = strides;
436  return x;
437 }
438 
439 /// \brief Expand dimensions
440 /// \param[in] idx Index in shape vector where dimension is added
441 /// \returns New RTensor
442 /// Inserts a dimension of one into the shape.
443 template <typename Value_t, typename Container_t>
444 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::ExpandDims(int idx)
445 {
446  // Compose shape vector with additional dimensions and adjust strides
447  const int len = fShape.size();
448  auto shape = fShape;
449  auto strides = fStrides;
450  if (idx < 0) {
451  if (len + idx + 1 < 0) {
452  throw std::runtime_error("Given negative index is invalid.");
453  }
454  shape.insert(shape.end() + 1 + idx, 1);
455  strides.insert(strides.begin() + 1 + idx, 1);
456  } else {
457  if (idx > len) {
458  throw std::runtime_error("Given index is invalid.");
459  }
460  shape.insert(shape.begin() + idx, 1);
461  strides.insert(strides.begin() + idx, 1);
462  }
463 
464  // Create copy, attach new shape and strides and return
465  RTensor<Value_t, Container_t> x(*this);
466  x.fShape = shape;
467  x.fStrides = strides;
468  return x;
469 }
470 
471 /// \brief Reshape tensor
472 /// \param[in] shape Shape vector
473 /// \returns New RTensor
474 /// Reshape tensor without changing the overall size
475 template <typename Value_t, typename Container_t>
476 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Reshape(const Shape_t &shape)
477 {
478  // Create copy, replace and return
479  RTensor<Value_t, Container_t> x(*this);
480  x.ReshapeInplace(shape);
481  return x;
482 }
483 
484 /// \brief Create a slice of the tensor
485 /// \param[in] slice Slice vector
486 /// \returns New RTensor
487 /// A slice is a subset of the tensor defined by a vector of pairs of indices.
488 template <typename Value_t, typename Container_t>
489 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Slice(const Slice_t &slice)
490 {
491  // Sanitize size of slice
492  const auto sliceSize = slice.size();
493  const auto shapeSize = fShape.size();
494  if (sliceSize != shapeSize) {
495  std::stringstream ss;
496  ss << "Size of slice (" << sliceSize << ") is unequal number of dimensions (" << shapeSize << ").";
497  throw std::runtime_error(ss.str());
498  }
499 
500  // Sanitize slice indices
501  // TODO: Sanitize slice indices
502  /*
503  for (std::size_t i = 0; i < sliceSize; i++) {
504  }
505  */
506 
507  // Convert -1 in slice to proper pair of indices
508  // TODO
509 
510  // Recompute shape and size
511  Shape_t shape(sliceSize);
512  for (std::size_t i = 0; i < sliceSize; i++) {
513  shape[i] = slice[i][1] - slice[i][0];
514  }
515  auto size = Internal::GetSizeFromShape(shape);
516 
517  // Determine first element contributing to the slice and get the data pointer
518  Value_t *data;
519  Shape_t idx(sliceSize);
520  for (std::size_t i = 0; i < sliceSize; i++) {
521  idx[i] = slice[i][0];
522  }
523  data = &operator()(idx);
524 
525  // Create copy and modify properties
526  RTensor<Value_t, Container_t> x(*this);
527  x.fData = data;
528  x.fShape = shape;
529  x.fSize = size;
530 
531  // Squeeze tensor and return
532  return x.Squeeze();
533 }
534 
535 /// Copy RTensor to new object
536 /// \param[in] layout Memory layout of the new RTensor
537 /// \returns New RTensor
538 /// The operation copies all elements of the current RTensor to a new RTensor
539 /// with the given layout contiguous in memory. Note that this copies by default
540 /// to a row major memory layout.
541 template <typename Value_t, typename Container_t>
542 inline RTensor<Value_t, Container_t> RTensor<Value_t, Container_t>::Copy(MemoryLayout layout)
543 {
544  // Create new tensor with zeros owning the memory
545  RTensor<Value_t, Container_t> r(fShape, layout);
546 
547  // Copy over the elements from this tensor
548  const auto mins = Shape_t(fShape.size());
549  const auto maxs = fShape;
550  auto idx = mins;
551  Internal::RecursiveCopy(*this, r, mins, maxs, idx, 0);
552 
553  return r;
554 }
555 
556 /// \brief Pretty printing
557 /// \param[in] os Output stream
558 /// \param[in] x RTensor
559 /// \return Modified output stream
560 template <typename T>
561 std::ostream &operator<<(std::ostream &os, RTensor<T> &x)
562 {
563  const auto shapeSize = x.GetShape().size();
564  if (shapeSize == 1) {
565  os << "{ ";
566  const auto size = x.GetSize();
567  for (std::size_t i = 0; i < size; i++) {
568  os << x({i});
569  if (i != size - 1)
570  os << ", ";
571  }
572  os << " }";
573  } else if (shapeSize == 2) {
574  os << "{";
575  const auto shape = x.GetShape();
576  for (std::size_t i = 0; i < shape[0]; i++) {
577  os << " { ";
578  for (std::size_t j = 0; j < shape[1]; j++) {
579  os << x({i, j});
580  if (j < shape[1] - 1) {
581  os << ", ";
582  } else {
583  os << " ";
584  }
585  }
586  os << "}";
587  }
588  os << " }";
589  } else {
590  os << "{ printing not yet implemented for this rank }";
591  }
592  return os;
593 }
594 
595 } // namespace TMVA::Experimental
596 } // namespace TMVA
597 
598 namespace cling {
599 template <typename T>
600 std::string printValue(TMVA::Experimental::RTensor<T> *x)
601 {
602  std::stringstream ss;
603  ss << *x;
604  return ss.str();
605 }
606 } // namespace cling
607 
608 #endif // TMVA_RTENSOR