Skip to content

Commit eb0560c

Browse files
committed
Added unit tests, refactored cmake
1 parent 5055455 commit eb0560c

15 files changed

Lines changed: 326 additions & 61 deletions

src/backend/computational_graph/add_node.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
namespace graph {
1717
class AddNode final : public GraphNode {
1818
public:
19-
AddNode(Tensor* t1, Tensor* t2) {
20-
parents = {t1, t2};
21-
}
19+
explicit AddNode(Tensor* t1, Tensor* t2) : GraphNode({t1, t2}) {}
2220

2321
AddNode(const AddNode& other) = delete;
2422
AddNode& operator=(const AddNode& other) = delete;

src/backend/computational_graph/elementwise_mul_node.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
namespace graph {
1717
class ElementwiseMulNode final : public GraphNode {
1818
public:
19-
ElementwiseMulNode(Tensor* t1, Tensor* t2) {
20-
parents = {t1, t2};
21-
}
19+
explicit ElementwiseMulNode(Tensor* t1, Tensor* t2) : GraphNode({t1, t2}) {}
2220

2321
ElementwiseMulNode(const ElementwiseMulNode& other) = delete;
2422
ElementwiseMulNode& operator=(const ElementwiseMulNode& other) = delete;

src/backend/computational_graph/graph_node.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
#include <vector>
1717
#include <memory>
1818

19+
#include <utility>
20+
1921
namespace graph {
2022
class GraphNode {
2123
protected:
2224
std::vector<Tensor*> parents;
23-
25+
explicit GraphNode(std::vector<Tensor*> parents) : parents{std::move(parents)}{}
26+
2427
public:
2528
virtual std::vector<std::shared_ptr<Tensor>> backward(const Tensor& upstreamGrad) = 0;
2629

src/backend/computational_graph/matmul_node.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
namespace graph {
1919
class MatMulNode final : public GraphNode {
2020
public:
21-
MatMulNode(Tensor* t1, Tensor* t2) {
22-
parents = {t1, t2};
23-
}
21+
explicit MatMulNode(Tensor* t1, Tensor* t2): GraphNode({t1, t2}) {}
2422

2523
MatMulNode(const MatMulNode& other) = delete;
2624
MatMulNode& operator=(const MatMulNode& other) = delete;

src/backend/computational_graph/relu_node.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ using namespace std;
1717
using namespace graph;
1818

1919
vector<shared_ptr<Tensor>> ReLuNode::backward(const Tensor& upstreamGrad) {
20+
constexpr ftype zero = 0.0;
21+
2022
auto res = make_shared<Tensor>(upstreamGrad.getDims().toVector(), upstreamGrad.getDevice(), false);
2123
for(tensorSize_t i=0; i<upstreamGrad.getSize(); i++){
22-
(*res)[i] = upstreamGrad[i];
24+
auto v = upstreamGrad.get(i);
25+
res->set(v > zero ? v : zero, i);
2326
}
2427
return {std::move(res)};
2528
}

src/backend/computational_graph/relu_node.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
namespace graph {
1919
class ReLuNode final : public GraphNode {
2020
public:
21-
ReLuNode(Tensor* t) {
22-
parents = {t};
23-
}
21+
explicit ReLuNode(Tensor* t): GraphNode({t}) {}
2422

2523
ReLuNode(const ReLuNode& other) = delete;
2624
ReLuNode& operator=(const ReLuNode& other) = delete;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @file scalar_op_nodes.cpp
3+
* @author Robert Baumgartner (r.baumgartner-1@tudelft.nl)
4+
* @brief
5+
* @version 0.1
6+
* @date 2026-02-17
7+
*
8+
* @copyright Copyright (c) 2026
9+
*
10+
*/
11+
12+
#include "scalar_op_nodes.h"
13+
14+
#include <utility>
15+
16+
using namespace std;
17+
using namespace graph;
18+
19+
vector<shared_ptr<Tensor>> graph::ScalarAddNode::backward(const Tensor& upstreamGrad) {
20+
return {make_shared<Tensor>(upstreamGrad.createDeepCopy())};
21+
}
22+
23+
vector<shared_ptr<Tensor>> graph::ScalarMulNode::backward(const Tensor& upstreamGrad) {
24+
auto res = make_shared<Tensor>(upstreamGrad.createDeepCopy());
25+
for(tensorSize_t i=0; i<res->getSize(); i++){
26+
res->set(res->get(i) * factor, i);
27+
}
28+
return {std::move(res)};
29+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @file scalar_op_nodes.h
3+
* @author Robert Baumgartner (r.baumgartner-1@tudelft.nl)
4+
* @brief
5+
* @version 0.1
6+
* @date 2026-02-17
7+
*
8+
* @copyright Copyright (c) 2026
9+
*
10+
*/
11+
12+
#pragma once
13+
14+
#include "graph_node.h"
15+
16+
namespace graph {
17+
class ScalarAddNode final : public GraphNode {
18+
public:
19+
explicit ScalarAddNode(Tensor* t) : GraphNode({t}) {}
20+
21+
ScalarAddNode(const ScalarAddNode& other) = delete;
22+
ScalarAddNode& operator=(const ScalarAddNode& other) = delete;
23+
24+
ScalarAddNode(ScalarAddNode&& other) = default;
25+
ScalarAddNode& operator=(ScalarAddNode&& other) = default;
26+
27+
~ScalarAddNode() noexcept = default;
28+
29+
std::vector<std::shared_ptr<Tensor>> backward(const Tensor& upstreamGrad) override;
30+
};
31+
32+
class ScalarMulNode final : public GraphNode {
33+
private:
34+
const ftype factor;
35+
36+
public:
37+
explicit ScalarMulNode(Tensor* t, const ftype factor) : GraphNode({t}), factor{factor} {}
38+
39+
ScalarMulNode(const ScalarMulNode& other) = delete;
40+
ScalarMulNode& operator=(const ScalarMulNode& other) = delete;
41+
42+
ScalarMulNode(ScalarMulNode&& other) = default;
43+
ScalarMulNode& operator=(ScalarMulNode&& other) = default;
44+
45+
~ScalarMulNode() noexcept = default;
46+
47+
std::vector<std::shared_ptr<Tensor>> backward(const Tensor& upstreamGrad) override;
48+
};
49+
}

src/backend/data_modeling/tensor.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "computational_graph/add_node.h"
1616
#include "computational_graph/matmul_node.h"
1717
#include "computational_graph/elementwise_mul_node.h"
18+
#include "computational_graph/scalar_op_nodes.h"
1819
#include "computational_graph/topological_sort.h"
1920

2021
#include <utility>
@@ -148,6 +149,36 @@ ftype Tensor::tensorValues_t::operator[](const tensorSize_t idx) const {
148149
return values[idx];
149150
}
150151

152+
void Tensor::tensorValues_t::set(ftype v, tensorSize_t idx) {
153+
if(idx >= size)
154+
throw std::out_of_range("Out of range for tensor");
155+
156+
switch(device){
157+
case Device::CPU:
158+
values[idx] = v;
159+
return;
160+
case Device::CUDA:
161+
__throw_runtime_error("Not implemented for CUDA yet");
162+
}
163+
164+
__throw_runtime_error("Should never reach here.");
165+
}
166+
167+
ftype Tensor::tensorValues_t::get(tensorSize_t idx) {
168+
if(idx >= size)
169+
throw std::out_of_range("Out of range for tensor");
170+
171+
switch(device){
172+
case Device::CPU:
173+
return values[idx];
174+
case Device::CUDA:
175+
__throw_runtime_error("Not implemented for CUDA yet");
176+
}
177+
178+
__throw_runtime_error("Should never reach here.");
179+
return 0; // suppress warnings
180+
}
181+
151182
/********************************************************************
152183
*************************** Tensor **********************************
153184
********************************************************************/
@@ -445,14 +476,28 @@ Tensor Tensor::operator*(ftype scalar) const {
445476
for (tensorSize_t i = 0; i < values->getSize(); ++i) {
446477
(*res.values)[i] = (*values)[i] * scalar;
447478
}
479+
480+
if(requiresGrad){
481+
res.cgNode = std::make_shared<graph::ScalarMulNode>(const_cast<Tensor*>(this), scalar);
482+
}
483+
448484
return res;
449485
}
450486

451487
Tensor Tensor::operator/(ftype scalar) const {
488+
if(scalar==0.0){
489+
__throw_runtime_error("Cannot divide by zero.");
490+
}
491+
452492
Tensor res(dims, values->getDevice(), requiresGrad);
453493
for (tensorSize_t i = 0; i < values->getSize(); ++i) {
454494
(*res.values)[i] = (*values)[i] / scalar;
455495
}
496+
497+
if(requiresGrad){
498+
res.cgNode = std::make_shared<graph::ScalarMulNode>(const_cast<Tensor*>(this), (ftype)(1.0)/scalar);
499+
}
500+
456501
return res;
457502
}
458503

@@ -461,6 +506,11 @@ Tensor Tensor::operator+(ftype scalar) const {
461506
for (tensorSize_t i = 0; i < values->getSize(); ++i) {
462507
(*res.values)[i] = (*values)[i] + scalar;
463508
}
509+
510+
if(requiresGrad){
511+
res.cgNode = std::make_shared<graph::ScalarAddNode>(const_cast<Tensor*>(this));
512+
}
513+
464514
return res;
465515
}
466516

@@ -469,6 +519,11 @@ Tensor Tensor::operator-(ftype scalar) const {
469519
for (tensorSize_t i = 0; i < values->getSize(); ++i) {
470520
(*res.values)[i] = (*values)[i] - scalar;
471521
}
522+
523+
if(requiresGrad){
524+
res.cgNode = std::make_shared<graph::ScalarAddNode>(const_cast<Tensor*>(this));
525+
}
526+
472527
return res;
473528
}
474529

@@ -521,6 +576,16 @@ void Tensor::backward() {
521576
}
522577
}
523578

579+
/**
580+
* @brief Get gradients
581+
*/
582+
const shared_ptr<Tensor>& Tensor::getGrads() const {
583+
if(!grads){
584+
__throw_runtime_error("Tensor has no gradients.");
585+
}
586+
return grads;
587+
}
588+
524589
/**
525590
* @brief Sometimes we do accept negative dim-values. In accordance with e.g.
526591
* NumPy we map from the end to the beginning in that case.

src/backend/data_modeling/tensor.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ class Tensor final {
8282
ftype& operator[](const tensorSize_t idx);
8383
ftype operator[](const tensorSize_t idx) const;
8484

85+
void set(ftype v, tensorSize_t idx);
86+
ftype get(tensorSize_t idx);
87+
8588
tensorSize_t getSize() const noexcept;
8689

8790
// needed for gradient descent
@@ -151,6 +154,16 @@ class Tensor final {
151154
values->resize(this->dims.getSize());
152155
}
153156

157+
explicit Tensor(const std::vector<tensorDim_t>& dims, std::vector<ftype>&& initValues, bool requiresGrad=true) :
158+
Tensor{dims, std::move(initValues), Tensor::getDefaultDevice(), requiresGrad} {}
159+
160+
explicit Tensor(const std::vector<tensorDim_t>& dims, std::vector<ftype>&& initValues, Device d, bool requiresGrad=true) :
161+
Tensor{dims, d, requiresGrad} {
162+
for(tensorSize_t i=0; i<initValues.size(); i++){
163+
values->set(initValues[i], i);
164+
}
165+
}
166+
154167
/**
155168
* Tensors can become very large. Deleting those two
156169
* helps us to not accidentally copy something we do not
@@ -198,11 +211,11 @@ class Tensor final {
198211
friend Tensor operator*(ftype scalar, const Tensor& tensor);
199212
friend Tensor operator+(ftype scalar, const Tensor& tensor);
200213

201-
ftype& operator[](const tensorSize_t idx);
202-
ftype operator[](const tensorSize_t idx) const;
203-
204214
void backward();
205215

216+
bool hasGrads() const noexcept { return grads!=nullptr; }
217+
const std::shared_ptr<Tensor>& getGrads() const;
218+
206219
void transposeThis() noexcept;
207220
void transposeThis(int dim1, int dim2) noexcept;
208221

0 commit comments

Comments
 (0)