diff --git a/tasks/agafonov_i_sparse_matrix_ccs/common/include/common.hpp b/tasks/agafonov_i_sparse_matrix_ccs/common/include/common.hpp new file mode 100644 index 0000000000..0f66e32fbe --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/common/include/common.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +struct SparseMatrixCCS { + int m = 0; + int n = 0; + std::vector values; + std::vector row_indices; + std::vector col_ptr; + + SparseMatrixCCS() = default; + SparseMatrixCCS(int m_val, int n_val) : m(m_val), n(n_val), col_ptr(static_cast(n_val) + 1, 0) {} +}; + +struct InType { + SparseMatrixCCS A; + SparseMatrixCCS B; +}; + +using OutType = SparseMatrixCCS; +using BaseTask = ppc::task::Task; + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/info.json b/tasks/agafonov_i_sparse_matrix_ccs/info.json new file mode 100644 index 0000000000..6d57f0823c --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Илья", + "last_name": "Агафонов", + "middle_name": "Дмитриевич", + "group_number": "3823Б1ФИ1", + "task_number": "3" + } +} diff --git a/tasks/agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp b/tasks/agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..67145c7501 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +class SparseMatrixCCSResMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit SparseMatrixCCSResMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void MultiplyColumn(int j, int dims0, const SparseMatrixCCS &at, const SparseMatrixCCS &local_b, + std::vector &dense_col, SparseMatrixCCS &local_c); + + static void BroadcastSparseMatrix(SparseMatrixCCS &m, int rank); + static void DistributeData(const SparseMatrixCCS &b, SparseMatrixCCS &local_b, int size, int rank, + const std::vector &send_counts, const std::vector &displs); + static void GatherResults(SparseMatrixCCS &local_c, int size, int rank, const std::vector &send_counts, + int dims0, int dims3); +}; + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/mpi/src/ops_mpi.cpp b/tasks/agafonov_i_sparse_matrix_ccs/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..d29ec293b5 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/mpi/src/ops_mpi.cpp @@ -0,0 +1,208 @@ +#include "agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" +#include "agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +SparseMatrixCCSResMPI::SparseMatrixCCSResMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool SparseMatrixCCSResMPI::ValidationImpl() { + return GetInput().A.n == GetInput().B.m; +} + +bool SparseMatrixCCSResMPI::PreProcessingImpl() { + return true; +} + +void SparseMatrixCCSResMPI::MultiplyColumn(int j, int dims0, const SparseMatrixCCS &at, const SparseMatrixCCS &local_b, + std::vector &dense_col, SparseMatrixCCS &local_c) { + for (int k_ptr = local_b.col_ptr[static_cast(j)]; k_ptr < local_b.col_ptr[static_cast(j) + 1]; + ++k_ptr) { + int k = local_b.row_indices[static_cast(k_ptr)]; + double v = local_b.values[static_cast(k_ptr)]; + if (k < static_cast(at.col_ptr.size()) - 1) { + for (int i_p = at.col_ptr[static_cast(k)]; i_p < at.col_ptr[static_cast(k) + 1]; ++i_p) { + dense_col[static_cast(at.row_indices[static_cast(i_p)])] += + at.values[static_cast(i_p)] * v; + } + } + } + for (int i = 0; i < dims0; ++i) { + if (std::abs(dense_col[static_cast(i)]) > 1e-15) { + local_c.values.push_back(dense_col[static_cast(i)]); + local_c.row_indices.push_back(i); + dense_col[static_cast(i)] = 0.0; + } + } +} + +void SparseMatrixCCSResMPI::BroadcastSparseMatrix(SparseMatrixCCS &m, int rank) { + int nnz = (rank == 0) ? static_cast(m.values.size()) : 0; + int cols = (rank == 0) ? m.n : 0; + MPI_Bcast(&nnz, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + if (rank != 0) { + m.n = cols; + m.values.resize(static_cast(nnz)); + m.row_indices.resize(static_cast(nnz)); + m.col_ptr.resize(static_cast(m.n) + 1); + } + if (nnz > 0) { + MPI_Bcast(m.values.data(), nnz, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(m.row_indices.data(), nnz, MPI_INT, 0, MPI_COMM_WORLD); + } + MPI_Bcast(m.col_ptr.data(), m.n + 1, MPI_INT, 0, MPI_COMM_WORLD); +} + +void SparseMatrixCCSResMPI::DistributeData(const SparseMatrixCCS &b, SparseMatrixCCS &local_b, int size, int rank, + const std::vector &send_counts, const std::vector &displs) { + if (rank == 0) { + for (int i = 1; i < size; i++) { + int start = displs[static_cast(i)]; + int count = send_counts[static_cast(i)]; + int nnz_s = b.col_ptr[static_cast(start)]; + int nnz_c = b.col_ptr[static_cast(start) + static_cast(count)] - nnz_s; + MPI_Send(&nnz_c, 1, MPI_INT, i, 0, MPI_COMM_WORLD); + if (nnz_c > 0) { + MPI_Send(&b.values[static_cast(nnz_s)], nnz_c, MPI_DOUBLE, i, 1, MPI_COMM_WORLD); + MPI_Send(&b.row_indices[static_cast(nnz_s)], nnz_c, MPI_INT, i, 2, MPI_COMM_WORLD); + } + std::vector adj_ptr(static_cast(count) + 1); + for (int k = 0; k <= count; ++k) { + adj_ptr[static_cast(k)] = b.col_ptr[static_cast(start) + k] - nnz_s; + } + MPI_Send(adj_ptr.data(), count + 1, MPI_INT, i, 3, MPI_COMM_WORLD); + } + int r_nnz = b.col_ptr[static_cast(send_counts[0])]; + local_b.values.assign(b.values.begin(), b.values.begin() + r_nnz); + local_b.row_indices.assign(b.row_indices.begin(), b.row_indices.begin() + r_nnz); + local_b.col_ptr.assign(b.col_ptr.begin(), b.col_ptr.begin() + send_counts[0] + 1); + } else { + int l_nnz = 0; + MPI_Recv(&l_nnz, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + local_b.values.resize(static_cast(l_nnz)); + local_b.row_indices.resize(static_cast(l_nnz)); + local_b.col_ptr.resize(static_cast(send_counts[static_cast(rank)]) + 1); + if (l_nnz > 0) { + MPI_Recv(local_b.values.data(), l_nnz, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(local_b.row_indices.data(), l_nnz, MPI_INT, 0, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + MPI_Recv(local_b.col_ptr.data(), send_counts[static_cast(rank)] + 1, MPI_INT, 0, 3, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + } +} + +void SparseMatrixCCSResMPI::GatherResults(SparseMatrixCCS &local_c, int size, int rank, + const std::vector &send_counts, int dims0, int dims3) { + if (rank == 0) { + SparseMatrixCCS &fc = local_c; + fc.m = dims0; + fc.n = dims3; + for (int i = 1; i < size; ++i) { + int r_nnz = 0; + MPI_Recv(&r_nnz, 1, MPI_INT, i, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + std::vector rv(static_cast(r_nnz)); + std::vector rr(static_cast(r_nnz)); + int r_cols = send_counts[static_cast(i)]; + std::vector rp(static_cast(r_cols) + 1); + if (r_nnz > 0) { + MPI_Recv(rv.data(), r_nnz, MPI_DOUBLE, i, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(rr.data(), r_nnz, MPI_INT, i, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + MPI_Recv(rp.data(), r_cols + 1, MPI_INT, i, 3, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + int current_total_nnz = static_cast(fc.values.size()); + fc.values.insert(fc.values.end(), rv.begin(), rv.end()); + fc.row_indices.insert(fc.row_indices.end(), rr.begin(), rr.end()); + for (int k = 1; k <= r_cols; ++k) { + fc.col_ptr.push_back(rp[static_cast(k)] + current_total_nnz); + } + } + } else { + int l_nnz = static_cast(local_c.values.size()); + MPI_Send(&l_nnz, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); + if (l_nnz > 0) { + MPI_Send(local_c.values.data(), l_nnz, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD); + MPI_Send(local_c.row_indices.data(), l_nnz, MPI_INT, 0, 2, MPI_COMM_WORLD); + } + MPI_Send(local_c.col_ptr.data(), static_cast(local_c.col_ptr.size()), MPI_INT, 0, 3, MPI_COMM_WORLD); + } +} + +bool SparseMatrixCCSResMPI::RunImpl() { + int size = 0; + int rank = 0; + MPI_Comm_size(MPI_COMM_WORLD, &size); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + auto &a = GetInput().A; + auto &b = GetInput().B; + SparseMatrixCCS at; + std::vector dims(4, 0); + + if (rank == 0) { + dims[0] = a.m; + dims[1] = a.n; + dims[2] = b.m; + dims[3] = b.n; + at = SparseMatrixCCSSeq::Transpose(a); + } + MPI_Bcast(dims.data(), 4, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank != 0) { + at.m = dims[1]; + at.n = dims[0]; + b.m = dims[2]; + b.n = dims[3]; + } + + BroadcastSparseMatrix(at, rank); + + int chunk = b.n / size; + int remainder = b.n % size; + std::vector send_counts(static_cast(size)); + std::vector displs(static_cast(size)); + for (int i = 0; i < size; ++i) { + send_counts[static_cast(i)] = chunk + (i < remainder ? 1 : 0); + displs[static_cast(i)] = + (i == 0) ? 0 : displs[static_cast(i) - 1] + send_counts[static_cast(i) - 1]; + } + + SparseMatrixCCS local_b(b.m, send_counts[static_cast(rank)]); + DistributeData(b, local_b, size, rank, send_counts, displs); + + int local_n = send_counts[static_cast(rank)]; + SparseMatrixCCS local_c(dims[0], local_n); + std::vector dense_col(static_cast(dims[0]), 0.0); + for (int j = 0; j < local_n; ++j) { + MultiplyColumn(j, dims[0], at, local_b, dense_col, local_c); + local_c.col_ptr[static_cast(j) + 1] = static_cast(local_c.values.size()); + } + + if (rank == 0) { + GatherResults(local_c, size, rank, send_counts, dims[0], dims[3]); + GetOutput() = local_c; + } else { + GatherResults(local_c, size, rank, send_counts, dims[0], dims[3]); + } + + MPI_Bcast(&GetOutput().m, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&GetOutput().n, 1, MPI_INT, 0, MPI_COMM_WORLD); + + return true; +} + +bool SparseMatrixCCSResMPI::PostProcessingImpl() { + return true; +} + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/report.md b/tasks/agafonov_i_sparse_matrix_ccs/report.md new file mode 100644 index 0000000000..4ae0185009 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/report.md @@ -0,0 +1,81 @@ +# Умножение разреженных матриц. Элементы типа double. Формат хранения матрицы – столбцовый (CCS). + +- Student: Агафонов Илья Дмитриевич, group 3823Б1ФИ1 +- Technology: SEQ | MPI +- Variant: 5 + +## 1. Introduction +Задача умножения разреженных матриц является критически важной в научных вычислениях и машинном обучении. Использование формата **CCS (Compressed Column Storage)** позволяет эффективно работать с матрицами, где большинство элементов равны нулю, экономя память и время процессора за счет обработки только ненулевых значений. + +## 2. Problem Statement +Необходимо реализовать умножение двух разреженных матриц $A$ и $B$ в формате CCS для получения результирующей матрицы $C = A \times B$. + +**Входные данные:** +- Матрицы $A (m \times k)$ и $B (k \times n)$ в формате CCS. +- Формат включает три вектора: `values` (ненулевые значения), `row_indices` (индексы строк), `col_ptr` (индексы начала столбцов). + +**Выходные данные:** +- Результирующая матрица $C (m \times n)$ в формате CCS. + +**Ограничения:** +- Тип данных — `double`. +- Число столбцов $A$ равно числу строк $B$ ($A.n == B.m$). + +## 3. Baseline Algorithm (Sequential) +Последовательный алгоритм использует "столбцовую" логику: +1. Матрица $A$ предварительно транспонируется ($A^T$) для обеспечения быстрого доступа к строкам исходной матрицы. +2. Для каждого столбца $j$ матрицы $B$: + - Инициализируется плотный вектор-аккумулятор размера $m$. + - Проход по ненулевым элементам $B_{kj}$. + - Для каждого $B_{kj}$ выбирается соответствующий столбец $k$ из $A^T$ (бывшая строка $A$) и выполняется обновление аккумулятора: $C_{ij} += A_{ik} \times B_{kj}$. + - Ненулевые значения из аккумулятора упаковываются обратно в формат CCS. + +## 4. Parallelization Scheme +Для распараллеливания используется распределение столбцов выходной матрицы между MPI-процессами: + +- **Data Distribution**: + - Процесс 0 транспонирует матрицу $A$ и рассылает её всем узлам через `MPI_Bcast`. + - Столбцы матрицы $B$ распределяются между процессами (декомпозиция по столбцам). +- **Rank Roles**: + - **Rank 0**: Подготовка данных, распределение блоков матрицы $B$, вычисление своей части и сборка финальной матрицы $C$ через `GatherResults`. + - **Workers**: Получение своей порции столбцов $B$, вычисление локальных столбцов $C$ и отправка их мастеру. +- **Communication Pattern**: + - Групповые рассылки (`MPI_Bcast`) для передачи матрицы $A$. + - Точечные обмены (`MPI_Send`/`MPI_Recv`) для распределения работы и сбора результатов. + +## 5. Implementation Details +- **Код разбит на функциональные блоки**: + - `SparseMatrixCCS`: Основная структура данных. + - `BroadcastSparseMatrix`: Кастомная функция для передачи разреженной структуры через MPI. + - `GatherResults`: Логика объединения локальных CCS-структур в одну глобальную с пересчетом индексов. +## 6. Experimental Setup +**Hardware/OS:** + - **Процессор:** Процессор AMD Ryzen 5 5500U, ядер: 6, логических процессоров: 12 + - **Оперативная память:** 16 ГБ DDR4 + - **Операционная система:** Windows 10 Pro 22H2 +- **Toolchain:** + - **Компилятор:** g++ 13.3.0 + - **Тип сборки:** Release (-O3 ) + - **MPI:** Open MPI 4.1.6 + +## 7. Results and Discussion + +### 7.1 Correctness +Корректность подтверждена успешным прохождением тестов `agafonov_i_sparse_matrix_ccs_func`, где результаты параллельной версии сравнивались с последовательным эталоном. Все тесты `PASSED`. + +### 7.2 Performance +На основе замеров в режиме `Release` на 4 процессах: + +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|---------|---------|------------| +| seq | 1 | 0.0195 | 1.00 | 100 | +| mpi | 4 | 0.2072 | 0.52 | 11.2% | + +*\* Примечание: На матрицах время пересылки данных (коммуникационная составляющая) превышает время вычислений. Для получения ускорения требуются матрицы размерностью от 3000x3000 и выше.* + +## 8. Conclusions +Алгоритм успешно реализован и проходит все тесты на корректность. Реализация MPI-версии показала работоспособность механизмов распределения и сборки разреженных данных. Для повышения эффективности на малых данных рекомендуется использовать гибридный подход (MPI+OpenMP) или увеличивать вычислительную нагрузку на каждый процесс. + +## 9. References +1. Материлы и документация по курсу +2. MPI Standard Specification: [https://www.mpi-forum.org/]. diff --git a/tasks/agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp b/tasks/agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..754c604b25 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +class SparseMatrixCCSSeq : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit SparseMatrixCCSSeq(const InType &in); + static SparseMatrixCCS Transpose(const SparseMatrixCCS &matrix); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/seq/src/ops_seq.cpp b/tasks/agafonov_i_sparse_matrix_ccs/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..14dadc33d6 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/seq/src/ops_seq.cpp @@ -0,0 +1,96 @@ +#include "agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +SparseMatrixCCSSeq::SparseMatrixCCSSeq(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool SparseMatrixCCSSeq::ValidationImpl() { + return GetInput().A.n == GetInput().B.m; +} + +bool SparseMatrixCCSSeq::PreProcessingImpl() { + return true; +} + +SparseMatrixCCS SparseMatrixCCSSeq::Transpose(const SparseMatrixCCS &matrix) { + int target_cols = matrix.m; + int target_rows = matrix.n; + SparseMatrixCCS at(target_rows, target_cols); + + at.col_ptr.assign(static_cast(target_cols) + 1, 0); + for (int row_indice : matrix.row_indices) { + at.col_ptr[static_cast(row_indice) + 1]++; + } + + for (int i = 0; i < target_cols; ++i) { + at.col_ptr[static_cast(i) + 1] += at.col_ptr[static_cast(i)]; + } + + at.row_indices.resize(matrix.values.size()); + at.values.resize(matrix.values.size()); + std::vector current_pos = at.col_ptr; + + for (int col = 0; col < matrix.n; ++col) { + for (int j = matrix.col_ptr[static_cast(col)]; j < matrix.col_ptr[static_cast(col) + 1]; + ++j) { + int row = matrix.row_indices[static_cast(j)]; + int dest_pos = current_pos[static_cast(row)]++; + at.row_indices[static_cast(dest_pos)] = col; + at.values[static_cast(dest_pos)] = matrix.values[static_cast(j)]; + } + } + return at; +} + +bool SparseMatrixCCSSeq::RunImpl() { + auto &a = GetInput().A; + auto &b = GetInput().B; + SparseMatrixCCS at = Transpose(a); + + auto &c = GetOutput(); + c.m = a.m; + c.n = b.n; + c.col_ptr.assign(static_cast(c.n) + 1, 0); + c.values.clear(); + c.row_indices.clear(); + + std::vector dense_col(static_cast(a.m), 0.0); + for (int col_b = 0; col_b < b.n; ++col_b) { + for (int k_ptr = b.col_ptr[static_cast(col_b)]; k_ptr < b.col_ptr[static_cast(col_b) + 1]; + ++k_ptr) { + int k = b.row_indices[static_cast(k_ptr)]; + double val_b = b.values[static_cast(k_ptr)]; + if (k < static_cast(at.col_ptr.size()) - 1) { + for (int i_ptr = at.col_ptr[static_cast(k)]; i_ptr < at.col_ptr[static_cast(k) + 1]; + ++i_ptr) { + dense_col[static_cast(at.row_indices[static_cast(i_ptr)])] += + at.values[static_cast(i_ptr)] * val_b; + } + } + } + for (int i = 0; i < a.m; ++i) { + if (std::abs(dense_col[static_cast(i)]) > 1e-15) { + c.values.push_back(dense_col[static_cast(i)]); + c.row_indices.push_back(i); + dense_col[static_cast(i)] = 0.0; + } + } + c.col_ptr[static_cast(col_b) + 1] = static_cast(c.values.size()); + } + return true; +} + +bool SparseMatrixCCSSeq::PostProcessingImpl() { + return true; +} + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/settings.json b/tasks/agafonov_i_sparse_matrix_ccs/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/agafonov_i_sparse_matrix_ccs/tests/.clang-tidy b/tasks/agafonov_i_sparse_matrix_ccs/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/agafonov_i_sparse_matrix_ccs/tests/functional/main.cpp b/tasks/agafonov_i_sparse_matrix_ccs/tests/functional/main.cpp new file mode 100644 index 0000000000..220e691de6 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/tests/functional/main.cpp @@ -0,0 +1,120 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" +#include "agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp" +#include "agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +using TestParams = std::tuple; + +static SparseMatrixCCS CreateRandomSparseMatrix(int m, int n, double density) { + SparseMatrixCCS matrix(m, n); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, 1.0); + std::uniform_real_distribution<> val_dis(-100.0, 100.0); + + for (int j = 0; j < n; ++j) { + for (int i = 0; i < m; ++i) { + if (dis(gen) < density) { + matrix.values.push_back(val_dis(gen)); + matrix.row_indices.push_back(i); + } + } + matrix.col_ptr[static_cast(j) + 1] = static_cast(matrix.values.size()); + } + return matrix; +} + +class SparseMatrixFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const testing::TestParamInfo &info) { + auto params = std::get(ppc::util::GTestParamIndex::kTestParams)>(info.param); + std::string test_name = std::get<4>(params); + return test_name + "_" + std::to_string(info.index); + } + + protected: + void SetUp() override { + auto params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + int m = std::get<0>(params); + int k = std::get<1>(params); + int n = std::get<2>(params); + double density = std::get<3>(params); + + input_data_.A = CreateRandomSparseMatrix(m, k, density); + input_data_.B = CreateRandomSparseMatrix(k, n, density); + + SparseMatrixCCSSeq task_seq(input_data_); + task_seq.Validation(); + task_seq.PreProcessing(); + task_seq.Run(); + task_seq.PostProcessing(); + expected_output_ = task_seq.GetOutput(); + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank != 0) { + return true; + } + + if (output_data.values.size() != expected_output_.values.size()) { + return false; + } + if (output_data.col_ptr != expected_output_.col_ptr) { + return false; + } + if (output_data.row_indices != expected_output_.row_indices) { + return false; + } + + for (std::size_t i = 0; i < output_data.values.size(); ++i) { + if (std::abs(output_data.values[i] - expected_output_.values[i]) > 1e-6) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_output_; +}; + +TEST_P(SparseMatrixFuncTests, MatmulTests) { + ExecuteTest(GetParam()); +} + +namespace { +const std::array kFuncTestParams = { + std::make_tuple(10, 10, 10, 0.1, "Square_Small"), std::make_tuple(32, 32, 32, 0.2, "Square_Mid"), + std::make_tuple(15, 7, 20, 0.15, "Rectangular_Diverse"), std::make_tuple(1, 50, 1, 0.3, "Inner_Product_Vector"), + std::make_tuple(50, 1, 50, 0.8, "Outer_Product_Dense"), std::make_tuple(20, 20, 20, 0.0, "Empty_Matrix")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kFuncTestParams, PPC_SETTINGS_agafonov_i_sparse_matrix_ccs), + ppc::util::AddFuncTask(kFuncTestParams, PPC_SETTINGS_agafonov_i_sparse_matrix_ccs)); + +INSTANTIATE_TEST_SUITE_P(SparseMatrixTests, SparseMatrixFuncTests, ppc::util::ExpandToValues(kTestTasksList), + SparseMatrixFuncTests::PrintTestParam); +} // namespace + +} // namespace agafonov_i_sparse_matrix_ccs diff --git a/tasks/agafonov_i_sparse_matrix_ccs/tests/performance/main.cpp b/tasks/agafonov_i_sparse_matrix_ccs/tests/performance/main.cpp new file mode 100644 index 0000000000..8140f895e4 --- /dev/null +++ b/tasks/agafonov_i_sparse_matrix_ccs/tests/performance/main.cpp @@ -0,0 +1,73 @@ +#include + +#include +#include +#include +#include +#include + +#include "agafonov_i_sparse_matrix_ccs/common/include/common.hpp" +#include "agafonov_i_sparse_matrix_ccs/mpi/include/ops_mpi.hpp" +#include "agafonov_i_sparse_matrix_ccs/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace agafonov_i_sparse_matrix_ccs { + +static SparseMatrixCCS CreatePerfMatrix(int m, int n, double density) { + SparseMatrixCCS matrix(m, n); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, 1.0); + std::uniform_real_distribution<> val_dis(-100.0, 100.0); + for (int j = 0; j < n; ++j) { + for (int i = 0; i < m; ++i) { + if (dis(gen) < density) { + matrix.values.push_back(val_dis(gen)); + matrix.row_indices.push_back(i); + } + } + matrix.col_ptr[static_cast(j) + 1] = static_cast(matrix.values.size()); + } + return matrix; +} + +class SparseMatrixPerfTests : public ppc::util::BaseRunPerfTests { + protected: + const int k_size = 4000; + void SetUp() override { + const int m = k_size; + const int k = k_size; + const int n = k_size; + const double density = 0.01; + + input_data_.A = CreatePerfMatrix(m, k, density); + input_data_.B = CreatePerfMatrix(k, n, density); + } + + bool CheckTestOutputData(OutType &output_data) final { + return output_data.m == k_size && output_data.n == k_size; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(SparseMatrixPerfTests, RunPerfModes) { + const auto ¶ms = GetParam(); + ExecuteTest(params); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_agafonov_i_sparse_matrix_ccs); + +INSTANTIATE_TEST_SUITE_P(RunModeTests, SparseMatrixPerfTests, ppc::util::TupleToGTestValues(kAllPerfTasks), + [](const testing::TestParamInfo &info) { + const std::string &name = std::get<1>(info.param); + return name + "_" + std::to_string(info.index); + }); + +} // namespace agafonov_i_sparse_matrix_ccs