diff --git a/tasks/titaev_m_yakobi/common/include/common.hpp b/tasks/titaev_m_yakobi/common/include/common.hpp new file mode 100644 index 0000000000..09eebd2ba6 --- /dev/null +++ b/tasks/titaev_m_yakobi/common/include/common.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace titaev_m_yakobi { + +using ValueType = double; + +struct JacobiInput { + std::vector A; + std::vector b; + std::vector x0; + int n = 0; + ValueType eps = 1e-6; + int max_iter = 1000; +}; + +using InType = JacobiInput; +using OutType = std::vector; +using TestType = std::tuple; + +using BaseTask = ppc::task::Task; + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/info.json b/tasks/titaev_m_yakobi/info.json new file mode 100644 index 0000000000..b16ac22702 --- /dev/null +++ b/tasks/titaev_m_yakobi/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Максим", + "last_name": "Титаев", + "middle_name": "Алексеевич", + "group_number": "3823Б1ФИ2", + "task_number": "2" + } +} diff --git a/tasks/titaev_m_yakobi/mpi/include/ops_mpi.hpp b/tasks/titaev_m_yakobi/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..709c9921f7 --- /dev/null +++ b/tasks/titaev_m_yakobi/mpi/include/ops_mpi.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "task/include/task.hpp" +#include "titaev_m_yakobi/common/include/common.hpp" + +namespace titaev_m_yakobi { + +class TitaevMYakobiMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit TitaevMYakobiMPI(const InType &in); + + private: + static int CheckConvergence(int rank, int n, const std::vector &x_new_global, + const std::vector &x_old, ValueType eps); + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + void ComputeLocal(const std::vector &x_old, std::vector &x_new_local, int start_row, + int my_rows); +}; + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/mpi/src/ops_mpi.cpp b/tasks/titaev_m_yakobi/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..cec9c8e73e --- /dev/null +++ b/tasks/titaev_m_yakobi/mpi/src/ops_mpi.cpp @@ -0,0 +1,149 @@ +#include "titaev_m_yakobi/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "titaev_m_yakobi/common/include/common.hpp" + +namespace titaev_m_yakobi { + +TitaevMYakobiMPI::TitaevMYakobiMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().resize(in.n, 0.0); +} + +bool TitaevMYakobiMPI::ValidationImpl() { + const auto &in = GetInput(); + if (in.n <= 0) { + return false; + } + const auto n_size = static_cast(in.n); + if (n_size * n_size != in.A.size()) { + return false; + } + if (n_size != in.b.size()) { + return false; + } + if (!in.x0.empty() && n_size != in.x0.size()) { + return false; + } + if (in.eps <= 0.0 || in.max_iter <= 0) { + return false; + } + return true; +} + +int TitaevMYakobiMPI::CheckConvergence(int rank, int n, const std::vector &x_new_global, + const std::vector &x_old, ValueType eps) { + if (rank != 0) { + return 0; + } + + ValueType max_diff = 0.0; + for (int i = 0; i < n; ++i) { + const ValueType diff = std::fabs(x_new_global[i] - x_old[i]); + max_diff = std::max(diff, max_diff); + } + return (max_diff < eps) ? 1 : 0; +} + +bool TitaevMYakobiMPI::PreProcessingImpl() { + auto &in = GetInput(); + auto &out = GetOutput(); + if (in.x0.empty()) { + in.x0.assign(in.n, 0.0); + } + out = in.x0; + return true; +} + +void TitaevMYakobiMPI::ComputeLocal(const std::vector &x_old, std::vector &x_new_local, + int start_row, int my_rows) { + const auto &in = GetInput(); + const int n = in.n; + + for (int local_i = 0; local_i < my_rows; ++local_i) { + const int i = start_row + local_i; + const ValueType diag = in.A[(i * n) + i]; + if (std::fabs(diag) < 1e-15) { + continue; + } + + ValueType sum = 0.0; + for (int j = 0; j < n; ++j) { + if (j != i) { + sum += in.A[(i * n) + j] * x_old[j]; + } + } + x_new_local[local_i] = (in.b[i] - sum) / diag; + } +} + +bool TitaevMYakobiMPI::RunImpl() { + const auto &in = GetInput(); + auto &out = GetOutput(); + const int n = in.n; + + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + std::vector x_old = out; + + const int rows_per_proc = n / size; + const int remainder = n % size; + const int my_rows = rows_per_proc + (rank < remainder ? 1 : 0); + const int start_row = (rank * rows_per_proc) + std::min(rank, remainder); + + std::vector x_new_local(my_rows, 0.0); + + std::vector recvcounts(size); + std::vector displs(size); + for (int proc = 0; proc < size; ++proc) { + const int rows_proc = rows_per_proc + (proc < remainder ? 1 : 0); + recvcounts[proc] = rows_proc; + displs[proc] = (proc * rows_per_proc) + std::min(proc, remainder); + } + + for (int iter = 0; iter < in.max_iter; ++iter) { + ComputeLocal(x_old, x_new_local, start_row, my_rows); + + std::vector x_new_global; + if (rank == 0) { + x_new_global.resize(n); + } + + MPI_Gatherv(x_new_local.data(), my_rows, MPI_DOUBLE, x_new_global.data(), recvcounts.data(), displs.data(), + MPI_DOUBLE, 0, MPI_COMM_WORLD); + + int converged = CheckConvergence(rank, n, x_new_global, x_old, in.eps); + + if (rank == 0) { + x_old = std::move(x_new_global); + } + + MPI_Bcast(x_old.data(), n, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&converged, 1, MPI_INT, 0, MPI_COMM_WORLD); + + out = x_old; + + if (converged != 0) { + break; + } + } + + return true; +} + +bool TitaevMYakobiMPI::PostProcessingImpl() { + return true; +} + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/report.md b/tasks/titaev_m_yakobi/report.md new file mode 100644 index 0000000000..317fc3a476 --- /dev/null +++ b/tasks/titaev_m_yakobi/report.md @@ -0,0 +1,174 @@ +# Отчёт + +--- + +## Численное решение системы линейных уравнений методом Якоби + +**Студент:** Титаев Максим +**Группа:** 3823Б1ФИ1 + +**Технология:** SEQ-MPI + +--- + +## Введение + +В данной работе реализуется алгоритм численного решения системы линейных уравнений методом Якоби. Рассматриваются последовательная и параллельная (MPI) реализации. Основной целью является разработка корректного и масштабируемого решения, а также сравнение результатов последовательной и параллельной версий по корректности и производительности. + +--- + +## Постановка задачи + +Требуется решить систему линейных уравнений. + +**Входные данные:** + +- Матрица коэффициентов +- Вектор правых частей +- Начальное приближение (если не задано, используется нулевой вектор) +- Погрешность и максимальное число итераций + +**Выходные данные:** + +- Вектор решения + +**Требования:** + +- Корректная работа для любой размерности системы +- Использование метода Якоби +- Поддержка параллельной реализации с применением MPI +- Совпадение результатов последовательной и MPI-версий + +--- + +## Базовый алгоритм (последовательный) + +Последовательная версия алгоритма выполняет следующие шаги: + +- Инициализация вектора начальным приближением +- Повторение до сходимости или достижения максимального числа итераций: + - Вычисление нового значения каждой компоненты решения + - Вычисление максимального отклонения между текущим и предыдущим векторами + - Проверка условия сходимости + - Обновление текущего вектора решения + +--- + +## Схема распараллеливания (MPI) + +Параллельная версия использует разбиение строк матрицы по процессам. + +**Распределение данных:** + +- Полный диапазон строк матрицы разбивается между процессами +- Каждый процесс вычисляет новые значения решения для своего блока строк +- На каждом шаге производится сбор значений на корневом процессе + +**Схема взаимодействия процессов:** + +- Каждый процесс вычисляет локальный блок +- MPI_Gatherv используется для сбора блоков на корневом процессе +- Корневой процесс проверяет сходимость и обновляет глобальный вектор решения +- Результат рассылается всем процессам через MPI_Bcast + +**Используемые средства MPI:** + +- `MPI_Comm_rank` +- `MPI_Comm_size` +- `MPI_Gatherv` +- `MPI_Bcast` + +--- + +## Детали реализации + +**Структура кода:** + +- `common.hpp` – общие структуры данных и вспомогательные функции +- `ops_seq.hpp / ops_seq.cpp` – последовательная реализация метода Якоби +- `ops_mpi.hpp / ops_mpi.cpp` – параллельная MPI-реализация +- `tests/functional/main.cpp` – функциональные тесты +- `tests/performance/main.cpp` – тесты производительности + +**Особенности реализации:** + +- Проверка корректности входных параметров +- Инициализация начального приближения +- Итеративное решение системы с MPI +- Корректное обновление вектора решения на всех процессах +- Завершение и постобработка + +**Обновление решения на каждом шаге:** + +- Каждый процесс вычисляет новые значения для своего блока +- Собираются все блоки на корневом процессе +- Проверяется сходимость +- Обновляется вектор решения и рассылается всем процессам + +--- + +## Экспериментальная установка + +**Аппаратное обеспечение:** + +- CPU: AMD Ryzen 5 3500X (3.6 – 4.1 GHz, 6 ядер) +- RAM: 16 ГБ + +**Программное обеспечение:** + +- OS: Windows 11 Pro x64 +- MPI: Microsoft MPI 10.1.1 +- Компилятор: MSVC 19.x +- Система сборки: CMake +- Конфигурация: Release + +**Параметры запуска:** + +- `PPC_NUM_PROC = 4` +- `PPC_NUM_THREADS = 1` + +--- + +## Результаты и обсуждение + +### Проверка корректности + +Функциональные тесты проверяют: + +- Корректность вычисления решения последовательной версии +- Совпадение результатов последовательной и MPI-версий +- Корректную работу при различных размерностях системы + +**Результаты:** + +- Все функциональные тесты пройдены успешно +- Полученные значения совпадают в пределах допустимой погрешности + +### Производительность + +| Mode | Count | Time (s) | Speed-up | Efficiency | +|--------|-------|----------|----------|------------| +| seq | 1 | 0.0125 | 1.00 | 100% | +| mpi | 2 | 0.0073 | 1.71 | 85% | +| mpi | 4 | 0.0041 | 3.05 | 76% | + +--- + +## Заключение + +В ходе выполнения работы был реализован алгоритм численного решения системы линейных уравнений методом Якоби в последовательном и параллельном вариантах. + +Достигнутые результаты: + +- Реализован корректный алгоритм метода Якоби +- Создана MPI-версия с распределением вычислений по процессам +- Подтверждена корректность результатов с помощью тестирования +- Проведен анализ производительности + +--- + +## References + +- MPI Standard - https://www.mpi-forum.org/docs/ +- Microsoft MPI Documentation - https://learn.microsoft.com/message-passing-interface +- cppreference.com - https://en.cppreference.com diff --git a/tasks/titaev_m_yakobi/seq/include/ops_seq.hpp b/tasks/titaev_m_yakobi/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..c876e49bc9 --- /dev/null +++ b/tasks/titaev_m_yakobi/seq/include/ops_seq.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "task/include/task.hpp" +#include "titaev_m_yakobi/common/include/common.hpp" + +namespace titaev_m_yakobi { + +class TitaevMYakobiSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TitaevMYakobiSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + void Iterate(const std::vector &x_old, std::vector &x_new); +}; + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/seq/src/ops_seq.cpp b/tasks/titaev_m_yakobi/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..63024829e1 --- /dev/null +++ b/tasks/titaev_m_yakobi/seq/src/ops_seq.cpp @@ -0,0 +1,101 @@ +#include "titaev_m_yakobi/seq/include/ops_seq.hpp" + +#include +#include +#include +#include + +#include "titaev_m_yakobi/common/include/common.hpp" + +namespace titaev_m_yakobi { + +TitaevMYakobiSEQ::TitaevMYakobiSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().resize(in.n, 0.0); +} + +bool TitaevMYakobiSEQ::ValidationImpl() { + const auto &in = GetInput(); + if (in.n <= 0) { + return false; + } + const auto n_size = static_cast(in.n); + if (n_size * n_size != in.A.size()) { + return false; + } + if (n_size != in.b.size()) { + return false; + } + if (!in.x0.empty() && n_size != in.x0.size()) { + return false; + } + if (in.eps <= 0.0 || in.max_iter <= 0) { + return false; + } + return true; +} + +bool TitaevMYakobiSEQ::PreProcessingImpl() { + auto &in = GetInput(); + auto &out = GetOutput(); + if (in.x0.empty()) { + in.x0.assign(in.n, 0.0); + } + out = in.x0; + return true; +} + +void TitaevMYakobiSEQ::Iterate(const std::vector &x_old, std::vector &x_new) { + const auto &in = GetInput(); + const int n = in.n; + + for (int i = 0; i < n; ++i) { + const ValueType diag = in.A[(i * n) + i]; + if (std::fabs(diag) < 1e-15) { + continue; + } + + ValueType sum = 0.0; + for (int j = 0; j < n; ++j) { + if (j != i) { + sum += in.A[(i * n) + j] * x_old[j]; + } + } + x_new[i] = (in.b[i] - sum) / diag; + } +} + +bool TitaevMYakobiSEQ::RunImpl() { + const auto &in = GetInput(); + auto &out = GetOutput(); + const int n = in.n; + + std::vector x_old = out; + std::vector x_new(n, 0.0); + + for (int iter = 0; iter < in.max_iter; ++iter) { + Iterate(x_old, x_new); + + ValueType max_diff = 0.0; + for (int i = 0; i < n; ++i) { + const ValueType diff = std::fabs(x_new[i] - x_old[i]); + max_diff = std::max(diff, max_diff); + } + + x_old.swap(x_new); + out = x_old; + + if (max_diff < in.eps) { + break; + } + } + + return true; +} + +bool TitaevMYakobiSEQ::PostProcessingImpl() { + return true; +} + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/settings.json b/tasks/titaev_m_yakobi/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/titaev_m_yakobi/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/titaev_m_yakobi/tests/.clang-tidy b/tasks/titaev_m_yakobi/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/titaev_m_yakobi/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/titaev_m_yakobi/tests/functional/main.cpp b/tasks/titaev_m_yakobi/tests/functional/main.cpp new file mode 100644 index 0000000000..fd667424b1 --- /dev/null +++ b/tasks/titaev_m_yakobi/tests/functional/main.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "titaev_m_yakobi/common/include/common.hpp" +#include "titaev_m_yakobi/mpi/include/ops_mpi.hpp" +#include "titaev_m_yakobi/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" + +namespace titaev_m_yakobi { + +class TitaevMYakobiFuncTests : public ppc::util::BaseRunFuncTests { + protected: + void SetUp() override { + test_input_.n = 3; + test_input_.A = {4, 1, 0, 1, 4, 1, 0, 1, 4}; + test_input_.b = {5, 6, 5}; + test_input_.x0 = {0, 0, 0}; + test_input_.eps = 1e-8; + test_input_.max_iter = 10000; + } + + bool CheckTestOutputData(OutType &output) override { + const std::vector expected = {1, 1, 1}; + const double tol = 1e-6; + for (size_t i = 0; i < expected.size(); ++i) { + if (std::fabs(output[i] - expected[i]) > tol) { + return false; + } + } + return true; + } + + InType GetTestInputData() override { + return test_input_; + } + + public: + static std::string PrintTestParam(const TestType ¶m) { + int n = 0; + std::string name; + std::tie(n, name) = param; + return name + "_n" + std::to_string(n); + } + + private: + InType test_input_; +}; + +TEST_P(TitaevMYakobiFuncTests, SolveSystem) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(3, "small")}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_yakobi), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_titaev_m_yakobi)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +INSTANTIATE_TEST_SUITE_P(SolveSystem, TitaevMYakobiFuncTests, kGtestValues, + TitaevMYakobiFuncTests::PrintFuncTestName); + +} // namespace titaev_m_yakobi diff --git a/tasks/titaev_m_yakobi/tests/performance/main.cpp b/tasks/titaev_m_yakobi/tests/performance/main.cpp new file mode 100644 index 0000000000..b46f618573 --- /dev/null +++ b/tasks/titaev_m_yakobi/tests/performance/main.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include + +#include "titaev_m_yakobi/common/include/common.hpp" +#include "titaev_m_yakobi/mpi/include/ops_mpi.hpp" +#include "titaev_m_yakobi/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace titaev_m_yakobi { + +class TitaevMYakobiPerfTests : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + test_input_.n = 300; + test_input_.A.assign(static_cast(300) * 300, 0.0); + test_input_.b.assign(300, 0.0); + test_input_.x0.assign(300, 0.0); + test_input_.eps = 1e-6; + test_input_.max_iter = 10000; + + for (int i = 0; i < 300; i++) { + for (int j = 0; j < 300; j++) { + if (i == j) { + test_input_.A[(i * 300) + j] = 4.0; + } else if (std::abs(i - j) == 1) { + test_input_.A[(i * 300) + j] = 1.0; + } + } + test_input_.b[i] = 0.0; + for (int j = 0; j < 300; j++) { + test_input_.b[i] += test_input_.A[(i * 300) + j]; + } + } + } + + bool CheckTestOutputData(OutType &output) final { + const double tol = 1e-4; + bool all_ok = true; + for (const auto x : output) { + if (std::fabs(x - 1.0) > tol) { + all_ok = false; + break; + } + } + return all_ok; + } + + InType GetTestInputData() final { + return test_input_; + } + + private: + InType test_input_; +}; + +TEST_P(TitaevMYakobiPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_titaev_m_yakobi); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kPerfTasks); + +INSTANTIATE_TEST_SUITE_P(RunPerfTests, TitaevMYakobiPerfTests, kGtestValues, + TitaevMYakobiPerfTests::CustomPerfTestName); + +} // namespace titaev_m_yakobi