diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 31adbd5..eaf6a59 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,7 @@ jobs: submodules: recursive - name: Install dependencies run: | + sudo apt-get update sudo apt-get install -y cmake ninja-build ccache scons - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -37,6 +38,7 @@ jobs: submodules: recursive - name: Install dependencies run: | + sudo apt-get update sudo apt-get install -y cmake ninja-build ccache scons - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -81,6 +83,7 @@ jobs: submodules: true - name: Install dependencies run: | + sudo apt-get update sudo apt-get install -y cmake ninja-build ccache gcovr lcov scons - uses: actions/checkout@v4 with: @@ -102,7 +105,7 @@ jobs: cmake --build build --parallel - name: Test run: | - build/bin/run_tests + build/test/run_test env: CTEST_OUTPUT_ON_FAILURE: 1 - name: Generate lcov Coverage Data diff --git a/CMakeLists.txt b/CMakeLists.txt index a8e8360..98d5ae1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,22 @@ cmake_minimum_required(VERSION 3.20) +set(CMAKE_CXX_STANDARD 11) -project(cpp_template) - -include(cmake/configure.cmake) +set(ProjectName "itlab") +project(${ProjectName}) include_directories(include) enable_testing() -add_subdirectory(3rdparty) -add_subdirectory(app) -add_subdirectory(include) + +add_subdirectory(3rdparty/googletest) add_subdirectory(src) add_subdirectory(test) + +# REPORT +message( STATUS "") +message( STATUS "General configuration for ${PROJECT_NAME}") +message( STATUS "======================================") +message( STATUS "") +message( STATUS " Configuration: ${CMAKE_BUILD_TYPE}") +message( STATUS "") \ No newline at end of file diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/include/graph/graph.h b/include/graph/graph.h new file mode 100644 index 0000000..734edc6 --- /dev/null +++ b/include/graph/graph.h @@ -0,0 +1,42 @@ +#ifndef GRAPH_H +#define GRAPH_H + +#include +#include +#include + +class Vertex { + private: + int id_; + std::list neighbors_; + + public: + Vertex(int id_); + void addNeighbor(int neighbor); + void removeNeighbor(int neighbor); + void print() const; + int getId() const; + const std::list& getNeighbors() const; +}; + +class Graph { + private: + std::unordered_map vertices_; + + public: + Graph(); + void addVertex(int id_); + void addEdge(int u, int v); + void removeEdge(int u, int v); + void removeVertex(int id_); + int getVertices() const; + int getEdges() const; + bool empty() const; + void printGraph() const; + bool bfs_helper(int start, int vert, bool flag, std::vector* v_ord); + bool hasPath(int u, int v); + std::vector BFS(int start); + ~Graph(); +}; + +#endif diff --git a/include/tensor/tensor.h b/include/tensor/tensor.h new file mode 100644 index 0000000..b1df5c4 --- /dev/null +++ b/include/tensor/tensor.h @@ -0,0 +1,98 @@ +#ifndef TENSOR_H +#define TENSOR_H + +#include +#include +#include +#include +#include +#include + +struct Shape { + std::vector dimensions; + size_t total_elements; + + Shape(std::vector dims); + + size_t get_rank() const; +}; + +enum Layout : std::uint8_t { kNchw, kNhwc, kNd }; + +template +class Tensor { + public: + Shape shape; + Layout layout; + std::vector data; + + Tensor(const Shape &sh, Layout l = Layout::kNd); + Tensor(std::vector dims, Layout l = Layout::kNd); + + size_t get_linear_index(const std::vector &indices) const; + + T &at(const std::vector &indices); + const T &at(const std::vector &indices) const; +}; + +template +Tensor::Tensor(const Shape &sh, Layout l) + : shape(sh), layout(l), data(sh.total_elements) {} + +template +Tensor::Tensor(std::vector dims, Layout l) + : Tensor(Shape(std::move(dims)), l) {} + +template +size_t Tensor::get_linear_index(const std::vector &indices) const { + if (indices.size() != shape.get_rank()) { + throw std::runtime_error("Incorrect number of indices provided."); + } + for (size_t i = 0; i < indices.size(); ++i) { + if (indices[i] >= shape.dimensions[i]) { + throw std::out_of_range("Index out of range for dimension"); + } + } + + size_t linear_index = 0; + size_t stride = 1; + + if (shape.get_rank() == 4) { + if (layout == Layout::kNchw) { + linear_index = indices[0] * (shape.dimensions[1] * shape.dimensions[2] * + shape.dimensions[3]) + + indices[1] * (shape.dimensions[2] * shape.dimensions[3]) + + indices[2] * shape.dimensions[3] + indices[3]; + } else if (layout == Layout::kNhwc) { + linear_index = indices[0] * (shape.dimensions[1] * shape.dimensions[2] * + shape.dimensions[3]) + + indices[1] * (shape.dimensions[2] * shape.dimensions[3]) + + indices[2] * shape.dimensions[3] + indices[3]; + } else { + linear_index = indices[0] * (shape.dimensions[1] * shape.dimensions[2] * + shape.dimensions[3]) + + indices[1] * (shape.dimensions[2] * shape.dimensions[3]) + + indices[2] * shape.dimensions[3] + indices[3]; + } + } else { + std::vector reversed_dims = shape.dimensions; + std::reverse(reversed_dims.begin(), reversed_dims.end()); + for (int i = static_cast(reversed_dims.size()) - 1; i >= 0; --i) { + linear_index += indices[i] * stride; + stride *= reversed_dims[i]; + } + } + + return linear_index; +} + +template +T &Tensor::at(const std::vector &indices) { + return data[get_linear_index(indices)]; +} + +template +const T &Tensor::at(const std::vector &indices) const { + return data[get_linear_index(indices)]; +} +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e69de29..af77d7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE HEADER_FILES "${CMAKE_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE SOURCE_FILES "${CMAKE_SOURCE_DIR}/src/*.cpp") + +add_library(${ProjectName} STATIC ${SOURCE_FILES} ${HEADER_FILES}) +target_sources(${ProjectName} PRIVATE ${HEADER_FILES}) + +target_include_directories(${ProjectName} PUBLIC ${CMAKE_SOURCE_DIR}/src) \ No newline at end of file diff --git a/src/graph/graph.cpp b/src/graph/graph.cpp new file mode 100644 index 0000000..54d7065 --- /dev/null +++ b/src/graph/graph.cpp @@ -0,0 +1,132 @@ +#include "./graph/graph.h" + +#include +#include +#include +#include +#include + +Vertex::Vertex(int id_) : id_(id_) {} + +void Vertex::addNeighbor(int neighbor) { + if (neighbor != id_) { + neighbors_.push_back(neighbor); + } +} + +void Vertex::removeNeighbor(int neighbor) { neighbors_.remove(neighbor); } + +void Vertex::print() const { + std::cout << id_ << ": "; + for (const int& neighbor : neighbors_) { + std::cout << neighbor << " "; + } + std::cout << '\n'; +} + +int Vertex::getId() const { return id_; } + +const std::list& Vertex::getNeighbors() const { return neighbors_; } + +Graph::Graph() = default; + +void Graph::addVertex(int id_) { + if (vertices_.find(id_) == vertices_.end()) { + vertices_[id_] = new Vertex(id_); + } +} + +void Graph::addEdge(int u, int v) { + if (vertices_.find(u) == vertices_.end()) { + addVertex(u); + } + if (vertices_.find(v) == vertices_.end()) { + addVertex(v); + } + vertices_[u]->addNeighbor(v); +} + +void Graph::removeEdge(int u, int v) { + if (vertices_.find(u) != vertices_.end()) { + vertices_[u]->removeNeighbor(v); + } +} + +void Graph::removeVertex(int id_) { + for (auto& pair : vertices_) { + pair.second->removeNeighbor(id_); + } + auto it = vertices_.find(id_); + if (it != vertices_.end()) { + delete it->second; + vertices_.erase(it); + } +} + +int Graph::getVertices() const { return static_cast(vertices_.size()); } + +int Graph::getEdges() const { + int count = 0; + for (const auto& vertice : vertices_) { + count += (vertice.second->getNeighbors()).size(); + } + return count; +} + +bool Graph::empty() const { return vertices_.empty(); } + +void Graph::printGraph() const { + for (const auto& pair : vertices_) { + pair.second->print(); + } +} + +bool Graph::bfs_helper(int start, int vert, bool flag, + std::vector* v_ord) { + std::unordered_map visited; + std::queue queue; + queue.push(start); + visited[start] = true; + + while (!queue.empty()) { + int current = queue.front(); + queue.pop(); + + if (flag && current == vert) { + return true; + } + if (v_ord != nullptr) { + v_ord->push_back(current); + } + + if (vertices_.find(current) != vertices_.end()) { + for (const int& neighbor : vertices_[current]->getNeighbors()) { + if (!visited[neighbor]) { + visited[neighbor] = true; + queue.push(neighbor); + } + } + } + } + return false; +} + +bool Graph::hasPath(int u, int v) { + if (vertices_.find(u) == vertices_.end() || + vertices_.find(v) == vertices_.end()) { + return false; + } + return bfs_helper(u, v, true, nullptr); +} + +std::vector Graph::BFS(int start) { + std::vector v_ord; + bfs_helper(start, -1, false, &v_ord); + return v_ord; +} + +Graph::~Graph() { + for (auto& pair : vertices_) { + delete pair.second; + } +} \ No newline at end of file diff --git a/src/tensor/tensor.cpp b/src/tensor/tensor.cpp new file mode 100644 index 0000000..2c57e0b --- /dev/null +++ b/src/tensor/tensor.cpp @@ -0,0 +1,14 @@ +#include "./tensor/tensor.h" + +#include +#include +#include +#include + +Shape::Shape(std::vector dims) : dimensions(std::move(dims)) { + total_elements = std::accumulate(dimensions.begin(), dimensions.end(), + static_cast(1), + [](size_t a, size_t b) { return a * b; }); +} + +size_t Shape::get_rank() const { return dimensions.size(); } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9dada0c..049ad49 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,11 @@ -file(GLOB_RECURSE TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -add_executable(run_tests ${TEST_SRC_FILES}) -target_link_libraries(run_tests PUBLIC - gtest_main -) +file(GLOB_RECURSE TEST_FILES ./*.cpp) + +set(TestsName "run_test") + +add_executable(${TestsName} ${TEST_FILES}) + +target_link_libraries(${TestsName} PRIVATE ${ProjectName} gtest) + +enable_testing() +add_test(NAME ${TestsName} COMMAND ${TestsName}) \ No newline at end of file diff --git a/test/graph/test_graph.cpp b/test/graph/test_graph.cpp new file mode 100644 index 0000000..a382832 --- /dev/null +++ b/test/graph/test_graph.cpp @@ -0,0 +1,145 @@ +#include + +#include "graph/graph.h" +#include "gtest/gtest.h" + +TEST(Graph, can_add_vertex_to_graph) { + Graph g; + + g.addVertex(1); + + ASSERT_EQ(1, g.getVertices()); +} + +TEST(Graph, can_add_edge_to_graph) { + Graph g; + + g.addEdge(1, 0); + + ASSERT_EQ(1, g.getEdges()); +} + +TEST(Graph, cant_add_edge_with_same_id_to_graph) { + Graph g; + + g.addEdge(1, 1); + + ASSERT_EQ(0, g.getEdges()); +} + +TEST(Graph, can_add_vertex_and_edge_to_graph) { + Graph g; + + g.addEdge(1, 0); + g.addVertex(1); + + ASSERT_EQ(1, g.getEdges()); + ASSERT_EQ(1, g.getEdges()); +} + +TEST(Graph, can_remove_vertex) { + Graph g; + g.addEdge(1, 0); + + g.removeVertex(1); + + ASSERT_EQ(g.getVertices(), 1); +} + +TEST(Graph, can_get_vertices_count) { + Graph g; + + g.addEdge(1, 0); + g.addVertex(2); + g.addVertex(3); + g.removeVertex(1); + g.addVertex(1); + g.removeVertex(3); + + ASSERT_EQ(g.getVertices(), 3); +} + +TEST(Graph, can_get_edges_count) { + Graph g; + + g.addEdge(1, 0); + g.addEdge(2, 0); + g.addEdge(0, 2); + g.addEdge(2, 2); + + ASSERT_EQ(g.getEdges(), 3); +} + +TEST(Graph, check_graph_is_empty) { + Graph g; + + ASSERT_TRUE(g.empty()); +} + +TEST(Graph, check_graph_is_not_empty) { + Graph g; + + g.addEdge(1, 0); + + ASSERT_FALSE(g.empty()); +} + +TEST(Graph, check_graph_no_path_between_vertexes) { + Graph g; + + g.addEdge(0, 1); + g.addEdge(0, 3); + g.addEdge(1, 2); + g.addEdge(3, 1); + + ASSERT_FALSE(g.hasPath(1, 0)); +} + +TEST(Graph, check_graph_has_path_between_vertexes) { + Graph g; + + g.addEdge(0, 1); + g.addEdge(0, 3); + g.addEdge(1, 2); + g.addEdge(3, 1); + + ASSERT_TRUE(g.hasPath(3, 1)); +} + +TEST(Graph, check_graph_can_find_path_between_vertexes_after_delete) { + Graph g; + + g.addEdge(0, 1); + g.addEdge(0, 3); + g.addEdge(1, 2); + g.addEdge(3, 1); + g.removeVertex(3); + + ASSERT_FALSE(g.hasPath(3, 1)); + ASSERT_FALSE(g.hasPath(0, 3)); +} + +TEST(Graph, can_create_bfs_path_in_empty_graph) { + Graph g; + std::vector v1 = {0}; + + ASSERT_EQ(g.BFS(0), v1); +} + +TEST(Graph, check_bfs_path) { + Graph g; + std::vector v1 = {0, 1, 3, 2, 4, 6, 7, 5, 8, 9}; + + g.addEdge(0, 1); + g.addEdge(0, 3); + g.addEdge(1, 2); + g.addEdge(3, 4); + g.addEdge(4, 5); + g.addEdge(3, 6); + g.addEdge(3, 7); + g.addEdge(7, 8); + g.addEdge(8, 9); + std::vector v = g.BFS(0); + + ASSERT_EQ(v, v1); +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index 4d820af..9a17845 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,6 +1,6 @@ -#include +#include "gtest/gtest.h" -int main(int argc, char **argv) { +int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/test/tensor/test_tensor.cpp b/test/tensor/test_tensor.cpp new file mode 100644 index 0000000..f21fd86 --- /dev/null +++ b/test/tensor/test_tensor.cpp @@ -0,0 +1,81 @@ +#include + +#include "./tensor/tensor.h" +#include "gtest/gtest.h" + +TEST(ShapeTest, get_rank_and_elem_checks) { + Shape s({2, 3, 4}); + + ASSERT_EQ(s.get_rank(), 3); + ASSERT_EQ(s.total_elements, 24); +} + +TEST(TensorTestDouble, can_at_to_tensor) { + Tensor t({2, 3}, Layout::kNd); + t.at({0, 0}) = 1.0; + t.at({0, 1}) = 2.0; + t.at({0, 2}) = 3.0; + t.at({1, 0}) = 4.0; + t.at({1, 1}) = 5.0; + t.at({1, 2}) = 6.0; + + ASSERT_DOUBLE_EQ(t.at({0, 1}), 2.0); + ASSERT_DOUBLE_EQ(t.at({0, 2}), 4.0); + ASSERT_DOUBLE_EQ(t.at({1, 0}), 4.0); + ASSERT_DOUBLE_EQ(t.at({1, 1}), 5.0); + ASSERT_DOUBLE_EQ(t.at({1, 2}), 6.0); + + const Tensor &ct = t; + + ASSERT_DOUBLE_EQ(ct.at({0, 1}), 2.0); +} + +TEST(TensorTestDouble, can_get_linear_index2D_ND) { + Tensor t({2, 3}, Layout::kNd); + + ASSERT_EQ(t.get_linear_index({0, 0}), 0); + ASSERT_EQ(t.get_linear_index({0, 2}), 2); + ASSERT_EQ(t.get_linear_index({1, 0}), 2); + ASSERT_EQ(t.get_linear_index({1, 2}), 4); +} + +TEST(TensorTestDouble, can_get_linear_index4D_NCHW) { + Tensor t({2, 3, 4, 5}, Layout::kNchw); + + ASSERT_EQ(t.get_linear_index({0, 0, 0, 0}), 0); + ASSERT_EQ(t.get_linear_index({1, 2, 3, 4}), 119); +} + +TEST(TensorTestDouble, can_get_linear_index4D_NHWC) { + Tensor t({2, 3, 4, 5}, Layout::kNhwc); + + ASSERT_EQ(t.get_linear_index({0, 0, 0, 0}), 0); + ASSERT_EQ(t.get_linear_index({1, 2, 3, 4}), 119); +} +TEST(TensorTestDouble, can_get_linear_index4D_ND) { + Tensor t4d_nd({2, 3, 4, 5}, Layout::kNd); + + ASSERT_EQ(t4d_nd.get_linear_index({0, 0, 0, 0}), 0); + ASSERT_EQ(t4d_nd.get_linear_index({1, 2, 3, 4}), 119); +} + +TEST(TensorTestDouble, cant_get_linear_index_out_of_bounds) { + Tensor t2d({2, 3}, Layout::kNd); + Tensor t4d_nchw({2, 3, 4, 5}, Layout::kNchw); + Tensor t4d_nhwc({2, 3, 4, 5}, Layout::kNhwc); + Tensor t4d_nd({2, 3, 4, 5}, Layout::kNd); + + EXPECT_THROW(t2d.get_linear_index({2, 0}), std::out_of_range); + EXPECT_THROW(t2d.get_linear_index({0, 3}), std::out_of_range); + EXPECT_THROW(t4d_nchw.get_linear_index({2, 0, 0, 0}), std::out_of_range); + EXPECT_THROW(t4d_nhwc.get_linear_index({0, 3, 0, 0}), std::out_of_range); + EXPECT_THROW(t4d_nd.get_linear_index({0, 0, 4, 0}), std::out_of_range); +} + +TEST(TensorTestDouble, cant_get_linear_index_with_wrong_num_of_indicies) { + Tensor t2d({2, 3}, Layout::kNd); + Tensor t4d_nchw({2, 3, 4, 5}, Layout::kNchw); + + EXPECT_THROW(t2d.get_linear_index({0}), std::runtime_error); + EXPECT_THROW(t4d_nchw.get_linear_index({0, 0, 0}), std::runtime_error); +} \ No newline at end of file