diff --git a/tasks/savva_d_conjugent_gradients/common/include/common.hpp b/tasks/savva_d_conjugent_gradients/common/include/common.hpp new file mode 100644 index 0000000000..39cc63e105 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/common/include/common.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace savva_d_conjugent_gradients { + +struct InputSystem { + int n = 0; + std::vector a; + std::vector b; +}; + +using InType = InputSystem; +using OutType = std::vector; + +struct TestParams { + InputSystem in{}; + OutType out; + std::string name; +}; + +using TestType = TestParams; +using BaseTask = ppc::task::Task; + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/info.json b/tasks/savva_d_conjugent_gradients/info.json new file mode 100644 index 0000000000..aec7330134 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/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/savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp b/tasks/savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..120d4e2454 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace savva_d_conjugent_gradients { + +class SavvaDConjugentGradientsMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit SavvaDConjugentGradientsMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + static void UpdateXR(std::vector &x, std::vector &r, const std::vector &p, + const std::vector &global_ap, double alpha, int n); + static std::vector ComputeLocalAp(int n, int local_rows, const std::vector &local_a, + const std::vector &p); + static void RunCGIterations(int n, int local_rows, int local_offset, std::vector &r, + const std::vector &local_a, std::vector &vector_x, + const std::vector &counts, const std::vector &displs); +}; + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/mpi/src/ops_mpi.cpp b/tasks/savva_d_conjugent_gradients/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..520aba0878 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/mpi/src/ops_mpi.cpp @@ -0,0 +1,191 @@ +#include "savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +// #include +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" + +namespace savva_d_conjugent_gradients { + +SavvaDConjugentGradientsMPI::SavvaDConjugentGradientsMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector{}; +} + +bool SavvaDConjugentGradientsMPI::ValidationImpl() { + const auto &in = GetInput(); + + if (in.n < 0 || in.a.size() != static_cast(in.n) * static_cast(in.n) || + in.b.size() != static_cast(in.n)) { + return false; + } + + for (int i = 0; i < in.n; ++i) { + for (int j = i + 1; j < in.n; ++j) { + if (std::abs(in.a[(i * in.n) + j] - in.a[(j * in.n) + i]) > 1e-9) { + return false; + } + } + } + + return true; +} + +bool SavvaDConjugentGradientsMPI::PreProcessingImpl() { + GetOutput().assign(GetInput().n, 0.0); + return true; +} + +std::vector SavvaDConjugentGradientsMPI::ComputeLocalAp(int n, int local_rows, + const std::vector &local_a, + const std::vector &p) { + std::vector local_ap(local_rows, 0.0); + for (int i = 0; i < local_rows; ++i) { + double sum = 0.0; + for (int j = 0; j < n; ++j) { + sum += local_a[(i * n) + j] * p[j]; + } + local_ap[i] = sum; + } + return local_ap; +} + +void SavvaDConjugentGradientsMPI::UpdateXR(std::vector &x, std::vector &r, const std::vector &p, + const std::vector &global_ap, double alpha, int n) { + for (int i = 0; i < n; ++i) { + x[i] += alpha * p[i]; + r[i] -= alpha * global_ap[i]; + } +} + +void SavvaDConjugentGradientsMPI::RunCGIterations(int n, int local_rows, int local_offset, std::vector &r, + const std::vector &local_a, std::vector &vector_x, + const std::vector &counts, const std::vector &displs) { + // Константы + const int max_iter = 1000; + const double eps = 1e-9; + + std::vector p(n, 0.0); + p = r; + std::vector global_ap(n, 0.0); + // std::vector local_ap(local_rows, 0.0); + + double rr_old = std::inner_product(r.begin(), r.end(), r.begin(), 0.0); + + for (int iter = 0; iter < max_iter; ++iter) { + if (std::sqrt(rr_old) < eps) { + break; + } + + // A_local * p + + std::vector local_ap = ComputeLocalAp(n, local_rows, local_a, p); + + // собираем глобальный ap + std::ranges::fill(global_ap, 0.0); + for (int i = 0; i < local_rows; ++i) { + global_ap[local_offset + i] = local_ap[i]; + } + MPI_Allgatherv(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, global_ap.data(), counts.data(), displs.data(), MPI_DOUBLE, + MPI_COMM_WORLD); + + double p_ap = std::inner_product(p.begin(), p.end(), global_ap.begin(), 0.0); + + if (std::abs(p_ap) < eps) { + break; + } + + double alpha = rr_old / p_ap; + + UpdateXR(vector_x, r, p, global_ap, alpha, n); + + double rr_new = std::inner_product(r.begin(), r.end(), r.begin(), 0.0); + + double beta = rr_new / rr_old; + + rr_old = rr_new; + + for (int i = 0; i < n; ++i) { + p[i] = r[i] + (beta * p[i]); + } + } +} + +bool SavvaDConjugentGradientsMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + const double *sendbuf_a = nullptr; // будут ненулевыми только на 0 процессе + + int n = 0; + if (rank == 0) { + n = GetInput().n; + } + MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); + std::vector r(n, 0.0); + + if (rank == 0) { + sendbuf_a = GetInput().a.data(); + const auto &full_b = GetInput().b; + std::ranges::copy(full_b, r.begin()); + } + + MPI_Bcast(r.data(), n, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + if (n == 0) { + return true; + } + + std::vector counts(size); + std::vector displs(size); + std::vector counts_a(size); + std::vector displs_a(size); + + int rows_per_proc = n / size; + int remainder = n % size; + int offset = 0; + int local_rows = 0; + int local_offset = 0; + + for (int i = 0; i < size; ++i) { + counts[i] = rows_per_proc + (i < remainder ? 1 : 0); + displs[i] = offset; + counts_a[i] = counts[i] * n; + displs_a[i] = displs[i] * n; + offset += counts[i]; + + if (i == rank) { + local_offset = displs[rank]; + local_rows = counts[rank]; + } + } + + // Выделение памяти под локальные данные + + std::vector local_a(static_cast(local_rows) * static_cast(n)); + + // Рассылка данных + MPI_Scatterv(sendbuf_a, counts_a.data(), displs_a.data(), MPI_DOUBLE, local_a.data(), local_rows * n, MPI_DOUBLE, 0, + MPI_COMM_WORLD); + + auto &x = GetOutput(); + x.assign(n, 0.0); + // Запуск алгоритма + RunCGIterations(n, local_rows, local_offset, r, local_a, x, counts, displs); + + return true; +} + +bool SavvaDConjugentGradientsMPI::PostProcessingImpl() { + return true; +} + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/report.md b/tasks/savva_d_conjugent_gradients/report.md new file mode 100644 index 0000000000..0167f7d2a0 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/report.md @@ -0,0 +1,153 @@ +# Решение систем линейных уравнений методом сопряженных градиентов. + +- Студент: Савва Дария Александровна, 3823Б1ФИ1 +- Технологии: SEQ | MPI +- Вариант: 6 + +## 1. Введение. +Целью данной лабораторной работы является реализация и исследование метода сопряжённых градиентов для решения систем линейных уравнений. +В работе рассматриваются две реализации алгоритма: последовательная (SEQ) и параллельная (MPI), а также проверяется их корректность и измеряется производительность. +Особое внимание уделяется анализу ускорения параллельной версии на многопроцессорной системе и оценке эффективности распределения вычислений между процессами. +## 2. Постановка задачи. +Пусть дана система линейных уравнений порядка n, имеющая вид: + A · x = b, где: + +- $A$ — квадратная симметричная положительно определённая матрица размера n, +- $x$ — вектор неизвестных длины n, +- $b$ — вектор свободных членов длины n. + +Необходимо найти приближённое решение системы линейных уравнений $x$ заданной точности `eps`. + +**Ограничения и требования:** + +- Матрица $A$ должна являться симметричной положительно определённой, чтобы гарантировать сходимость метода. +- Максимальное количество итераций и точность решения фиксируются в алгоритме. + +## 3. Последовательная реализация. + +Основана на классическом методе сопряжённых градиентов. +Алгоритм начинается с начального приближения $x^{(0)}\$ и уточняет решение с каждой последующей итерацией. + +1. Инициализация: + x₀ = 0 - начальный вектор решения + r₀ = b - A·x₀ - начальная невязка + p₀ = r₀ - начальное направление + k = 0 - номер итерации + +2. На каждой итерации $k = 1, 2, \dots,$ max_iter вычисляются: + 2.1. αₖ = (rₖᵀ·rₖ) / (pₖᵀ·A·pₖ) + 2.2. xₖ₊₁ = xₖ + αₖ·pₖ + 2.3. rₖ₊₁ = rₖ - αₖ·A·pₖ + 2.4. Проверка условия остановки: ∥rₖ₊₁∥<ε. Если выполняется - переход к шагу 3. + 2.4. βₖ = (rₖ₊₁ᵀ·rₖ₊₁) / (rₖᵀ·rₖ) + 2.5. pₖ₊₁ = rₖ₊₁ + βₖ·pₖ + +3. Возврат вектора xₖ₊₁ как приближенного решения системы. + +Алгоритм завершается при достижении заданной точности или максимального числа итераций. + +## 4. Описание схемы параллельного алгоритма + +Для ускорения решения системы методом сопряжённых градиентов используется **параллельная реализация с MPI**, основанная на распределении строк матрицы между процессами. + + +**Схема работы алгоритма:** + +**1 этап. Инициализация и разделение данных**: + + 1) Нулевой процесс имеет доступ к входным данным задачи, и рассылает всем процессам размерность матрицы коэффициентов $n$, используя Bcast. + 2) Каждый процесс создаёт вектор $r$ размера $n$, участвующий в последующих вычилениях. Нулевой процесс присваивает ему значение вектора $b$ и рассылает полученный вектор $r$ другим процессам, используя Bcast. + 3) Матрица $A$ делится по строкам между процессами следующим образом: если количество строк $n$ не кратно числу процессов, первые процессы получают на одну строку больше, чтобы сбалансировать нагрузку. Иначе - строки делятся поровну между процессами. + Нулевой процесс рассылает каждому процессу соответсвующие ему строки матрицы $A$. + 4) На каждом процессе создаётся вектор $x$ результирующего решения, изначально инициализированный нулями. Этот вектор одинаковый на соответсвующих итерациях каждого процесса, +в том числе по окончании выполнения алгоритма, расчитывается локально и не участвует в обмене данными. + 5) Каждый процесс также имеет свой вектор $p$, одинаковый для всех процессов на соответсвующих итерациях и расчитываемый локально. Изначально ему присваивается значение вектора $r$. + +**2 этап. Локальные вычисления**: + +Каждый процесс вызывает функцию $RunCGIterations$, выполняющую основной цикл алгоритма. +Внутри этой функции создаётся вектор $global_Ap$ предназначенный для хранения вектора результата умножения всей матрицы $A$ на $p$, +определяются параметры максимальное число итераций $max_iter$ и минимальная погрешность решения $eps$. +Далее цикл $for$, имеющий $max_iter$ шагов. На итерации каждый процесс считает свою часть умножения матрицы $A$ на вектор $p$. Записывает результат в $global_Ap$. +Далее со всех процессов собирается итоговый $global_Ap$, его получает каждый процесс. +Для этого используется MPI-функция $MPI_Allgatherv$, в неё в качестве параметров прередаются в частности массивы $displs$ и $counts$, +последовательно хранящие смещения и количества строк для каждого процесса. +После, используя полученный вектор, локально вычисляются скаляры αₖ, βₖ и вектора rₖ₊₁, pₖ₊₁, $x$ согласно приведённым выше формулам. + +**3 этап. Точка выхода**: +В начале каждой итерации вычисляется невязка предыдущей итерации, и в случае если невязка меньше погрешности $eps$, происходит выход из цикла на каждом процессе - +решение достаточной точности найдено. + +**4 этап. Завершение вычислений**: +Происходит выход из функции $RunCGIterations$. Вектор $x$ содержит решение системы и доступен на всех процессах. + +**Особенности параллельной реализации:** + +- Декомпозиция по строкам матрицы коэффициентов обеспечивает равномерное распределение нагрузки между процессами. +- Основная эффективность параллельного алгоритма достигается за счёт распределения между процессами умножения матрицы системы на вектор. +Вычисления с векторами и числами осуществяются локально, так как распределение вычислений и обмен данными здесь привёл бы к ненужным накладным расходам. + +## 5. Проведение экспериментов + +### 5.1 Условия экспериментов + +- Размер системы линейных уравнений: $n = 3000$ +- Среда выполнения: Windows 10, процессор 4 ядра, MPI (OpenMPI 4.1) +- Число процессов MPI: 1, 2, 4 +- Последовательная версия (SEQ) запускается на одном процессе +- Измерение времени: встроенные средства тестового фреймворка GoogleTest с фиксированной точностью + +### 5.2 Результаты времени выполнения и ускорения + +| Режим выполнения | Число процессов | Время (сек) | Ускорение | Эффективность, % | +|:-----------------|---------------:|------------:|----------:|----------------:| +| SEQ | 1 | 0.0469286000 | 1.00 | – | +| MPI | 2 | 0.0275360926 | 1.70 | 0.85 | +| MPI | 4 | 0.0155672884 | 3.01 | 0.75 | +| MPI | 8 | 0.0537610600 | 0.87 | 0.11 | + + +**Выводы по производительности:** + +- Наиболее эффективно себя показало использование 4 процессов, при котором получено ускорение в 3 раза. При этом количестве процессов достигается наилучший компромисс между эффективностью параллельных вычислений и накладными расходами на коммуникацию между процессами. +- Эффективность уменьшается при дальнейшем увеличении числа процессов после 4 процессов из-за накладных расходов на коммуникацию. + +## Подтверждение корректности + +Функциональные тесты проверяли алгоритмы на следующих системах: + +1. Пустая система +2. Система из одного уравнения +3. 2×2, 3×3, 4×4 системы с отрицательными и дробными элементами + +**Результаты:** +- Решения, которые вернули алгоритмы, сравнивались с вектором, являющимся точным решением соответсвующей системы линейных уравнений. +- Последовательная и параллельная версии дают максимальную погрешность с правильным решением не более 0.0001 для всех тестов. +При этом все системы имеют положительно определённую симметричную матрицу коэффициентов. + +Таким образом, обе реализации метода сопряжённых градиентов работают корректно. + +--- +## 6. Выводы и заключение + +1. Реализованы **последовательная (SEQ)** и **параллельная (MPI)** версии метода сопряжённых градиентов для решения систем линейных уравнений. +2. Проверена корректность обеих реализаций с помощью функциональных тестов на системах разного размера и содержания; результаты с достаточной точностью совпадают с эталонными решениями. +3. Проведены эксперименты на производительность на вычислительной машине с 4 ядрами, использовалась система линейных уравнений размера $n = 3000$: + - Параллельная версия MPI показала ускорение по сравнению с последовательной реализацией. + - Эффективность использования процессов уменьшается с ростом числа процессов, начиная от 4 процессов, из-за накладных расходов на обмен данными. +4. Метод сопряжённых градиентов эффективно решает большие системы при правильном выборе числа процессов и обеспечивает точное решение с высокой сходимостью при положительно определённой симметричной матрицы коэффициентов. + +**Заключение:** +В ходе лабораторной работы был изучен итерационный метод сопряжённых градиентов, реализован в последовательной и параллельной версиях, также была проверена корректность и измерена производительность реализаций алгоритма. MPI-реализация показала преимущество при решении больших систем, подтверждая эффективность распределения вычислений между процессами. +--- +## Список литературы +1. Документация по OpenMPI — https://www.open-mpi.org/doc/ +2. cppreference.com - https://en.cppreference.com/ +3. Лекции по параллельному программированию ННГУ +--- + + + + + + diff --git a/tasks/savva_d_conjugent_gradients/seq/include/ops_seq.hpp b/tasks/savva_d_conjugent_gradients/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..5e2d539085 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/seq/include/ops_seq.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace savva_d_conjugent_gradients { + +class SavvaDConjugentGradientsSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit SavvaDConjugentGradientsSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + static void UpdateXR(std::vector &x, std::vector &r, const std::vector &p, + const std::vector &ap, double alpha, int n); + static void UpdateP(std::vector &p, const std::vector &r, double beta, int n); + static void ComputeAp(const std::vector &a, const std::vector &p, std::vector &ap, int n); +}; + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/seq/src/ops_seq.cpp b/tasks/savva_d_conjugent_gradients/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..e9e8b5c7e2 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/seq/src/ops_seq.cpp @@ -0,0 +1,146 @@ +#include "savva_d_conjugent_gradients/seq/include/ops_seq.hpp" + +// #include +#include +#include +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" + +namespace savva_d_conjugent_gradients { + +SavvaDConjugentGradientsSEQ::SavvaDConjugentGradientsSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector{}; +} + +bool SavvaDConjugentGradientsSEQ::ValidationImpl() { + const auto &in = GetInput(); + + if (in.n < 0 || in.a.size() != static_cast(in.n) * in.n || in.b.size() != static_cast(in.n)) { + return false; + } + + for (int i = 0; i < in.n; ++i) { + for (int j = i + 1; j < in.n; ++j) { + if (std::abs(in.a[(i * in.n) + j] - in.a[(j * in.n) + i]) > 1e-9) { + return false; + } + } + } + + return true; +} + +bool SavvaDConjugentGradientsSEQ::PreProcessingImpl() { + GetOutput().assign(GetInput().n, 0.0); + return true; +} + +void SavvaDConjugentGradientsSEQ::ComputeAp(const std::vector &a, const std::vector &p, + std::vector &ap, int n) { + for (int i = 0; i < n; ++i) { + double sum = 0.0; + for (int j = 0; j < n; ++j) { + sum += a[(i * n) + j] * p[j]; + } + ap[i] = sum; + } +} + +void SavvaDConjugentGradientsSEQ::UpdateXR(std::vector &x, std::vector &r, const std::vector &p, + const std::vector &ap, double alpha, int n) { + for (int i = 0; i < n; ++i) { + x[i] += alpha * p[i]; + r[i] -= alpha * ap[i]; + } +} + +void SavvaDConjugentGradientsSEQ::UpdateP(std::vector &p, const std::vector &r, double beta, int n) { + for (int i = 0; i < n; ++i) { + p[i] = r[i] + (beta * p[i]); + } +} + +bool SavvaDConjugentGradientsSEQ::RunImpl() { + const auto &input = GetInput(); + auto &x = GetOutput(); + + const int n = input.n; + if (n == 0) { + x = std::vector{}; + return true; + } + const int max_iter = 10000; + const double eps = 1e-9; + + std::vector r(n); + std::vector p(n); + std::vector ap(n); + + for (int i = 0; i < n; ++i) { + r[i] = input.b[i]; // r0 = b - A*x0 => так как x0=0, то r0 = b + p[i] = r[i]; + } + + double rs_old = 0.0; // r^T * r + for (int i = 0; i < n; ++i) { + rs_old += r[i] * r[i]; + } + + for (int k = 0; k < max_iter; ++k) { + // Проверка сходимости + if (std::sqrt(rs_old) < eps) { + break; + } + + // 2.1 Вычисление ap = A * p + ComputeAp(input.a, p, ap, n); + + // 2.2 Вычисление alpha = (r^T * r) / (p^T * A * p) + // Знаменатель p^T * ap + double p_ap = 0.0; + for (int i = 0; i < n; ++i) { + p_ap += p[i] * ap[i]; + } + + // Защита от деления на ноль (если матрица не положительно определена) + if (std::abs(p_ap) < 1e-15) { + return false; + } + + double alpha = rs_old / p_ap; + + // 2.3 x = x + alpha * p + // 2.4 r = r - alpha * ap + UpdateXR(x, r, p, ap, alpha, n); + + // Вычисление новой невязки r^T * r (для следующего шага и проверки) + double rs_new = 0.0; + for (int i = 0; i < n; ++i) { + rs_new += r[i] * r[i]; + } + + // Проверка условия выхода + if (std::sqrt(rs_new) < eps) { + break; + } + + // 2.5 Вычисление beta + double beta = rs_new / rs_old; + + // 2.6 p = r + beta * p + UpdateP(p, r, beta, n); + + rs_old = rs_new; + } + + return true; +} + +bool SavvaDConjugentGradientsSEQ::PostProcessingImpl() { + return true; +} + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/settings.json b/tasks/savva_d_conjugent_gradients/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/savva_d_conjugent_gradients/tests/.clang-tidy b/tasks/savva_d_conjugent_gradients/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/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/savva_d_conjugent_gradients/tests/functional/main.cpp b/tasks/savva_d_conjugent_gradients/tests/functional/main.cpp new file mode 100644 index 0000000000..9c4b454dcd --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/tests/functional/main.cpp @@ -0,0 +1,131 @@ +#include +#include + +// #include +#include +#include +#include +#include +#include +#include +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" +#include "savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp" +#include "savva_d_conjugent_gradients/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace savva_d_conjugent_gradients { + +static std::ostream &operator<<(std::ostream &os, const TestType &test_param); + +std::ostream &operator<<(std::ostream &os, const TestType &test_param) { + const auto &in = test_param.in; + + const auto &name = test_param.name; + + os << "Test[" << name << ", n=" << in.n << ", a.size=" << in.a.size() << ", b.size=" << in.b.size() << "]"; + return os; +} + +class SavvaDConjugentGradientsFuncTests : public ppc::util::BaseRunFuncTests { + // тест один общий + public: + static std::string PrintTestParam(const TestType &test_param) { // конструктор названия тестов + const auto &seidelstruct = test_param.in; + const auto &name_test = test_param.name; + return name_test + "_" + "matrix_size_" + std::to_string(seidelstruct.n); + } + + protected: + void SetUp() override { // здесь данные готовятся - например читаются изображения + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = params.in; + right_output_data_ = params.out; + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != right_output_data_.size()) { + return false; + } + for (size_t i = 0; i < output_data.size(); ++i) { + if (0.001 < std::abs(output_data[i] - right_output_data_[i])) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType right_output_data_; +}; + +namespace { +// реализация (но пока не запуск) тестов +TEST_P(SavvaDConjugentGradientsFuncTests, + MatmulFromPic) { // не изменяется во всех задачах - генерация теста с параметрами + ExecuteTest(GetParam()); +} + +const InputSystem kParam1{.n = 0, .a = {}, .b = {}}; +const OutType kVec1{}; + +const InputSystem kParam2{.n = 1, .a = {2.0}, .b = {4.0}}; +const OutType kVec2{2.0}; + +const InputSystem kParam3{.n = 2, .a = {7.0, 2.0, 2.0, 5.0}, .b = {11.0, 12.0}}; +const OutType kVec3{1.0, 2.0}; + +const InputSystem kParam4{.n = 3, .a = {3.0, 1.0, 0.0, 1.0, 4.0, 2.0, 0.0, 2.0, 5.0}, .b = {4.0, 7.0, 7.0}}; +const OutType kVec4{1.0, 1.0, 1.0}; + +const double kV50 = 325581.0 / 17119448.0; +const double kV51 = 925413.0 / 17119448.0; +const double kV52 = 353133.0 / 17119448.0; +const double kV53 = 2896595.0 / 17119448.0; + +const InputSystem kParam5{ + .n = 4, + .a = {99.0, -6.0, 5.0, 2.0, -6.0, 50.0, 11.0, 7.0, 5.0, 11.0, 112.0, 0.0, 2.0, 7.0, 0.0, 33.0}, + .b = {2.0, 4.0, 3.0, 6.0}}; +const OutType kVec5{kV50, kV51, kV52, kV53}; + +const double kV60 = 200359.0 / 3581592.0; +const double kV61 = 21711.0 / 298466.0; +const double kV62 = 110773.0 / 596932.0; + +const InputSystem kParam6{.n = 3, .a = {15.6, 0.0, -1.2, 0.0, 17.9, -5.4, -1.2, -5.4, 40.2}, .b = {0.65, 0.3, 7.0}}; +const OutType kVec6{kV60, kV61, kV62}; + +const InputSystem kParam7{.n = 3, .a = {3.0, 1.0, 0.0, 1.0, 4.0, 2.0, 0.0, 2.0, 5.0}, .b = {0.0, 0.0, 0.0}}; +const OutType kVec7{0.0, 0.0, 0.0}; + +const std::array kTestParam = {{{.in = kParam1, .out = kVec1, .name = "empty_system"}, + {.in = kParam2, .out = kVec2, .name = "single_equation"}, + {.in = kParam3, .out = kVec3, .name = "two_by_two_system"}, + {.in = kParam4, .out = kVec4, .name = "three_by_three_system"}, + {.in = kParam5, .out = kVec5, .name = "foo_by_foo_systame"}, + {.in = kParam6, .out = kVec6, .name = "float_system"}, + {.in = kParam7, .out = kVec7, .name = "null_execute"}}}; + +// не изменяется (определяет какие тесты будем запускать - сек и мпай ) +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_savva_d_conjugent_gradients), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_savva_d_conjugent_gradients)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = SavvaDConjugentGradientsFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, SavvaDConjugentGradientsFuncTests, kGtestValues, + kPerfTestName); // здесь запуск тестов + +} // namespace + +} // namespace savva_d_conjugent_gradients diff --git a/tasks/savva_d_conjugent_gradients/tests/performance/main.cpp b/tasks/savva_d_conjugent_gradients/tests/performance/main.cpp new file mode 100644 index 0000000000..f8171b1ba9 --- /dev/null +++ b/tasks/savva_d_conjugent_gradients/tests/performance/main.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include + +#include "savva_d_conjugent_gradients/common/include/common.hpp" +#include "savva_d_conjugent_gradients/mpi/include/ops_mpi.hpp" +#include "savva_d_conjugent_gradients/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" +// #include "util/include/util.hpp" + +namespace savva_d_conjugent_gradients { + +class SavvaDConjugentGradientsPerfTest : public ppc::util::BaseRunPerfTests { + protected: + InType input_data; + OutType right_output_data; + + void SetUp() override { + const int n = 3000; + + input_data.n = n; + input_data.a.assign(static_cast(n) * static_cast(n), 0.0); + input_data.b.assign(n, 0.0); + right_output_data.resize(n); + + for (int i = 0; i < n; ++i) { + right_output_data[i] = std::sin(0.001 * i) + 2.0; + } + + for (int i = 0; i < n; ++i) { + double diag_sum = 0.0; + + if (i > 0) { + input_data.a[(i * n) + (i - 1)] = -1.0; + diag_sum += 1.0; + } + + if (i < n - 1) { + input_data.a[(i * n) + (i + 1)] = -1.0; + diag_sum += 1.0; + } + + input_data.a[(i * n) + i] = diag_sum + 20.0; + } + + for (int i = 0; i < n; ++i) { + double sum = 0.0; + for (int j = 0; j < n; ++j) { + sum += input_data.a[(i * n) + j] * right_output_data[j]; + } + input_data.b[i] = sum; + } + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != right_output_data.size()) { + return false; + } + for (size_t i = 0; i < output_data.size(); ++i) { + if (std::abs(output_data[i] - right_output_data[i]) > 0.0001) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data; + } +}; + +// Тест на производительность +TEST_P(SavvaDConjugentGradientsPerfTest, RunPerfModes) { + ExecuteTest(GetParam()); // pipeline (SEQ или MPI) +} + +// Создаем список всех перф-задач (SEQ и MPI) +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_savva_d_conjugent_gradients); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = SavvaDConjugentGradientsPerfTest::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, SavvaDConjugentGradientsPerfTest, kGtestValues, kPerfTestName); + +} // namespace savva_d_conjugent_gradients