Proyecto Final - Turinmachin
Recreación del minijuego de matemáticas de Brain-Age usando redes neuronales
Loading...
Searching...
No Matches
tensor.h
Go to the documentation of this file.
1#ifndef PROG3_NN_FINAL_PROJECT_V2025_01_TENSOR_H
2#define PROG3_NN_FINAL_PROJECT_V2025_01_TENSOR_H
3
4#include <algorithm>
5#include <array>
6#include <cstddef>
7#include <functional>
8#include <iostream>
9#include <iterator>
10#include <numeric>
11#include <stdexcept>
12#include <vector>
13
14namespace {
15
23 template <std::size_t Size>
24 constexpr void apply_with_counter(const auto fn, const std::array<std::size_t, Size>& size) {
25 std::array<std::size_t, Size> index{};
26
27 const std::size_t total_size = std::ranges::fold_left(size, 1, std::multiplies());
28
29 for (std::size_t i = 0; i < total_size; ++i) {
30 fn(index);
31
32 index[0]++;
33
34 for (std::size_t j = 0; j < Size; ++j) {
35 if (index[j] < size[j]) {
36 // Carry stops here
37 break;
38 }
39
40 index[j] = 0;
41 if (j < Size - 1) {
42 index[j + 1]++;
43 }
44 }
45 }
46 }
47} // namespace
48
49namespace utec::algebra {
50
62 template <typename T, size_t Rank>
63 class Tensor {
64 std::array<size_t, Rank> m_shape;
65 std::array<size_t, Rank> m_steps;
66 std::vector<T> m_data;
67
68 void update_steps() {
69 std::size_t current_step = 1;
70
71 for (std::size_t i = Rank - 1; i != static_cast<std::size_t>(-1); --i) {
72 m_steps[i] = current_step;
73 current_step *= m_shape[i];
74 }
75 }
76
77 template <typename... Idxs>
78 requires(sizeof...(Idxs) == Rank)
79 constexpr auto physical_index(const Idxs... idxs) const -> std::size_t {
80 const std::array<std::size_t, Rank> idxs_arr{static_cast<std::size_t>(idxs)...};
81
82 std::size_t physical_index = 0;
83
84 for (std::size_t i = 0; i < Rank; ++i) {
85 if (idxs_arr[i] >= m_shape[i]) {
86 throw std::out_of_range("Tensor index out of bounds");
87 }
88 physical_index += m_steps[i] * idxs_arr[i];
89 }
90
91 return physical_index;
92 }
93
94 public:
100 explicit Tensor(const std::array<size_t, Rank>& shape)
101 : m_shape(shape),
102 m_data(std::accumulate(shape.begin(),
103 shape.end(),
104 static_cast<size_t>(1),
105 std::multiplies())) {
106 update_steps();
107 }
108
114 template <typename... Dims>
115 requires(sizeof...(Dims) == Rank)
116 explicit Tensor(const Dims... dims)
117 : m_shape{static_cast<size_t>(dims)...},
118 m_data(std::ranges::fold_left(m_shape, 1, std::multiplies())) {
119 update_steps();
120 }
121
122 [[nodiscard]] constexpr auto operator==(const Tensor<T, Rank>& other) const -> bool {
123 return m_shape == other.m_shape && m_data == other.m_data;
124 }
125
126 auto operator=(std::initializer_list<T> list) -> Tensor<T, Rank>& {
127 if (list.size() != m_data.size()) {
128 throw std::invalid_argument("Data size does not match tensor size");
129 }
130
131 std::copy(list.begin(), list.end(), m_data.begin());
132 return *this;
133 }
134
141 constexpr auto operator()(const auto... idxs) -> T& {
142 return m_data[physical_index(idxs...)];
143 }
144
145 [[nodiscard]] constexpr auto operator()(const auto... idxs) const -> const T& {
146 return m_data[physical_index(idxs...)];
147 }
148
149 auto operator()(const std::array<size_t, Rank>& idxs) -> T& {
150 size_t idx = 0;
151 for (size_t dim = 0; dim < Rank; dim++) {
152 idxs[dim] < m_shape[dim] ? idx += m_steps[dim] * idxs[dim]
153 : throw std::out_of_range("Index out of bounds");
154 }
155 return m_data[idx];
156 }
157
158 auto operator()(const std::array<size_t, Rank>& idxs) const -> const T& {
159 size_t idx = 0;
160 for (size_t dim = 0; dim < Rank; dim++) {
161 idxs[dim] < m_shape[dim] ? idx += m_steps[dim] * idxs[dim]
162 : throw std::out_of_range("Index out of bounds");
163 }
164 return m_data[idx];
165 }
166
167 auto operator[](const size_t idx) -> T& {
168 return m_data.at(idx);
169 }
170
171 auto operator[](const size_t idx) const -> const T& {
172 return m_data.at(idx);
173 }
174
175 [[nodiscard]] auto size() const -> size_t {
176 return m_data.size();
177 }
178
179 auto shape() const noexcept -> const std::array<size_t, Rank>& {
180 return m_shape;
181 }
182
188 void reshape(const std::array<size_t, Rank>& new_shape) {
189 m_data.resize(std::ranges::fold_left(new_shape, 1, std::multiplies()));
190 m_shape = new_shape;
191 update_steps();
192 }
193
199 template <typename... Dims>
200 requires(sizeof...(Dims) == Rank)
201 void reshape(const Dims... dims) {
202 std::array<size_t, Rank> new_shape{static_cast<size_t>(dims)...};
203 m_data.resize(std::ranges::fold_left(new_shape, 1, std::multiplies()));
204 m_shape = new_shape;
205 update_steps();
206 }
207
213 void fill(const T& value) noexcept {
214 std::ranges::fill(m_data, value);
215 }
216
223 auto row(const size_t index) const -> Tensor<T, 2>
224 requires(Rank == 2)
225 {
226 if (index >= m_shape[0]) {
227 throw std::out_of_range("Row index out of bounds");
228 }
229
230 Tensor<T, 2> result(1, m_shape[1]);
231 for (size_t j = 0; j < m_shape[1]; ++j) {
232 result(0, j) = (*this)(index, j);
233 }
234 return result;
235 }
236
243 void set_row(const size_t index, const Tensor<T, 2>& row_tensor)
244 requires(Rank == 2)
245 {
246 if (row_tensor.shape()[0] != 1 || row_tensor.shape()[1] != m_shape[1]) {
247 throw std::invalid_argument("Row shape does not match");
248 }
249
250 for (size_t j = 0; j < m_shape[1]; ++j) {
251 (*this)(index, j) = row_tensor(0, j);
252 }
253 }
254
261 auto slice(const size_t index) const -> Tensor<T, 2>
262 requires(Rank == 3)
263 {
264 if (index >= m_shape[0]) {
265 throw std::out_of_range("Index out of bounds");
266 }
267
268 Tensor<T, 2> result(m_shape[1], m_shape[2]);
269 for (size_t j = 0; j < m_shape[1]; ++j) {
270 for (size_t k = 0; k < m_shape[2]; ++k) {
271 result(j, k) = (*this)(index, j, k);
272 }
273 }
274
275 return result;
276 }
277
283 void set_slice(const size_t index, const Tensor<T, 2>& slice)
284 requires(Rank == 3)
285 {
286 if (index >= m_shape[0]) {
287 throw std::out_of_range("Index out of bounds");
288 }
289
290 if (slice.shape()[0] != m_shape[1] || slice.shape()[1] != m_shape[2]) {
291 throw std::invalid_argument("Slice shape does not match");
292 }
293
294 for (size_t j = 0; j < m_shape[1]; ++j) {
295 for (size_t k = 0; k < m_shape[2]; ++k) {
296 (*this)(index, j, k) = slice(j, k);
297 }
298 }
299 }
300
308 auto broadcast(const Tensor<T, Rank>& rhs, auto fn) const -> Tensor<T, Rank> {
309 if (m_shape == rhs.m_shape) {
310 // Element-wise
311 Tensor<T, Rank> result{m_shape};
312 for (std::size_t i = 0; i < m_data.size(); ++i) {
313 result[i] = fn(m_data[i], rhs[i]);
314 }
315 return result;
316 }
317
318 std::array<std::size_t, Rank> result_shape;
319
320 for (std::size_t i = 0; i < Rank; ++i) {
321 if (m_shape[i] == rhs.m_shape[i]) {
322 result_shape[i] = m_shape[i];
323 } else if (m_shape[i] == 1 || rhs.m_shape[i] == 1) {
324 result_shape[i] = std::max(m_shape[i], rhs.m_shape[i]);
325 } else {
326 throw std::invalid_argument(
327 "Shapes do not match and they are not compatible for broadcasting");
328 }
329 }
330
331 Tensor<T, Rank> result{result_shape};
332
333 apply_with_counter(
334 [&](const auto& result_index) {
335 std::array<std::size_t, Rank> lhs_index{result_index};
336 std::array<std::size_t, Rank> rhs_index{result_index};
337
338 for (std::size_t i = 0; i < Rank; ++i) {
339 lhs_index[i] %= m_shape[i];
340 }
341
342 for (std::size_t i = 0; i < Rank; ++i) {
343 rhs_index[i] %= rhs.m_shape[i];
344 }
345
346 std::apply(result, result_index) =
347 fn(std::apply(*this, lhs_index), std::apply(rhs, rhs_index));
348 },
349 result_shape);
350
351 return result;
352 }
353
354 auto operator+(const Tensor<T, Rank>& other) const -> Tensor<T, Rank> {
355 return broadcast(other, std::plus());
356 }
357
358 auto operator-(const Tensor<T, Rank>& other) const -> Tensor<T, Rank> {
359 return broadcast(other, std::minus());
360 }
361
362 auto operator*(const Tensor<T, Rank>& other) const -> Tensor<T, Rank> {
363 return broadcast(other, std::multiplies());
364 }
365
366 auto operator/(const Tensor<T, Rank>& other) const -> Tensor<T, Rank> {
367 return broadcast(other, std::divides());
368 }
369
370 auto operator+(const T& scalar) const -> Tensor<T, Rank> {
371 Tensor<T, Rank> result(m_shape);
372 std::ranges::transform(m_data, result.m_data.begin(),
373 [&](const T& value) { return value + scalar; });
374 return result;
375 }
376
377 auto operator-(const T& scalar) const -> Tensor<T, Rank> {
378 Tensor<T, Rank> result(m_shape);
379 std::ranges::transform(m_data, result.m_data.begin(),
380 [&](const T& value) { return value - scalar; });
381 return result;
382 }
383
384 auto operator*(const T& scalar) const -> Tensor<T, Rank> {
385 Tensor<T, Rank> result(m_shape);
386 std::ranges::transform(m_data, result.m_data.begin(),
387 [&](const T& value) { return value * scalar; });
388 return result;
389 }
390
391 auto operator/(const T& scalar) const -> Tensor<T, Rank> {
392 Tensor<T, Rank> result(m_shape);
393 std::ranges::transform(m_data, result.m_data.begin(),
394 [&](const T& value) { return value / scalar; });
395 return result;
396 }
397
398 friend auto operator+(const T& scalar, const Tensor& tensor) -> Tensor<T, Rank> {
399 Tensor<T, Rank> result(tensor.m_shape);
400 std::ranges::transform(tensor.m_data, result.m_data.begin(),
401 [&](const T& value) { return scalar + value; });
402 return result;
403 }
404
405 friend auto operator-(const T& scalar, const Tensor& tensor) -> Tensor<T, Rank> {
406 Tensor<T, Rank> result(tensor.m_shape);
407 std::ranges::transform(tensor.m_data, result.m_data.begin(),
408 [&](const T& value) { return scalar - value; });
409 return result;
410 }
411
412 friend auto operator*(const T& scalar, const Tensor& tensor) -> Tensor<T, Rank> {
413 Tensor<T, Rank> result(tensor.m_shape);
414 std::ranges::transform(tensor.m_data, result.m_data.begin(),
415 [&](const T& value) { return scalar * value; });
416 return result;
417 }
418
419 friend auto operator/(const T& scalar, const Tensor& tensor) -> Tensor<T, Rank> {
420 Tensor<T, Rank> result(tensor.m_shape);
421 std::ranges::transform(tensor.m_data, result.m_data.begin(),
422 [&](const T& value) { return scalar / value; });
423 return result;
424 }
425
426 auto operator-() const -> Tensor<T, Rank> {
427 Tensor<T, Rank> result(m_shape);
428 std::ranges::transform(m_data, result.m_data.begin(), std::negate());
429 return result;
430 }
431
432 friend auto operator<<(std::ostream& out, const Tensor<T, Rank>& tensor)
433 -> std::ostream& requires(Rank > 1) {
434 const auto& shape = tensor.shape();
435 std::array<size_t, Rank> index{};
436
437 std::function<void(size_t, size_t)> print_recursive = [&](size_t dim,
438 const size_t indent) {
439 out << std::string(indent, ' ') << "{\n";
440
441 for (size_t i = 0; i < shape[dim]; ++i) {
442 index[dim] = i;
443
444 if (dim == Rank - 2) {
445 out << std::string(indent + 2, ' ');
446 for (size_t j = 0; j < shape[Rank - 1]; ++j) {
447 index[Rank - 1] = j;
448 out << tensor(index) << " ";
449 }
450 out << "\n";
451 } else {
452 print_recursive(dim + 1, indent + 2);
453 }
454 }
455
456 out << std::string(indent, ' ') << "}\n";
457 };
458
459 print_recursive(0, 0);
460 return out;
461 }
462
463 friend auto operator<<(std::ostream& out, const Tensor<T, Rank>& tensor)
464 -> std::ostream& requires(Rank == 1) {
465 std::ranges::copy(tensor, std::ostream_iterator<T>(out, " "));
466 return out;
467 }
468
469 auto begin() noexcept {
470 return m_data.begin();
471 }
472
473 auto end() noexcept {
474 return m_data.end();
475 }
476
477 [[nodiscard]] auto begin() const noexcept {
478 return m_data.begin();
479 }
480
481 [[nodiscard]] auto end() const noexcept {
482 return m_data.end();
483 }
484
490 [[nodiscard]] constexpr auto transpose_2d() const -> Tensor<T, 2> {
491 Tensor<T, 2> result(m_shape[1], m_shape[0]);
492
493 for (std::size_t i = 0; i < m_shape[0]; ++i) {
494 for (std::size_t j = 0; j < m_shape[1]; ++j) {
495 result(j, i) = (*this)(i, j);
496 }
497 }
498
499 return result;
500 }
501
507 [[nodiscard]] constexpr auto transpose_2d() const -> Tensor<T, Rank>
508 requires(Rank > 2)
509 {
510 std::array<std::size_t, Rank> new_shape{m_shape};
511 std::swap(new_shape[Rank - 2], new_shape[Rank - 1]);
512
513 Tensor<T, Rank> result{new_shape};
514 std::array<std::size_t, Rank - 2> size{};
515
516 std::copy(m_shape.begin(), m_shape.end() - 2, size.begin());
517
518 apply_with_counter(
519 [&](const auto& index) {
520 std::array<std::size_t, Rank> full_index;
521 std::copy(index.begin(), index.end(), full_index.begin());
522
523 for (std::size_t i = 0; i < m_shape[Rank - 2]; ++i) {
524 for (std::size_t j = 0; j < m_shape[Rank - 1]; ++j) {
525 full_index[Rank - 2] = i;
526 full_index[Rank - 1] = j;
527 const T src = std::apply(*this, full_index);
528
529 full_index[Rank - 2] = j;
530 full_index[Rank - 1] = i;
531 T& dest = std::apply(result, full_index);
532
533 dest = src;
534 }
535 }
536 },
537 size);
538
539 return result;
540 }
541
548 constexpr auto apply(auto fn) const -> Tensor<T, Rank> {
549 Tensor<T, Rank> result(m_shape);
550 std::ranges::transform(m_data, result.m_data.begin(), fn);
551 return result;
552 }
553 };
554
562 template <typename T>
563 [[nodiscard]] constexpr auto matrix_product(const Tensor<T, 2>& lhs, const Tensor<T, 2>& rhs)
564 -> Tensor<T, 2> {
565 if (lhs.shape()[1] != rhs.shape()[0]) {
566 throw std::invalid_argument("Incompatible matrix dimensions for multiplication");
567 }
568
569 // Simple matrix multiplication
570 Tensor<T, 2> result(lhs.shape()[0], rhs.shape()[1]);
571
572 for (std::size_t i = 0; i < result.shape()[0]; ++i) {
573 for (std::size_t j = 0; j < result.shape()[1]; ++j) {
574 for (std::size_t k = 0; k < lhs.shape()[1]; ++k) {
575 result(i, j) += lhs(i, k) * rhs(k, j);
576 }
577 }
578 }
579
580 return result;
581 }
582
590 template <typename T, std::size_t Rank>
591 requires(Rank > 2)
592 [[nodiscard]] constexpr auto matrix_product(const Tensor<T, Rank>& lhs,
593 const Tensor<T, Rank>& rhs) -> Tensor<T, Rank> {
594 for (std::size_t i = 0; i < Rank - 2; ++i) {
595 if (lhs.shape()[i] != rhs.shape()[i]) {
596 throw std::invalid_argument("Incompatible batch dimensions for multiplication");
597 }
598 }
599
600 std::array<std::size_t, Rank> new_shape{lhs.shape()};
601 new_shape[Rank - 1] = rhs.shape()[Rank - 1];
602
603 Tensor<T, Rank> result(new_shape);
604 std::array<std::size_t, Rank - 2> size{};
605 std::copy(lhs.shape().begin(), lhs.shape().end() - 2, size.begin());
606
607 apply_with_counter(
608 [&](const auto& index) {
609 std::array<std::size_t, Rank> full_index;
610 std::ranges::copy(index, full_index.begin());
611
612 for (std::size_t i = 0; i < result.shape()[Rank - 2]; ++i) {
613 for (std::size_t j = 0; j < result.shape()[Rank - 1]; ++j) {
614 for (std::size_t k = 0; k < lhs.shape()[Rank - 1]; ++k) {
615 full_index[Rank - 2] = i;
616 full_index[Rank - 1] = k;
617 const T& src1 = std::apply(lhs, full_index);
618
619 full_index[Rank - 2] = k;
620 full_index[Rank - 1] = j;
621 const T& src2 = std::apply(rhs, full_index);
622
623 full_index[Rank - 2] = i;
624 full_index[Rank - 1] = j;
625 T& dest = std::apply(result, full_index);
626
627 dest += src1 * src2;
628 }
629 }
630 }
631 },
632 size);
633
634 return result;
635 }
636
637} // namespace utec::algebra
638
639#endif
Tensor(const std::array< size_t, Rank > &shape)
Constructor que inicializa el tensor con una forma dada.
Definition tensor.h:100
Representa un tensor de tipo T y rango Rank.
Definition tensor.h:63
friend auto operator*(const T &scalar, const Tensor &tensor) -> Tensor< T, Rank >
Definition tensor.h:412
auto operator-() const -> Tensor< T, Rank >
Definition tensor.h:426
void reshape(const std::array< size_t, Rank > &new_shape)
Cambia la forma del tensor actual.
Definition tensor.h:188
auto operator-(const Tensor< T, Rank > &other) const -> Tensor< T, Rank >
Definition tensor.h:358
auto size() const -> size_t
Definition tensor.h:175
auto shape() const noexcept -> const std::array< size_t, Rank > &
Definition tensor.h:179
constexpr auto transpose_2d() const -> Tensor< T, 2 >
Trasponer un tensor de dimension 2. @complexity O(n).
Definition tensor.h:490
constexpr auto operator()(const auto... idxs) const -> const T &
Definition tensor.h:145
auto operator/(const T &scalar) const -> Tensor< T, Rank >
Definition tensor.h:391
auto operator[](const size_t idx) -> T &
Definition tensor.h:167
auto operator+(const T &scalar) const -> Tensor< T, Rank >
Definition tensor.h:370
constexpr auto apply(auto fn) const -> Tensor< T, Rank >
Aplica una funcion a todos los elementos del tensor.
Definition tensor.h:548
auto row(const size_t index) const -> Tensor< T, 2 > requires(Rank==2)
Genera tensor con fila particular.
Definition tensor.h:223
void reshape(const Dims... dims)
Cambia la forma del tensor actual. @taram Dims Nuevo "Rank" del tensor. @complexity O(Rank).
Definition tensor.h:201
auto operator()(const std::array< size_t, Rank > &idxs) -> T &
Definition tensor.h:149
void fill(const T &value) noexcept
Llena la data de un tesor con un valor.
Definition tensor.h:213
auto operator/(const Tensor< T, Rank > &other) const -> Tensor< T, Rank >
Definition tensor.h:366
friend auto operator+(const T &scalar, const Tensor &tensor) -> Tensor< T, Rank >
Definition tensor.h:398
auto end() const noexcept
Definition tensor.h:481
auto operator+(const Tensor< T, Rank > &other) const -> Tensor< T, Rank >
Definition tensor.h:354
auto end() noexcept
Definition tensor.h:473
auto slice(const size_t index) const -> Tensor< T, 2 > requires(Rank==3)
Genera tensor con fila particular para tensor de Rank 3.
Definition tensor.h:261
auto operator*(const T &scalar) const -> Tensor< T, Rank >
Definition tensor.h:384
Tensor(const Dims... dims)
Constructor variádico para inicializar la forma del tensor.
Definition tensor.h:116
constexpr auto transpose_2d() const -> Tensor< T, Rank > requires(Rank > 2)
Trasponer un tensor de dimension n mayor a 2. @complexity O(n).
Definition tensor.h:507
friend auto operator-(const T &scalar, const Tensor &tensor) -> Tensor< T, Rank >
Definition tensor.h:405
friend auto operator<<(std::ostream &out, const Tensor< T, Rank > &tensor) -> std::ostream &requires(Rank==1)
Definition tensor.h:463
friend auto operator/(const T &scalar, const Tensor &tensor) -> Tensor< T, Rank >
Definition tensor.h:419
constexpr auto operator==(const Tensor< T, Rank > &other) const -> bool
Definition tensor.h:122
auto operator[](const size_t idx) const -> const T &
Definition tensor.h:171
auto begin() noexcept
Definition tensor.h:469
auto operator*(const Tensor< T, Rank > &other) const -> Tensor< T, Rank >
Definition tensor.h:362
auto broadcast(const Tensor< T, Rank > &rhs, auto fn) const -> Tensor< T, Rank >
Realiza Broadcasting para un tensor.
Definition tensor.h:308
constexpr auto operator()(const auto... idxs) -> T &
Acceso a un elemento por índices.
Definition tensor.h:141
auto operator()(const std::array< size_t, Rank > &idxs) const -> const T &
Definition tensor.h:158
friend auto operator<<(std::ostream &out, const Tensor< T, Rank > &tensor) -> std::ostream &requires(Rank > 1)
Definition tensor.h:432
void set_slice(const size_t index, const Tensor< T, 2 > &slice)
Cambia el subtensor asignado.
Definition tensor.h:283
auto begin() const noexcept
Definition tensor.h:477
void set_row(const size_t index, const Tensor< T, 2 > &row_tensor)
Cambia fila especifica de un tensor.
Definition tensor.h:243
auto operator-(const T &scalar) const -> Tensor< T, Rank >
Definition tensor.h:377
Tensor(const std::array< size_t, Rank > &shape)
Constructor que inicializa el tensor con una forma dada.
Definition tensor.h:100
auto operator=(std::initializer_list< T > list) -> Tensor< T, Rank > &
Definition tensor.h:126
Definition tensor.h:49
constexpr auto matrix_product(const Tensor< T, 2 > &lhs, const Tensor< T, 2 > &rhs) -> Tensor< T, 2 >
Realiza producto matricial entre 2 tensores de dimension 2.
Definition tensor.h:563