MU Library
ndarray.hpp
Go to the documentation of this file.
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright 2024 XCENA Inc.
3 
4 #pragma once
5 
6 #include <algorithm>
7 #include <array>
8 #include <cstddef>
9 #include <cstdint>
10 #include <initializer_list>
11 #include <type_traits>
12 
13 namespace mu
14 {
15 
21 template <typename T>
22 class NDArray
23 {
24 public:
25  static constexpr std::int64_t MAX_DIMS = 10;
26  using ShapeArray = std::array<std::int64_t, MAX_DIMS>;
27  using StridesArray = std::array<std::int64_t, MAX_DIMS>;
28 
29  NDArray() = default;
30  NDArray(T* data, std::initializer_list<int64_t> shape)
31  : data_(const_cast<void*>(reinterpret_cast<const void*>(data))),
32  dims_(shape.size()),
33  elemSize_(sizeof(T))
34  {
35  std::fill(shape_.begin(), shape_.end(), 0);
36  std::fill(strides_.begin(), strides_.end(), 0);
37  std::copy(shape.begin(), shape.end(), shape_.begin());
38  calculateSizeAndStrides();
39  }
40 
42  : data_(const_cast<void*>(reinterpret_cast<const void*>(data))),
43  dims_(0),
44  elemSize_(sizeof(T))
45  {
46  std::fill(shape_.begin(), shape_.end(), 0);
47  std::fill(strides_.begin(), strides_.end(), 0);
48  for (std::size_t i = 0; i < MAX_DIMS; ++i)
49  {
50  if (shape[i] != 0)
51  {
52  shape_[i] = shape[i];
53  dims_++;
54  }
55  }
56  calculateSizeAndStrides();
57  }
58 
59  NDArray(void* data, const ShapeArray& shape, std::int64_t elemSize)
60  : data_(data),
61  dims_(0),
62  elemSize_(elemSize)
63  {
64  std::fill(shape_.begin(), shape_.end(), 0);
65  std::fill(strides_.begin(), strides_.end(), 0);
66  for (std::size_t i = 0; i < MAX_DIMS; ++i)
67  {
68  if (shape[i] != 0)
69  {
70  shape_[i] = shape[i];
71  dims_++;
72  }
73  }
74  calculateSizeAndStrides();
75  }
76 
77  ~NDArray() = default;
78 
79  T* data()
80  {
81  return reinterpret_cast<T*>(data_);
82  }
83  const T* data() const
84  {
85  return reinterpret_cast<const T*>(data_);
86  }
87 
88  void setData(void* data)
89  {
90  data_ = data;
91  }
92 
93  const ShapeArray& shape() const
94  {
95  return shape_;
96  }
97  const StridesArray& strides() const
98  {
99  return strides_;
100  }
101  std::int64_t dims() const
102  {
103  return dims_;
104  }
105  std::int64_t elemSize() const
106  {
107  return elemSize_;
108  }
109  std::int64_t size() const
110  {
111  return size_;
112  }
113  std::int64_t numElements() const
114  {
115  return numElements_;
116  }
117 
118  template <typename... Idxs>
119  T& operator()(Idxs... idxs)
120  {
121  return const_cast<T&>(static_cast<const NDArray&>(*this)(idxs...));
122  }
123 
124  template <typename... Idxs>
125  const T& operator()(Idxs... idxs) const
126  {
127  return reinterpret_cast<const T*>(data_)[computeOffset({idxs...})];
128  }
129 
130  // Subndarray access with operator[] - always returns a NDArray
131  NDArray<T> operator[](std::int64_t idx)
132  {
133  auto const_result = static_cast<const NDArray&>(*this)[idx];
134  return NDArray<T>(
135  const_cast<T*>(const_result.data()),
136  const_result.shape());
137  }
138 
139  NDArray<const T> operator[](std::int64_t idx) const
140  {
141  ShapeArray new_shape;
142  std::fill(new_shape.begin(), new_shape.end(), 0);
143  std::copy(shape_.begin() + 1, shape_.end(), new_shape.begin());
144 
145  return NDArray<const T>(
146  reinterpret_cast<const T*>(data_) + idx * strides_[0],
147  new_shape);
148  }
149 
150  // Implicit conversion to scalar for 0-dimensional ndarrays
151  template <typename U = T>
152  operator typename std::enable_if_t<std::is_same_v<U, std::remove_const_t<T>>, T&>()
153  {
154  if (dims_ != 0)
155  {
156  // Return reference to first element as fallback
157  return *reinterpret_cast<T*>(data_);
158  }
159  return *reinterpret_cast<T*>(data_);
160  }
161 
162  operator const T&() const
163  {
164  if (dims_ != 0)
165  {
166  // Return reference to first element as fallback
167  return *reinterpret_cast<const T*>(data_);
168  }
169  return *reinterpret_cast<const T*>(data_);
170  }
171 
172  // Explicit scalar() method for clearer intent
173  T& scalar()
174  {
175  return const_cast<T&>(static_cast<const NDArray&>(*this).scalar());
176  }
177 
178  const T& scalar() const
179  {
180  if (dims_ != 0)
181  {
182  // Return reference to first element as fallback
183  return *reinterpret_cast<const T*>(data_);
184  }
185  return *reinterpret_cast<const T*>(data_);
186  }
187 
188  // Multi-dimensional access with bounds checking
189  template <typename... Idxs>
190  T& at(Idxs... idxs)
191  {
192  return const_cast<T&>(static_cast<const NDArray&>(*this).at(idxs...));
193  }
194 
195  template <typename... Idxs>
196  const T& at(Idxs... idxs) const
197  {
198  std::array<std::int64_t, MAX_DIMS> indices = {idxs...};
199  if (sizeof...(Idxs) != static_cast<std::size_t>(dims_))
200  {
201  // Return reference to first element as fallback
202  return *reinterpret_cast<const T*>(data_);
203  }
204  for (std::size_t i = 0; i < indices.size(); ++i)
205  {
206  if (indices[i] < 0 || indices[i] >= shape_[i])
207  {
208  // Return reference to first element as fallback
209  return *reinterpret_cast<const T*>(data_);
210  }
211  }
212  return operator()(idxs...);
213  }
214 
215 private:
216  void* data_;
217  ShapeArray shape_;
218  StridesArray strides_;
219  std::int64_t dims_;
220  std::int64_t size_;
221  std::int64_t elemSize_;
222  std::int64_t numElements_;
223 
224  void calculateSizeAndStrides()
225  {
226  // Calculate size
227  size_ = elemSize_;
228  for (std::int64_t i = 0; i < dims_; ++i)
229  {
230  size_ *= shape_[i];
231  }
232  numElements_ = size_ / elemSize_;
233 
234  // Calculate strides
235  std::int64_t stride = 1;
236  for (std::int64_t i = dims_ - 1; i >= 0; --i)
237  {
238  strides_[i] = stride;
239  stride *= shape_[i];
240  }
241  }
242 
243  std::int64_t computeOffset(const std::array<std::int64_t, MAX_DIMS>& idxs) const
244  {
245  std::int64_t offset = 0;
246  for (std::int64_t i = 0; i < dims_; ++i)
247  {
248  offset += idxs[i] * strides_[i];
249  }
250  return offset;
251  }
252 };
253 
254 } // namespace mu
NDArray is a class that can be used in both host and device code. NDArray is automatically divided in...
Definition: ndarray.hpp:23
const T & operator()(Idxs... idxs) const
Definition: ndarray.hpp:125
std::int64_t elemSize() const
Definition: ndarray.hpp:105
const T & scalar() const
Definition: ndarray.hpp:178
NDArray(T *data, std::initializer_list< int64_t > shape)
Definition: ndarray.hpp:30
const StridesArray & strides() const
Definition: ndarray.hpp:97
NDArray(void *data, const ShapeArray &shape, std::int64_t elemSize)
Definition: ndarray.hpp:59
T * data()
Definition: ndarray.hpp:79
const T * data() const
Definition: ndarray.hpp:83
T & operator()(Idxs... idxs)
Definition: ndarray.hpp:119
NDArray()=default
std::array< std::int64_t, MAX_DIMS > ShapeArray
Definition: ndarray.hpp:26
std::int64_t numElements() const
Definition: ndarray.hpp:113
~NDArray()=default
const ShapeArray & shape() const
Definition: ndarray.hpp:93
T & scalar()
Definition: ndarray.hpp:173
const T & at(Idxs... idxs) const
Definition: ndarray.hpp:196
void setData(void *data)
Definition: ndarray.hpp:88
std::int64_t dims() const
Definition: ndarray.hpp:101
NDArray< const T > operator[](std::int64_t idx) const
Definition: ndarray.hpp:139
NDArray< T > operator[](std::int64_t idx)
Definition: ndarray.hpp:131
std::int64_t size() const
Definition: ndarray.hpp:109
NDArray(T *data, const ShapeArray &shape)
Definition: ndarray.hpp:41
T & at(Idxs... idxs)
Definition: ndarray.hpp:190
std::array< std::int64_t, MAX_DIMS > StridesArray
Definition: ndarray.hpp:27
static constexpr std::int64_t MAX_DIMS
Definition: ndarray.hpp:25
Definition: assert.hpp:9