diff --git a/tasks/kaur_a_multy_matrix/common/include/common.hpp b/tasks/kaur_a_multy_matrix/common/include/common.hpp new file mode 100644 index 00000000..f683d67d --- /dev/null +++ b/tasks/kaur_a_multy_matrix/common/include/common.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace kaur_a_multy_matrix { + +struct SparseMatrixCCS { + std::vector values; + std::vector row_indices; + std::vector col_ptrs; + int rows = 0; + int cols = 0; + int nnz = 0; +}; + +using InType = std::pair; +using OutType = SparseMatrixCCS; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +SparseMatrixCCS GenerateRandomSparseMatrix(int rows, int cols, double density); +bool CompareMatrices(const SparseMatrixCCS &a, const SparseMatrixCCS &b, double k_epsilon = 1e-6); +void TransposeMatrixCCS(const SparseMatrixCCS &a, SparseMatrixCCS &at); + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/data/pic.ppm b/tasks/kaur_a_multy_matrix/data/pic.ppm new file mode 100644 index 00000000..63762423 Binary files /dev/null and b/tasks/kaur_a_multy_matrix/data/pic.ppm differ diff --git a/tasks/kaur_a_multy_matrix/info.json b/tasks/kaur_a_multy_matrix/info.json new file mode 100644 index 00000000..62693023 --- /dev/null +++ b/tasks/kaur_a_multy_matrix/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Александр", + "group_number": "3823Б1ПР4", + "last_name": "Каур", + "middle_name": "Максимович", + "task_number": "3" + } +} diff --git a/tasks/kaur_a_multy_matrix/mpi/include/ops_mpi.hpp b/tasks/kaur_a_multy_matrix/mpi/include/ops_mpi.hpp new file mode 100644 index 00000000..f442834f --- /dev/null +++ b/tasks/kaur_a_multy_matrix/mpi/include/ops_mpi.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include "kaur_a_multy_matrix/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kaur_a_multy_matrix { + +class KaurAMultyMatrixMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit KaurAMultyMatrixMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void TransposeMatrixMPI(const SparseMatrixCCS &a, SparseMatrixCCS &at); + static std::pair SplitColumns(int total_cols, int rank, int size); + static void ExtractLocalColumns(const SparseMatrixCCS &b, int start_col, int end_col, std::vector &loc_val, + std::vector &loc_row_ind, std::vector &loc_col_ptr); + static void MultiplyLocalMatrices(const SparseMatrixCCS &at, const std::vector &loc_val, + const std::vector &loc_row_ind, const std::vector &loc_col_ptr, + int loc_cols, std::vector &res_val, std::vector &res_row_ind, + std::vector &res_col_ptr); + bool ProcessRootRank(const SparseMatrixCCS &a, const SparseMatrixCCS &b, std::vector &loc_res_val, + std::vector &loc_res_row_ind, std::vector &loc_res_col_ptr, int size); + static bool ProcessWorkerRank(const std::vector &loc_res_val, const std::vector &loc_res_row_ind, + const std::vector &loc_res_col_ptr, int loc_cols); + static void ProcessLocalColumn(const SparseMatrixCCS &at, const std::vector &loc_val, + const std::vector &loc_row_ind, const std::vector &loc_col_ptr, + int col_index, std::vector &temp_row, std::vector &row_marker, + std::vector &res_val, std::vector &res_row_ind); +}; + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/mpi/src/ops_mpi.cpp b/tasks/kaur_a_multy_matrix/mpi/src/ops_mpi.cpp new file mode 100644 index 00000000..90c32b53 --- /dev/null +++ b/tasks/kaur_a_multy_matrix/mpi/src/ops_mpi.cpp @@ -0,0 +1,305 @@ +#include "kaur_a_multy_matrix/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "kaur_a_multy_matrix/common/include/common.hpp" + +namespace kaur_a_multy_matrix { + +namespace { +constexpr double kEpsilon = 1e-10; +} // namespace + +KaurAMultyMatrixMPI::KaurAMultyMatrixMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = SparseMatrixCCS{}; +} + +bool KaurAMultyMatrixMPI::ValidationImpl() { + const auto &[a, b] = GetInput(); + return (a.rows > 0 && a.cols > 0 && b.rows > 0 && b.cols > 0 && a.cols == b.rows); +} + +bool KaurAMultyMatrixMPI::PreProcessingImpl() { + return true; +} + +void KaurAMultyMatrixMPI::TransposeMatrixMPI(const SparseMatrixCCS &a, SparseMatrixCCS &at) { + at.rows = a.cols; + at.cols = a.rows; + at.nnz = a.nnz; + + if (a.nnz == 0) { + at.values.clear(); + at.row_indices.clear(); + at.col_ptrs.assign(at.cols + 1, 0); + return; + } + + std::vector row_count(at.cols, 0); + for (int i = 0; i < a.nnz; i++) { + row_count[a.row_indices[i]]++; + } + + at.col_ptrs.resize(at.cols + 1); + at.col_ptrs[0] = 0; + for (int i = 0; i < at.cols; i++) { + at.col_ptrs[i + 1] = at.col_ptrs[i] + row_count[i]; + } + + at.values.resize(a.nnz); + at.row_indices.resize(a.nnz); + + std::vector current_pos(at.cols, 0); + for (int col = 0; col < a.cols; col++) { + for (int i = a.col_ptrs[col]; i < a.col_ptrs[col + 1]; i++) { + int row = a.row_indices[i]; + double val = a.values[i]; + + int pos = at.col_ptrs[row] + current_pos[row]; + at.values[pos] = val; + at.row_indices[pos] = col; + current_pos[row]++; + } + } +} + +std::pair KaurAMultyMatrixMPI::SplitColumns(int total_cols, int rank, int size) { + int base_cols = total_cols / size; + int remainder = total_cols % size; + + int start_col = (rank * base_cols) + std::min(rank, remainder); + int end_col = start_col + base_cols + (rank < remainder ? 1 : 0); + + return {start_col, end_col}; +} + +void KaurAMultyMatrixMPI::ProcessLocalColumn(const SparseMatrixCCS &at, const std::vector &loc_val, + const std::vector &loc_row_ind, const std::vector &loc_col_ptr, + int col_index, std::vector &temp_row, std::vector &row_marker, + std::vector &res_val, std::vector &res_row_ind) { + int col_start = loc_col_ptr[col_index]; + int col_end = loc_col_ptr[col_index + 1]; + + for (int k = col_start; k < col_end; k++) { + int row_b = loc_row_ind[k]; + double val_b = loc_val[k]; + + for (int idx = at.col_ptrs[row_b]; idx < at.col_ptrs[row_b + 1]; idx++) { + int row_a = at.row_indices[idx]; + double val_a = at.values[idx]; + + if (row_marker[row_a] != col_index) { + row_marker[row_a] = col_index; + temp_row[row_a] = val_a * val_b; + } else { + temp_row[row_a] += val_a * val_b; + } + } + } + + for (int i = 0; i < at.cols; i++) { + if (row_marker[i] == col_index && std::abs(temp_row[i]) > kEpsilon) { + res_val.push_back(temp_row[i]); + res_row_ind.push_back(i); + } + } +} + +void KaurAMultyMatrixMPI::ExtractLocalColumns(const SparseMatrixCCS &b, int start_col, int end_col, + std::vector &loc_val, std::vector &loc_row_ind, + std::vector &loc_col_ptr) { + loc_val.clear(); + loc_row_ind.clear(); + loc_col_ptr.clear(); + + loc_col_ptr.push_back(0); + + for (int col = start_col; col < end_col; col++) { + int start_index = b.col_ptrs[col]; + int end_index = b.col_ptrs[col + 1]; + + for (int i = start_index; i < end_index; i++) { + loc_val.push_back(b.values[i]); + loc_row_ind.push_back(b.row_indices[i]); + } + + loc_col_ptr.push_back(static_cast(loc_val.size())); + } +} + +void KaurAMultyMatrixMPI::MultiplyLocalMatrices(const SparseMatrixCCS &at, const std::vector &loc_val, + const std::vector &loc_row_ind, + const std::vector &loc_col_ptr, int loc_cols, + std::vector &res_val, std::vector &res_row_ind, + std::vector &res_col_ptr) { + res_val.clear(); + res_row_ind.clear(); + res_col_ptr.clear(); + res_col_ptr.push_back(0); + + std::vector temp_row(at.cols, 0.0); + std::vector row_marker(at.cols, -1); + + for (int j = 0; j < loc_cols; j++) { + ProcessLocalColumn(at, loc_val, loc_row_ind, loc_col_ptr, j, temp_row, row_marker, res_val, res_row_ind); + res_col_ptr.push_back(static_cast(res_val.size())); + } +} + +bool KaurAMultyMatrixMPI::ProcessRootRank(const SparseMatrixCCS &a, const SparseMatrixCCS &b, + std::vector &loc_res_val, std::vector &loc_res_row_ind, + std::vector &loc_res_col_ptr, int size) { + SparseMatrixCCS c; + c.rows = a.rows; + c.cols = b.cols; + + std::vector> all_values(size); + std::vector> all_row_indices(size); + std::vector> all_col_ptrs(size); + + all_values[0] = std::move(loc_res_val); + all_row_indices[0] = std::move(loc_res_row_ind); + all_col_ptrs[0] = std::move(loc_res_col_ptr); + + for (int src = 1; src < size; src++) { + int src_nnz = 0; + int src_cols = 0; + MPI_Recv(&src_nnz, 1, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(&src_cols, 1, MPI_INT, src, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + std::vector src_vals(src_nnz); + std::vector src_rows(src_nnz); + std::vector src_ptrs(src_cols + 1); + + MPI_Recv(src_vals.data(), src_nnz, MPI_DOUBLE, src, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(src_rows.data(), src_nnz, MPI_INT, src, 3, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(src_ptrs.data(), src_cols + 1, MPI_INT, src, 4, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + all_values[src] = std::move(src_vals); + all_row_indices[src] = std::move(src_rows); + all_col_ptrs[src] = std::move(src_ptrs); + } + + c.col_ptrs.push_back(0); + + std::vector value_offsets(size, 0); + std::vector col_offsets(size, 0); + + for (int i = 0; i < size; i++) { + if (i > 0) { + value_offsets[i] = value_offsets[i - 1] + static_cast(all_values[i - 1].size()); + col_offsets[i] = col_offsets[i - 1] + static_cast(all_col_ptrs[i - 1].size() - 1); + } + } + + for (int i = 0; i < size; i++) { + c.values.insert(c.values.end(), all_values[i].begin(), all_values[i].end()); + c.row_indices.insert(c.row_indices.end(), all_row_indices[i].begin(), all_row_indices[i].end()); + + for (size_t j = 1; j < all_col_ptrs[i].size(); j++) { + c.col_ptrs.push_back(all_col_ptrs[i][j] + value_offsets[i]); + } + } + + c.nnz = static_cast(c.values.size()); + GetOutput() = c; + + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool KaurAMultyMatrixMPI::ProcessWorkerRank(const std::vector &loc_res_val, + const std::vector &loc_res_row_ind, + const std::vector &loc_res_col_ptr, int loc_cols) { + int local_nnz = static_cast(loc_res_val.size()); + int local_cols = loc_cols; + + MPI_Send(&local_nnz, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); + MPI_Send(&local_cols, 1, MPI_INT, 0, 1, MPI_COMM_WORLD); + MPI_Send(loc_res_val.data(), local_nnz, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD); + MPI_Send(loc_res_row_ind.data(), local_nnz, MPI_INT, 0, 3, MPI_COMM_WORLD); + MPI_Send(loc_res_col_ptr.data(), loc_cols + 1, MPI_INT, 0, 4, MPI_COMM_WORLD); + + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool KaurAMultyMatrixMPI::RunImpl() { + const auto &[a, b] = GetInput(); + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + SparseMatrixCCS at; + if (rank == 0) { + TransposeMatrixMPI(a, at); + } else { + at.rows = a.cols; + at.cols = a.rows; + } + + MPI_Bcast(&at.rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&at.cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank == 0) { + at.nnz = static_cast(at.values.size()); + } + MPI_Bcast(&at.nnz, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank != 0) { + at.values.resize(at.nnz); + at.row_indices.resize(at.nnz); + at.col_ptrs.resize(at.cols + 1); + } + + MPI_Bcast(at.values.data(), at.nnz, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(at.row_indices.data(), at.nnz, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(at.col_ptrs.data(), at.cols + 1, MPI_INT, 0, MPI_COMM_WORLD); + + auto [start_col, end_col] = SplitColumns(b.cols, rank, size); + int loc_cols = end_col - start_col; + + std::vector loc_b_val; + std::vector loc_b_row_ind; + std::vector loc_b_col_ptr; + + ExtractLocalColumns(b, start_col, end_col, loc_b_val, loc_b_row_ind, loc_b_col_ptr); + + std::vector loc_res_val; + std::vector loc_res_row_ind; + std::vector loc_res_col_ptr; + + MultiplyLocalMatrices(at, loc_b_val, loc_b_row_ind, loc_b_col_ptr, loc_cols, loc_res_val, loc_res_row_ind, + loc_res_col_ptr); + + if (rank == 0) { + return ProcessRootRank(a, b, loc_res_val, loc_res_row_ind, loc_res_col_ptr, size); + } + + return ProcessWorkerRank(loc_res_val, loc_res_row_ind, loc_res_col_ptr, loc_cols); +} + +bool KaurAMultyMatrixMPI::PostProcessingImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + const auto &c = GetOutput(); + + if (rank == 0) { + return c.rows > 0 && c.cols > 0 && c.col_ptrs.size() == static_cast(c.cols) + 1; + } + + return c.rows == 0 && c.cols == 0; +} + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/report.md b/tasks/kaur_a_multy_matrix/report.md new file mode 100644 index 00000000..b612f5ed --- /dev/null +++ b/tasks/kaur_a_multy_matrix/report.md @@ -0,0 +1,145 @@ +# Умножение разреженных матриц в формате CCS с использованием MPI +Студент: Каур Андрей Михайлович, группа __________ +Технология: SEQ | MPI +Вариант: __ + +## 1. Введение +Операция умножения разреженных матриц является фундаментальной в вычислительной математике и находит применение в научных расчётах, машинном обучении и обработке графов. Использование формата CCS (Column Compressed Storage) позволяет эффективно хранить и обрабатывать матрицы с большим количеством нулевых элементов. Цель данной работы — реализация параллельного алгоритма умножения таких матриц с применением MPI для распределения вычислений между несколькими процессами, что должно обеспечить существенное ускорение по сравнению с последовательной версией. + +## 2. Постановка задачи +Формальная задача: Умножение двух разреженных матриц A (размер m × k) и B (размер k × n), представленных в формате CCS. Результат — матрица C = A × B того же формата. + +Входные данные: Пара матриц в формате CCS (три массива: values, row_indices, col_ptrs, а также размеры rows, cols, nnz). +Выходные данные: Результирующая матрица C в формате CCS. +Ограничения: Количество столбцов матрицы A должно совпадать с количеством строк матрицы B (k). Элементы матриц имеют тип double. + +## 3. Последовательный алгоритм +Базовый последовательный алгоритм состоит из двух основных шагов: +1) Транспонирование матрицы A: Для эффективного доступа к строкам A при умножении матрица транспонируется. Алгоритм транспонирования: +- Подсчитывается количество ненулевых элементов в каждой строке A (после транспонирования — в каждом столбце Aᵀ). +- На основе этих подсчётов строится массив указателей col_ptrs для Aᵀ. +- Элементы перераспределяются в новые массивы values и row_indices с сохранением порядка по столбцам. + +2) Умножение Aᵀ на B: +- Для каждого столбца j матрицы B создаётся временный массив-накопитель temp_row размера m (число строк A), инициализированный нулями, и массив-маркер row_marker. +- Для каждого ненулевого элемента b[row_b][j] находится соответствующая строка row_b в Aᵀ. +- Каждый элемент этой строки Aᵀ умножается на b[row_b][j], и результат добавляется в temp_row на позиции, соответствующей столбцу исходной матрицы A. +- После обработки всех ненулевых элементов столбца B, из temp_row в результирующую матрицу C переносятся только значения, превышающие порог kEpsilon (1e-10). + +## 4. Схема распараллеливания (MPI) +Распределение данных: Столбцы матрицы B распределяются между процессами MPI. Функция SplitColumns делит общее число столбцов B (n) на примерно равные части с учётом остатка. Каждый процесс получает свой непрерывный диапазон столбцов. + +Роли процессов: +- Ранг 0 (координатор): Выполняет транспонирование матрицы A. Затем с помощью MPI_Bcast рассылает полученную транспонированную матрицу Aᵀ (её размеры и все три массива) всем остальным процессам. После этого выполняет локальное умножение для своего набора столбцов. В завершении собирает частичные результаты от всех процессов и объединяет их в итоговую матрицу C. + +- Рабочие ранги (1..size-1): Получают матрицу Aᵀ, извлекают свои локальные столбцы из B с помощью ExtractLocalColumns и выполняют умножение. Отправляют свои результаты (массивы values, row_indices и col_ptrs для своей части столбцов) рангу 0 с использованием MPI_Send. + +Коммуникационная модель: Используется однородная модель с ранговой рассылкой (broadcast) для Aᵀ и точечной коммуникацией (send/receive) для сбора результатов. Синхронизация в конце этапа выполняется с помощью MPI_Barrier. + +## 5. Детали реализации +Структура кода: Проект разделён на логические модули. Общая структура данных SparseMatrixCCS и утилиты объявлены в common.hpp. Последовательная реализация алгоритма находится в ops_seq.hpp/cpp. MPI-версия, содержащая всю логику распараллеливания и коммуникации, реализована в ops_mpi.hpp/cpp. + +Ключевые функции: +- TransposeMatrixMPI – транспонирование с предварительным подсчётом. +- ExtractLocalColumns – извлечение подмассива столбцов из матрицы B. +- MultiplyLocalMatrices и ProcessLocalColumn – ядро вычислений. +- ProcessRootRank и ProcessWorkerRank – управление логикой процессов. + +Важные аспекты: +- Для экономии памяти и избежания лишних копий при сборе результатов используется семантика перемещения (std::move). +- Временные массивы temp_row и row_marker переиспользуются для каждого столбца. +- Элементы результата, по модулю меньшие kEpsilon, отбрасываются, что контролирует рост заполненности из-за ошибок округления. + +## 6. Экспериментальная установка +### Аппаратное обеспечение: +- Процессор: Intel(R) Core(TM) Ultra 9 285H (2.90 GHz) +- Оперативная память: 32 GB +- ОС: Windows 11 + +### Инструментарий: +- Компилятор: clang version 21.1.6 +- Версия MPI: MS-MPI v10.1.3 +- Тип сборки: Release + +## 7. Результаты и обсуждение +7.1 Корректность +Корректность параллельной реализации проверялась сравнением её результатов с результатами последовательного алгоритма для широкого набора сгенерированных матриц. Дополнительно проверялись структурные инварианты результирующей матрицы C: соответствие размеров (C.rows == A.rows, C.cols == B.cols), корректность размера массива col_ptrs (равен cols + 1) и монотонность его значений. Все функциональные тесты были пройдены успешно. + +7.2 Производительность +Измерения проводились для матриц размером 2000x2000. Результаты представлены в таблице: + +| Mode | Count | Time, s | Speedup | Efficiency | +|-------------|-------|---------|---------|------------| +| seq | 1 | 1.71 | 1.00 | N/A | +| mpi | 2 | 1.26 | 1.35 | 67.5% | +| mpi | 4 | 1.07 | 1.60 | 40.0% | +| mpi | 8 | 1.21 | 1.41 | 17.6% | + +Анализ: +Алгоритм демонстрирует умеренное ускорение при увеличении числа процессов до 4. Дальнейший рост количества процессов приводит к снижению эффективности из-за увеличения накладных расходов на коммуникацию (рассылка Aᵀ и сбор результатов) и возможного дисбаланса нагрузки при неравномерном распределении ненулевых элементов по столбцам B. + +## 8. Выводы +В ходе работы был успешно реализован и протестирован параллельный алгоритм умножения разреженных матриц в формате CCS с использованием MPI. Основным ограничением масштабируемости являются коммуникационные издержки. Для дальнейшего улучшения производительности можно рассмотреть более сбалансированную схему распределения столбцов (не по количеству, а по числу ненулевых элементов) и оптимизацию этапа сбора данных через использование коллективных операций MPI, таких как Gatherv. + +## 9. Ссылки +1. Курс лекций по параллельному программированию Сысоева Александра Владимировича. +2. Документация по курсу: https://learning-process.github.io/parallel_programming_course/ru + +## Приложение +```cpp +bool KaurAMultyMatrixMPI::RunImpl() { + const auto &[a, b] = GetInput(); + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + SparseMatrixCCS at; + if (rank == 0) { + TransposeMatrixMPI(a, at); + } else { + at.rows = a.cols; + at.cols = a.rows; + } + + MPI_Bcast(&at.rows, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&at.cols, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank == 0) { + at.nnz = static_cast(at.values.size()); + } + MPI_Bcast(&at.nnz, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank != 0) { + at.values.resize(at.nnz); + at.row_indices.resize(at.nnz); + at.col_ptrs.resize(at.cols + 1); + } + + MPI_Bcast(at.values.data(), at.nnz, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(at.row_indices.data(), at.nnz, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(at.col_ptrs.data(), at.cols + 1, MPI_INT, 0, MPI_COMM_WORLD); + + auto [start_col, end_col] = SplitColumns(b.cols, rank, size); + int loc_cols = end_col - start_col; + + std::vector loc_b_val; + std::vector loc_b_row_ind; + std::vector loc_b_col_ptr; + + ExtractLocalColumns(b, start_col, end_col, loc_b_val, loc_b_row_ind, loc_b_col_ptr); + + std::vector loc_res_val; + std::vector loc_res_row_ind; + std::vector loc_res_col_ptr; + + MultiplyLocalMatrices(at, loc_b_val, loc_b_row_ind, loc_b_col_ptr, loc_cols, loc_res_val, loc_res_row_ind, + loc_res_col_ptr); + + if (rank == 0) { + return ProcessRootRank(a, b, loc_res_val, loc_res_row_ind, loc_res_col_ptr, size); + } + + return ProcessWorkerRank(loc_res_val, loc_res_row_ind, loc_res_col_ptr, loc_cols); +} \ No newline at end of file diff --git a/tasks/kaur_a_multy_matrix/seq/include/ops_seq.hpp b/tasks/kaur_a_multy_matrix/seq/include/ops_seq.hpp new file mode 100644 index 00000000..0d9d75e3 --- /dev/null +++ b/tasks/kaur_a_multy_matrix/seq/include/ops_seq.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "kaur_a_multy_matrix/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kaur_a_multy_matrix { + +class KaurAMultyMatrixSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit KaurAMultyMatrixSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static void TransposeMatrix(const SparseMatrixCCS &a, SparseMatrixCCS &at); + static void MultiplyMatrices(const SparseMatrixCCS &a, const SparseMatrixCCS &b, SparseMatrixCCS &c); +}; + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/seq/src/ops_seq.cpp b/tasks/kaur_a_multy_matrix/seq/src/ops_seq.cpp new file mode 100644 index 00000000..f981501b --- /dev/null +++ b/tasks/kaur_a_multy_matrix/seq/src/ops_seq.cpp @@ -0,0 +1,139 @@ +#include "kaur_a_multy_matrix/seq/include/ops_seq.hpp" + +#include +#include +#include +#include + +#include "kaur_a_multy_matrix/common/include/common.hpp" + +namespace kaur_a_multy_matrix { + +namespace { +constexpr double kEpsilon = 1e-10; +} // namespace + +KaurAMultyMatrixSEQ::KaurAMultyMatrixSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = SparseMatrixCCS{}; +} + +bool KaurAMultyMatrixSEQ::ValidationImpl() { + const auto &[a, b] = GetInput(); + return (a.rows > 0 && a.cols > 0 && b.rows > 0 && b.cols > 0 && a.cols == b.rows); +} + +bool KaurAMultyMatrixSEQ::PreProcessingImpl() { + return true; +} + +void KaurAMultyMatrixSEQ::TransposeMatrix(const SparseMatrixCCS &a, SparseMatrixCCS &at) { + at.rows = a.cols; + at.cols = a.rows; + at.nnz = a.nnz; + + if (a.nnz == 0) { + at.values.clear(); + at.row_indices.clear(); + at.col_ptrs.assign(at.cols + 1, 0); + return; + } + + std::vector row_count(at.cols, 0); + for (int i = 0; i < a.nnz; i++) { + row_count[a.row_indices[i]]++; + } + + at.col_ptrs.resize(at.cols + 1); + at.col_ptrs[0] = 0; + for (int i = 0; i < at.cols; i++) { + at.col_ptrs[i + 1] = at.col_ptrs[i] + row_count[i]; + } + + at.values.resize(a.nnz); + at.row_indices.resize(a.nnz); + + std::vector current_pos(at.cols, 0); + for (int col = 0; col < a.cols; col++) { + for (int i = a.col_ptrs[col]; i < a.col_ptrs[col + 1]; i++) { + int row = a.row_indices[i]; + double val = a.values[i]; + + int pos = at.col_ptrs[row] + current_pos[row]; + at.values[pos] = val; + at.row_indices[pos] = col; + current_pos[row]++; + } + } +} + +namespace { + +void ProcessColumnSEQ(const SparseMatrixCCS &at, const SparseMatrixCCS &b, int col_index, std::vector &temp_row, + std::vector &row_marker, std::vector &res_val, std::vector &res_row_ind) { + for (int k = b.col_ptrs[col_index]; k < b.col_ptrs[col_index + 1]; k++) { + int row_b = b.row_indices[k]; + double val_b = b.values[k]; + + for (int idx = at.col_ptrs[row_b]; idx < at.col_ptrs[row_b + 1]; idx++) { + int row_a = at.row_indices[idx]; + double val_a = at.values[idx]; + + if (row_marker[row_a] != col_index) { + row_marker[row_a] = col_index; + temp_row[row_a] = val_a * val_b; + } else { + temp_row[row_a] += val_a * val_b; + } + } + } + + for (size_t i = 0; i < temp_row.size(); i++) { + if (row_marker[i] == col_index && std::abs(temp_row[i]) > kEpsilon) { + res_val.push_back(temp_row[i]); + res_row_ind.push_back(static_cast(i)); + } + } +} + +} // namespace + +void KaurAMultyMatrixSEQ::MultiplyMatrices(const SparseMatrixCCS &a, const SparseMatrixCCS &b, SparseMatrixCCS &c) { + SparseMatrixCCS at; + TransposeMatrix(a, at); + + c.rows = a.rows; + c.cols = b.cols; + c.col_ptrs.push_back(0); + + std::vector temp_row(c.rows, 0.0); + std::vector row_marker(c.rows, -1); + + for (int j = 0; j < b.cols; j++) { + ProcessColumnSEQ(at, b, j, temp_row, row_marker, c.values, c.row_indices); + c.col_ptrs.push_back(static_cast(c.values.size())); + } + + c.nnz = static_cast(c.values.size()); +} + +bool KaurAMultyMatrixSEQ::RunImpl() { + const auto &[a, b] = GetInput(); + + try { + SparseMatrixCCS c; + MultiplyMatrices(a, b, c); + GetOutput() = c; + return true; + } catch (const std::exception &) { + return false; + } +} + +bool KaurAMultyMatrixSEQ::PostProcessingImpl() { + const auto &c = GetOutput(); + return c.rows > 0 && c.cols > 0 && c.col_ptrs.size() == static_cast(c.cols) + 1; +} + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/settings.json b/tasks/kaur_a_multy_matrix/settings.json new file mode 100644 index 00000000..16f25e42 --- /dev/null +++ b/tasks/kaur_a_multy_matrix/settings.json @@ -0,0 +1,7 @@ +{ + "tasks": { + "mpi": "enabled", + "seq": "enabled" + }, + "tasks_type": "processes" +} diff --git a/tasks/kaur_a_multy_matrix/tests/functional/main.cpp b/tasks/kaur_a_multy_matrix/tests/functional/main.cpp new file mode 100644 index 00000000..fd12acc1 --- /dev/null +++ b/tasks/kaur_a_multy_matrix/tests/functional/main.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kaur_a_multy_matrix/common/include/common.hpp" +#include "kaur_a_multy_matrix/mpi/include/ops_mpi.hpp" +#include "kaur_a_multy_matrix/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace kaur_a_multy_matrix { + +SparseMatrixCCS GenerateRandomSparseMatrix(int rows, int cols, double density) { + SparseMatrixCCS matrix; + matrix.rows = rows; + matrix.cols = cols; + matrix.col_ptrs.resize(cols + 1, 0); + + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution<> dis_value(0.1, 10.0); + static std::uniform_real_distribution<> dis_random(0.0, 1.0); + + std::vector col_counts(cols, 0); + std::vector> col_values(cols); + std::vector> col_rows(cols); + + int total_nnz = 0; + + for (int col = 0; col < cols; col++) { + for (int row = 0; row < rows; row++) { + double random_value = dis_random(gen); + + if (random_value < density) { + double value = dis_value(gen); + + col_values[col].push_back(value); + col_rows[col].push_back(row); + col_counts[col]++; + total_nnz++; + } + } + } + + matrix.nnz = total_nnz; + matrix.values.resize(total_nnz); + matrix.row_indices.resize(total_nnz); + + int current_index = 0; + matrix.col_ptrs[0] = 0; + + for (int col = 0; col < cols; col++) { + for (int i = 0; i < col_counts[col]; i++) { + matrix.values[current_index] = col_values[col][i]; + matrix.row_indices[current_index] = col_rows[col][i]; + current_index++; + } + matrix.col_ptrs[col + 1] = current_index; + } + + return matrix; +} + +class KaurMultyMatrixFuncTest : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + std::string matrix_type = std::get<1>(params); + + if (matrix_type == "small") { + a_ = GenerateRandomSparseMatrix(10, 10, 0.3); + b_ = GenerateRandomSparseMatrix(10, 10, 0.3); + } else if (matrix_type == "medium") { + a_ = GenerateRandomSparseMatrix(50, 50, 0.1); + b_ = GenerateRandomSparseMatrix(50, 50, 0.1); + } else { + a_ = GenerateRandomSparseMatrix(100, 100, 0.05); + b_ = GenerateRandomSparseMatrix(100, 100, 0.05); + } + + input_data_ = std::make_pair(a_, b_); + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.rows == 0 && output_data.cols == 0) { + return true; + } + + return output_data.rows == a_.rows && output_data.cols == b_.cols && + output_data.col_ptrs.size() == static_cast(output_data.cols) + 1; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + SparseMatrixCCS a_, b_; + InType input_data_; +}; + +namespace { + +TEST_P(KaurMultyMatrixFuncTest, MatrixMultiplyCorrectness) { + ExecuteTest(GetParam()); +} + +const std::array kTestParams = {std::make_tuple(1, "small"), std::make_tuple(2, "medium"), + std::make_tuple(3, "large")}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_kaur_a_multy_matrix), + ppc::util::AddFuncTask(kTestParams, PPC_SETTINGS_kaur_a_multy_matrix)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = KaurMultyMatrixFuncTest::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(MatrixMultiplyFuncTests, KaurMultyMatrixFuncTest, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace kaur_a_multy_matrix diff --git a/tasks/kaur_a_multy_matrix/tests/performance/main.cpp b/tasks/kaur_a_multy_matrix/tests/performance/main.cpp new file mode 100644 index 00000000..3f2b1b6a --- /dev/null +++ b/tasks/kaur_a_multy_matrix/tests/performance/main.cpp @@ -0,0 +1,109 @@ +#include + +#include +#include +#include +#include + +#include "kaur_a_multy_matrix/common/include/common.hpp" +#include "kaur_a_multy_matrix/mpi/include/ops_mpi.hpp" +#include "kaur_a_multy_matrix/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace kaur_a_multy_matrix { + +SparseMatrixCCS GenerateRandomSparseMatrix(int rows, int cols, double density) { + SparseMatrixCCS matrix; + matrix.rows = rows; + matrix.cols = cols; + matrix.col_ptrs.resize(cols + 1, 0); + + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution<> dis_value(0.1, 10.0); + static std::uniform_real_distribution<> dis_random(0.0, 1.0); + + std::vector col_counts(cols, 0); + std::vector> col_values(cols); + std::vector> col_rows(cols); + + int total_nnz = 0; + + for (int col = 0; col < cols; col++) { + for (int row = 0; row < rows; row++) { + double random_value = dis_random(gen); + + if (random_value < density) { + double value = dis_value(gen); + + col_values[col].push_back(value); + col_rows[col].push_back(row); + col_counts[col]++; + total_nnz++; + } + } + } + + matrix.nnz = total_nnz; + matrix.values.resize(total_nnz); + matrix.row_indices.resize(total_nnz); + + int current_index = 0; + matrix.col_ptrs[0] = 0; + + for (int col = 0; col < cols; col++) { + for (int i = 0; i < col_counts[col]; i++) { + matrix.values[current_index] = col_values[col][i]; + matrix.row_indices[current_index] = col_rows[col][i]; + current_index++; + } + matrix.col_ptrs[col + 1] = current_index; + } + + return matrix; +} + +class KaurMultyMatrixPerfTest : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + int size = 2000; + double density = 0.1; + a_ = GenerateRandomSparseMatrix(size, size, density); + b_ = GenerateRandomSparseMatrix(size, size, density); + input_data_ = std::make_pair(a_, b_); + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.rows == 0 && output_data.cols == 0) { + return true; + } + + return output_data.rows == a_.rows && output_data.cols == b_.cols && + output_data.col_ptrs.size() == static_cast(output_data.cols) + 1; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + SparseMatrixCCS a_, b_; + InType input_data_; +}; + +TEST_P(KaurMultyMatrixPerfTest, RunPerfModes) { + ExecuteTest(GetParam()); +} + +namespace { +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_kaur_a_multy_matrix); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = KaurMultyMatrixPerfTest::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(MatrixMultiplyPerfTests, KaurMultyMatrixPerfTest, kGtestValues, kPerfTestName); +} // namespace + +} // namespace kaur_a_multy_matrix