├── .gitignore ├── README.md ├── LICENSE ├── main.cpp └── tensor.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Numpy-like C++ Tensor Class for Matrix Algebra 2 | 3 | ``` 4 | #include "tensor.hpp" 5 | 6 | // Initilize 2 x 3 matrix 7 | float data_a[] = {1,2,3,4,5,6}; 8 | Tensor A = Tensor(2,3, data_a); 9 | 10 | 1 2 3 11 | 4 5 6 12 | 13 | // Transpose matrix 14 | Tensor B = A.transpose(); 15 | 16 | 1 4 17 | 2 5 18 | 3 6 19 | 20 | // Elementwise add 21 | Tensor C = A + A; 22 | 23 | 2 4 6 24 | 8 10 12 25 | 26 | // Elementwise multiply 27 | Tensor D = B * B; 28 | 29 | 1 16 30 | 4 25 31 | 9 36 32 | 33 | // Matrix multiply 34 |  Tensor F = Tensor::matmul(A,B); 35 | 36 | 14 32 37 | 32 77 38 | 39 | // Read element at postion (0,0) 40 | float a = A(0,0); // 1 41 | 42 | // Write element at postion (0,0) 43 | B(0,0) = 10; 44 | 45 | 10 4 46 | 2 5 47 | 3 6 48 | 49 | 50 | 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mathias Sundholm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "tensor.hpp" 3 | 4 | 5 | int main(){ 6 | float data_a[] = {1,2,3,4,5,6}; 7 | Tensor A = Tensor(2,3, data_a); 8 | std::cout << "A:" << std::endl << A.toString() << std::endl; 9 | 10 | Tensor B = A.transpose(); 11 | 12 | std::cout << "B:" << std::endl << B.toString() << std::endl; 13 | 14 | Tensor C = A + A; 15 | std::cout << "C = A + A:" << std::endl << C.toString() << std::endl; 16 | 17 | Tensor D = C * 2; 18 | std::cout << "D = C * 2:" << std::endl << D.toString() << std::endl; 19 | 20 | Tensor E = B * B; 21 | std::cout << "E = B * B:" << std::endl << E.toString() << std::endl; 22 | 23 | std::cout << "E(0,0): " << std::to_string(E(0,0)) << std::endl; 24 | std::cout << "E(1,0): " << std::to_string(E(1,0)) << std::endl; 25 | std::cout << "E(1,1): " << std::to_string(E(0,1)) << std::endl; 26 | std::cout << "E(1,1): " << std::to_string(E(1,1)) << std::endl; 27 | 28 | E(0,0) = 2; 29 | std::cout << "E(0,0): " << std::to_string(E(0,0)) << std::endl; 30 | 31 | Tensor F = Tensor::matmul(A,B); 32 | std::cout << "A:" << std::endl << A.toString() << std::endl; 33 | std::cout << "B:" << std::endl << B.toString() << std::endl; 34 | std::cout << "F:" << std::endl << F.toString() << std::endl; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /tensor.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Tensor { 8 | 9 | public: 10 | Tensor(unsigned int rows, unsigned int cols, float* data = nullptr); 11 | 12 | Tensor copy(); 13 | unsigned int cols() const; 14 | unsigned int rows() const; 15 | float* data() const; 16 | Tensor transpose(); 17 | std::string toString(); 18 | 19 | // Static methods 20 | static Tensor matmul(const Tensor &A, const Tensor &B); 21 | 22 | static Tensor zeros(unsigned int rows, unsigned int cols); 23 | static Tensor ones(unsigned int rows, unsigned int cols); 24 | static Tensor range(unsigned int rows, unsigned int cols); 25 | static Tensor rand(unsigned int rows, unsigned int cols); 26 | static Tensor randn(unsigned int rows, unsigned int cols); 27 | 28 | static Tensor zeros_like(const Tensor &other); 29 | static Tensor ones_like(const Tensor &other); 30 | static Tensor range_like(const Tensor &other); 31 | static Tensor rand_like(const Tensor &other); 32 | static Tensor randn_like(const Tensor &other); 33 | 34 | // Operators 35 | Tensor operator+(const Tensor &other); 36 | Tensor operator+(const float c); 37 | Tensor operator-(const Tensor &other); 38 | Tensor operator-(const float c); 39 | Tensor operator*(const Tensor &other); 40 | Tensor operator*(const float c); 41 | 42 | float& operator[](unsigned int index); 43 | float& operator()(unsigned int x, unsigned int y) const; 44 | 45 | private: 46 | // Internal input check 47 | static void checkEqualSize(const Tensor &A, const Tensor &B); 48 | static void checkMatMulPossible(const Tensor &A, const Tensor &B); 49 | static void checkInBounds(const Tensor &A, unsigned int x, unsigned int y); 50 | 51 | float *m_data; 52 | unsigned int m_rows; 53 | unsigned int m_cols; 54 | 55 | }; 56 | 57 | 58 | Tensor::Tensor(unsigned int rows, unsigned int cols, float* data){ 59 | m_data = new float[rows*cols](); 60 | if(data){ 61 | std::memcpy(m_data,data,rows*cols*sizeof(float)); 62 | } 63 | m_cols = cols; 64 | m_rows = rows; 65 | } 66 | 67 | unsigned int Tensor::rows() const{ 68 | return m_rows; 69 | } 70 | 71 | unsigned int Tensor::cols() const{ 72 | return m_cols; 73 | } 74 | 75 | float* Tensor::data() const{ 76 | return m_data; 77 | } 78 | 79 | void Tensor::checkInBounds(const Tensor &A, unsigned int x, unsigned int y){ 80 | if(x >= A.m_rows || y >= A.m_cols) 81 | { 82 | std::cout << "Error: Index out of bounds."<< std::endl; 83 | throw 0; 84 | } 85 | } 86 | 87 | void Tensor::checkEqualSize(const Tensor &A, const Tensor &B){ 88 | if(A.m_rows != B.m_rows || A.m_cols != B.m_cols) 89 | { 90 | std::cout << "Error: " << "Tensors must have the same size" << std::endl; 91 | throw 0; 92 | } 93 | } 94 | 95 | void Tensor::checkMatMulPossible(const Tensor &A, const Tensor &B){ 96 | if(A.m_cols != B.m_rows) 97 | { 98 | std::cout << "MatMult size error: A.cols() = " << A.m_cols 99 | << " != B.rows() = "<< B.m_rows << " " << std::endl; 100 | throw 0; 101 | } 102 | } 103 | 104 | Tensor Tensor::copy(){ 105 | Tensor copy = Tensor(m_rows,m_cols,m_data); 106 | return copy; 107 | } 108 | 109 | Tensor Tensor::transpose(){ 110 | 111 | Tensor transposed = Tensor(m_cols,m_rows); 112 | //swap data for rows and columns 113 | for(int i = 0; i < m_cols*m_rows; i++){ 114 | int j = (i % m_cols)*m_rows + i/m_cols; 115 | transposed[j] = m_data[i]; 116 | } 117 | return transposed; 118 | } 119 | 120 | /* 121 | Static methods 122 | */ 123 | 124 | Tensor Tensor::matmul(const Tensor &A, const Tensor &B){ 125 | checkMatMulPossible(A,B); 126 | unsigned int N = A.m_rows; 127 | unsigned int K = A.m_cols; 128 | unsigned int M = B.m_cols; 129 | 130 | Tensor C = Tensor(N,M); 131 | for(int k = 0; k < K; k++){ 132 | for(int i = 0; i < N; i++){ 133 | for(int j = 0; j < M; j++){ 134 | C(i,j) += A(i,k)*B(k,j); 135 | } 136 | } 137 | } 138 | return C; 139 | } 140 | 141 | Tensor Tensor::zeros(unsigned int rows, unsigned int cols){ 142 | Tensor tensor = Tensor(rows,cols); 143 | for(int i = 0; i < cols*rows; i++){ 144 | tensor[i] = 0; 145 | } 146 | return tensor; 147 | } 148 | 149 | Tensor Tensor::ones(unsigned int rows, unsigned int cols){ 150 | Tensor tensor = Tensor(rows,cols); 151 | for(int i = 0; i < cols*rows; i++){ 152 | tensor[i] = 1; 153 | } 154 | return tensor; 155 | } 156 | 157 | Tensor Tensor::range(unsigned int rows, unsigned int cols){ 158 | Tensor tensor = Tensor(rows,cols); 159 | for(int i = 0; i < cols*rows; i++){ 160 | tensor[i] = i; 161 | } 162 | return tensor; 163 | } 164 | 165 | Tensor Tensor::rand(unsigned int rows, unsigned int cols){ 166 | Tensor tensor = Tensor(rows,cols); 167 | std::random_device rd; 168 | std::mt19937 gen(rd()); 169 | std::uniform_real_distribution<> dis(0, 1); 170 | for(int i = 0; i < cols*rows; i++){ 171 | tensor[i] = dis(gen); 172 | } 173 | return tensor; 174 | } 175 | 176 | Tensor Tensor::randn(unsigned int rows, unsigned int cols){ 177 | Tensor tensor = Tensor(rows,cols); 178 | std::random_device rd; 179 | std::mt19937 gen(rd()); 180 | std::normal_distribution<> dis(0, 1); 181 | for(int i = 0; i < cols*rows; i++){ 182 | tensor[i] = dis(gen); 183 | } 184 | return tensor; 185 | } 186 | 187 | Tensor Tensor::zeros_like(const Tensor &other){ 188 | return Tensor::zeros(other.rows(),other.cols()); 189 | } 190 | Tensor Tensor::ones_like(const Tensor &other){ 191 | return Tensor::ones(other.rows(),other.cols()); 192 | } 193 | Tensor Tensor::range_like(const Tensor &other){ 194 | return Tensor::range(other.rows(),other.cols()); 195 | } 196 | Tensor Tensor::rand_like(const Tensor &other){ 197 | return Tensor::rand(other.rows(),other.cols()); 198 | } 199 | 200 | /* 201 | Operators 202 | */ 203 | float& Tensor::operator[](unsigned int index){ 204 | return m_data[index]; 205 | } 206 | 207 | float& Tensor::operator()(unsigned int x, unsigned int y) const { 208 | checkInBounds(*this,x,y); 209 | return m_data[x*m_cols+y]; 210 | } 211 | 212 | 213 | Tensor Tensor::operator+(const Tensor &other){ 214 | checkEqualSize(*this,other); 215 | Tensor result = this->copy(); 216 | for(int i = 0; i < m_rows*m_cols; i++){ 217 | result.m_data[i] += other.m_data[i]; 218 | } 219 | return result; 220 | } 221 | 222 | Tensor Tensor::operator+(const float c){ 223 | Tensor result = this->copy(); 224 | for(int i = 0; i < m_rows*m_cols; i++){ 225 | result.m_data[i] += c; 226 | } 227 | return result; 228 | } 229 | 230 | Tensor Tensor::operator-(const Tensor &other){ 231 | checkEqualSize(*this,other); 232 | Tensor result = this->copy(); 233 | for(int i = 0; i < m_rows*m_cols; i++){ 234 | result.m_data[i] -= other.m_data[i]; 235 | } 236 | return result; 237 | } 238 | 239 | Tensor Tensor::operator-(const float c){ 240 | Tensor result = this->copy(); 241 | for(int i = 0; i < m_rows*m_cols; i++){ 242 | result.m_data[i] -= c; 243 | } 244 | return result; 245 | } 246 | 247 | 248 | Tensor Tensor::operator*(const Tensor &other){ 249 | checkEqualSize(*this,other); 250 | Tensor result = this->copy(); 251 | for(int i = 0; i < m_rows*m_cols; i++){ 252 | result.m_data[i] *= other.m_data[i]; 253 | } 254 | return result; 255 | } 256 | 257 | Tensor Tensor::operator*(const float c){ 258 | Tensor result = this->copy(); 259 | for(int i = 0; i < m_rows*m_cols; i++){ 260 | result.m_data[i] *= c; 261 | } 262 | return result; 263 | } 264 | 265 | std::string Tensor::toString(){ 266 | std::stringstream ss; 267 | 268 | for(int i = 0; i < m_rows; i++){ 269 | for(int j = 0; j < m_cols; j++){ 270 | ss << std::setprecision(2) << m_data[i*m_cols+j] << " "; 271 | } 272 | ss << std::endl; 273 | } 274 | return ss.str(); 275 | } --------------------------------------------------------------------------------