forked from deepmodeling/abacus-develop
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathndarray.h
More file actions
226 lines (215 loc) · 8.41 KB
/
ndarray.h
File metadata and controls
226 lines (215 loc) · 8.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/**
* @file NDArray.h
* @author your name (you@domain.com)
* @brief under the restriction of C++11, a simple alternative to std::vector<T> + std::mdspan. In source_base/module_container/ATen/tensor.h, tensor class provides a cross-device container, but std::string is not supported. Therefore, this class is to provide a general (but CPU-only) container for multi-dimensional data. It can easily convert to ontainer::Tensor.
* @version 0.1
* @date 2024-04-24
*
* @copyright Copyright (c) 2024
*
*/
#ifndef NDARRAY_H
#define NDARRAY_H
#include <vector>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <type_traits>
#include <numeric>
// for heterogeneous computing, we can use ATen::Tensor
//#include "./module_container/ATen/tensor.h"
/**
* @brief under the restriction of C++11, a simple alternative to std::vector<T> + std::mdspan. In source_base/module_container/ATen/tensor.h, tensor class provides a cross-device container, but std::string is not supported. Therefore, this class is to provide a general (but CPU-only) container for multi-dimensional data. It can easily convert to container::Tensor.
*
* @tparam T
*/
template<typename T>
class NDArray
{
// align with STL container implementation, there are several functions compulsory to be implemented
// constructor: default, copy, move, initializer_list
// operator: =, ==, !=, <, <=, >, >=
// iterator: begin, cbegin, end, cend
// capacity: size, empty, max_size, reserve, shrink_to_fit
// element access: [], at, front, back, data
// modifiers: clear, insert, erase, push_back, pop_back, resize, swap
// allocator: get_allocator
public:
// constructors
/**
* @brief Construct a new NDArray object
*
*/
NDArray()= delete;
// initializer_list constructor
NDArray(std::initializer_list<size_t> il) : shape_(il), data_(std::accumulate(shape_.begin(), shape_.end(), 1, std::multiplies<size_t>())) {}
NDArray(std::initializer_list<int> il) : shape_(il.begin(), il.end()), data_(std::accumulate(shape_.begin(), shape_.end(), 1, std::multiplies<size_t>())) {}
// variadic template constructor, (delegate constructor)
template<typename... Args> NDArray(const size_t idx, Args... args) : NDArray({idx, static_cast<size_t>(args)...}) {}
template<typename... Args> NDArray(const int& idx, Args... args) : NDArray({idx, static_cast<int>(args)...}) {} // not happy with this because size_t can have larger range
// copy constructor
NDArray(const NDArray& other) : data_(other.data_), shape_(other.shape_) {}
// move constructor
NDArray(NDArray&& other) : data_(std::move(other.data_)), shape_(std::move(other.shape_)) {}
// destructor
~NDArray() {}
// operators
/**
* @brief = operator, copy assignment
*
* @param other
* @return NDArray&
*/
NDArray& operator=(const NDArray& other)
{
if (this != &other)
{
data_ = other.data_;
shape_ = other.shape_;
}
return *this;
}
/**
* @brief = operator, move assignment
*/
NDArray& operator=(NDArray&& other)
{
if (this != &other)
{
data_ = std::move(other.data_);
shape_ = std::move(other.shape_);
}
return *this;
}
/**
* @brief == operator
*
* @param other
* @return true if the data and shape are the same
* @return false otherwise
*/
bool operator==(const NDArray& other) const { return data_ == other.data_ && shape_ == other.shape_; }
/**
* @brief != operator
*
* @param other
* @return true if the data and shape are different
* @return false otherwise
*/
bool operator!=(const NDArray& other) const { return !(*this == other); }
// other operators are not generally supported
// element access
/**
* @brief at function
*
* @tparam Args
* @param args indices of the element
* @return T& or const T&
*/
template<typename... Args> T& at(const size_t idx, Args... args) { return data_[index(idx, args...)]; }
template<typename... Args> const T& at(const size_t idx, Args... args) const { return data_[index(idx, args...)]; }
/**
* @brief [] operator
*
* @tparam Args
* @param args indices of the element
* @return T& or const T&
*/
template<typename... Args> T& operator()(const size_t idx, Args... args) { return data_[index(idx, args...)]; }
template<typename... Args> const T& operator()(const size_t idx, Args... args) const { return data_[index(idx, args...)]; }
// front
T& front() { return data_.front(); }
const T& front() const { return data_.front(); }
// back
T& back() { return data_.back(); }
const T& back() const { return data_.back(); }
// data
T* data() { return data_.data(); }
const T* data() const { return data_.data(); }
// iterators
// iterators on the whole data
T* begin() { return data_.data(); }
T* end() { return data_.data() + data_.size(); }
const T* cbegin() const { return data_.data(); }
const T* cend() const { return data_.data() + data_.size(); }
// iterators on different dimensions
// capacity
// size
size_t size() const { return data_.size(); }
size_t size(const size_t& dim) const { return shape_.at(dim); }
// empty
bool empty() const { return data_.empty(); }
// multi-dimensional specific
// shape
const std::vector<size_t>& shape() const { return shape_; }
// reshape
template<typename... Args>
void reshape(Args... args)
{
// DEVELP WARNING: what if arg = -2? :)
// save args into a vector
//std::vector<int64_t> dims = {static_cast<int64_t>(args)...};
std::vector<int64_t> dims = {args...};
// assert number of -1 in dims is at most 1
// -1 is not type-safe!!!
size_t count = std::count_if(dims.begin(), dims.end(), [](size_t i) { return i == -1; });
assert(count <= 1);
// if there is -1, calculate the size
if (count == 1)
{
size_t size = 1;
for (size_t i = 0; i < dims.size(); ++i)
{
if (dims[i] != -1)
{
size *= dims[i];
}
}
size_t idx = std::find(dims.begin(), dims.end(), -1) - dims.begin();
dims[idx] = data_.size() / size;
}
// calculate the size
size_t size = std::accumulate(dims.begin(), dims.end(), 1, std::multiplies<size_t>());
// assert size is the same
assert(size == data_.size());
// assign dims to shape_
std::copy(dims.begin(), dims.end(), shape_.begin());
}
// interface to ATen::Tensor, but constraint to int, double, float, std::complex<float>, std::complex<double>
/**
* @brief SFINAE (Substitution Failure Is Not An Error) to_tensor function, only if T is int, double, float, std::complex<float>, std::complex<double>, otherwise there is no such function
*
* @return container::Tensor, only if T is int, double, float, std::complex<float>, std::complex<double>
*/
// std::enable_if<
// std::is_same<T, int>::value
// || std::is_same<T, double>::value
// || std::is_same<T, float>::value
// || std::is_same<T, std::complex<float>>::value
// || std::is_same<T, std::complex<double>>::value, container::Tensor
// >::type to_tensor() const
// {
// container::TensorShape shape(shape_);
// container::Tensor result = container::Tensor(container::DataTypeToEnum<T>::value, shape);
// std::memcpy(result.data<T>(), data_.data(), data_.size() * sizeof(T));
// return result;
// }
template<typename... Args>
size_t index(const size_t idx, Args... args) const
{
assert(sizeof...(args) == shape_.size() - 1); // assert the indices are the same as the shape
size_t indices[] = {idx, static_cast<size_t>(args)...};
size_t index = 0;
for (size_t i = 0; i < shape_.size(); ++i)
{
index += indices[i] * std::accumulate(shape_.begin() + i + 1, shape_.end(), 1, std::multiplies<size_t>());
}
assert(index < data_.size()); // assert the index is within the data
return index;
}
private:
std::vector<size_t> shape_;
// for GPU-compatible data container, will be replaced by raw pointer
std::vector<T> data_;
};
#endif // NDARRAY_H