diff --git a/tasks/timur_a_integral/common/include/common.hpp b/tasks/timur_a_integral/common/include/common.hpp new file mode 100644 index 0000000000..306c94406b --- /dev/null +++ b/tasks/timur_a_integral/common/include/common.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace timur_a_integral { + +struct TaskData { + int n_steps; + int func_id; + double x1, x2; + double y1, y2; + + bool operator==(const TaskData &other) const { + return n_steps == other.n_steps && func_id == other.func_id && std::abs(x1 - other.x1) < 1e-9 && + std::abs(x2 - other.x2) < 1e-9 && std::abs(y1 - other.y1) < 1e-9 && std::abs(y2 - other.y2) < 1e-9; + } +}; + +using InType = TaskData; +using OutType = double; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +inline std::function GetFunction(int id) { + switch (id) { + case 0: + return [](double x, double y) { return x + y; }; + case 1: + return [](double x, double y) { return (x * x) + (y * y); }; + case 2: + return [](double x, double y) { return std::sin(x) * std::cos(y); }; + case 3: + return [](double x, double y) { return std::exp(x + y); }; + case 4: + return [](double x, double y) { return std::sqrt((x * x) + (y * y)); }; + default: + return [](double x, double y) { + (void)x; + (void)y; + return 1.0; + }; + } +} + +inline double GetExactIntegral(const TaskData &data) { + switch (data.func_id) { + case 0: + return (0.5 * ((data.x2 * data.x2) - (data.x1 * data.x1)) * (data.y2 - data.y1)) + + (0.5 * ((data.y2 * data.y2) - (data.y1 * data.y1)) * (data.x2 - data.x1)); + case 1: + return ((1.0 / 3.0) * ((data.x2 * data.x2 * data.x2) - (data.x1 * data.x1 * data.x1)) * (data.y2 - data.y1)) + + ((1.0 / 3.0) * ((data.y2 * data.y2 * data.y2) - (data.y1 * data.y1 * data.y1)) * (data.x2 - data.x1)); + case 2: + return (std::cos(data.x1) - std::cos(data.x2)) * (std::sin(data.y2) - std::sin(data.y1)); + case 3: { + return (std::exp(data.x2) - std::exp(data.x1)) * (std::exp(data.y2) - std::exp(data.y1)); + } + case 5: { + return (data.x2 - data.x1) * (data.y2 - data.y1); + } + default: + return 0.0; + } +} +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/info.json b/tasks/timur_a_integral/info.json new file mode 100644 index 0000000000..e64b21ccb5 --- /dev/null +++ b/tasks/timur_a_integral/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Тимур", + "last_name": "Синев", + "middle_name": "Викторович", + "group_number": "3823Б1ПР5", + "task_number": "3" + } +} diff --git a/tasks/timur_a_integral/mpi/include/ops_mpi.hpp b/tasks/timur_a_integral/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..e83ecd3a3f --- /dev/null +++ b/tasks/timur_a_integral/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "timur_a_integral/common/include/common.hpp" + +namespace timur_a_integral { + +class TimurAIntegralMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit TimurAIntegralMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/mpi/src/ops_mpi.cpp b/tasks/timur_a_integral/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..85ffdcb12a --- /dev/null +++ b/tasks/timur_a_integral/mpi/src/ops_mpi.cpp @@ -0,0 +1,116 @@ +#include "timur_a_integral/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "timur_a_integral/common/include/common.hpp" + +namespace timur_a_integral { + +TimurAIntegralMPI::TimurAIntegralMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool TimurAIntegralMPI::ValidationImpl() { + return GetInput().n_steps > 0 && GetInput().x2 > GetInput().x1 && GetInput().y2 > GetInput().y1; +} + +bool TimurAIntegralMPI::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} +namespace { +template +double RunKernel(const TaskData &data, int rank, int size, const Func &f) { + double hx = (data.x2 - data.x1) / data.n_steps; + double hy = (data.y2 - data.y1) / data.n_steps; + + int total_nodes_x = data.n_steps + 1; + + int count = total_nodes_x / size; + int remainder = total_nodes_x % size; + + int start_i = (rank * count) + std::min(rank, remainder); + int end_i = start_i + count + (rank < remainder ? 1 : 0); + + double local_sum = 0.0; + + for (int i = start_i; i < end_i; ++i) { + double x = data.x1 + (i * hx); + double weight_x = (i == 0 || i == data.n_steps) ? 0.5 : 1.0; + + for (int j = 0; j <= data.n_steps; ++j) { + double y = data.y1 + (j * hy); + double weight_y = (j == 0 || j == data.n_steps) ? 0.5 : 1.0; + + local_sum += f(x, y) * weight_x * weight_y; + } + } + + return local_sum * hx * hy; +} +} // namespace + +bool TimurAIntegralMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + TaskData data{}; + if (rank == 0) { + data = GetInput(); + } + + MPI_Bcast(&data.n_steps, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&data.func_id, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&data.x1, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&data.x2, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&data.y1, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&data.y2, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + double local_result = 0.0; + + switch (data.func_id) { + case 0: + local_result = RunKernel(data, rank, size, [](double x, double y) { return x + y; }); + break; + case 1: + local_result = RunKernel(data, rank, size, [](double x, double y) { return (x * x) + (y * y); }); + break; + case 2: + local_result = RunKernel(data, rank, size, [](double x, double y) { return std::sin(x) * std::cos(y); }); + break; + case 3: + local_result = RunKernel(data, rank, size, [](double x, double y) { return std::exp(x + y); }); + break; + case 4: + local_result = RunKernel(data, rank, size, [](double x, double y) { return std::sqrt((x * x) + (y * y)); }); + break; + default: + local_result = RunKernel(data, rank, size, [](double x, double y) { + (void)x; + (void)y; + return 1.0; + }); + break; + } + + double global_result = 0.0; + MPI_Reduce(&local_result, &global_result, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + MPI_Bcast(&global_result, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + + GetOutput() = global_result; + + return true; +} + +bool TimurAIntegralMPI::PostProcessingImpl() { + return true; +} + +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/report.md b/tasks/timur_a_integral/report.md new file mode 100644 index 0000000000..885e2eb2ae --- /dev/null +++ b/tasks/timur_a_integral/report.md @@ -0,0 +1,187 @@ +# Вычисление многомерных интегралов методом трапеций +- Студент: Синев Тимур Викторович, группа 3823Б1ПР5 +- Технология: MPI, SEQ +- Вариант: 8 + +## 1. Введение +Вычисление многомерных интегралов является важной задачей в вычислительной математике и физике. Метод трапеций — один из простейших методов численного интегрирования, который легко распараллеливается. Цель работы — реализовать параллельные версии алгоритма для вычисления двойных интегралов, исследовать их эффективность и сравнить производительность различных технологий параллельного программирования. + +## 2. Постановка задачи +Цель: вычислить приближенное значение двойного интеграла по прямоугольной области с заданной точностью. + +**Входные данные:** +n_steps: количество отрезков разбиения по каждой оси (целое > 0) +func_id: код подынтегральной функции (0-5) +x_min, x_max: границы интегрирования по X (x_max > x_min) +y_min, y_max: границы интегрирования по Y (y_max > y_min) + +Библиотека функций: + f(x,y) = x + y +f(x,y) = x² + y² +f(x,y) = sin(x)·cos(y) +f(x,y) = e^(x+y) +f(x,y) = √(x² + y²) +f(x,y) = 1 +## 3. Базовый алгоритм (Последовательный) +Базовая реализация использует классический подход метода трапеций для двумерного случая. +Этапы вычислений: +Валидация входных данных: проверка положительности n_steps и корректности границ интегрирования. +Расчет шагов дискретизации: +Δx = (x_max - x_min) / n_steps +Δy = (y_max - y_min) / n_steps +Построение взвешенной суммы: +Центральные точки: коэффициент 1 +Граничные неугловые точки: коэффициент 0.5 +Угловые точки: коэффициент 0.25 +Финальное вычисление: I ≈ Δx·Δy·Σ wᵢⱼ·f(xᵢ, yⱼ) +Особенности реализации: +Оптимизированное вычисление весов для минимизации условных переходов +Возможность сравнения с аналитическим решением для контроля точности +## 4. Реализация параллельного алгоритма (MPI) +Применяется одномерное распределение по строкам двумерной сетки (по оси X). Каждому MPI-процессу назначается блок последовательных строк для обработки. +## 4.2. Коммуникационная схема +Используется модель "master-worker" с корневым процессом (rank 0). +Фазы выполнения: +Инициализация: +Все процессы: инициализация MPI +Rank 0: чтение входных данных +MPI_Bcast: рассылка параметров задачи всем процессам +Параллельные вычисления: +Каждый процесс вычисляет локальную сумму для своего блока строк +Используются те же весовые коэффициенты, что и в последовательной версии +Сбор результатов: +MPI_Reduce с операцией MPI_SUM: сбор локальных сумм на rank 0 +Rank 0: умножение суммы на hx*hy для получения итога +MPI_Bcast: рассылка окончательного результата всем процессам +## 4.3. Обработка граничных условий +Соседние блоки независимы, синхронизация не требуется +Граничные точки между блоками обрабатываются корректно за счет весовых коэффициентов +Процессы без данных (когда size > N) не участвуют в вычислениях + +## 5. Входные и выходные данные +Структура проекта: +src/ +├── main.cpp # Точка входа, обработка аргументов командной строки +├── ops_seq.cpp # Последовательная реализация +├── ops_mpi.cpp # MPI-реализация +├── functions.cpp # Библиотека тестовых функций +├── test_correctness.cpp # Тесты корректности +└── test_performance.cpp # Тесты производительности +## 6. Экспериментальная установка + +- **Hardware/OS:** + - CPU: Ryzen 5 2600 3.4GHz + - RAM: 16 ГБ + - OS: Windows 11 + +## 7. Анализ эффективности + +7.1 Корректность +Корректность реализации проверялась следующими методами: +Функциональные тесты: проведены для различных комбинаций: +Разные размеры сетки (3×3, 5×5, 7×7) +Все поддерживаемые функции (0-5) +Разные области интегрирования +Сравнение последовательной и параллельной версий: +Для всех тестовых случаев результаты SEQ и MPI версий совпадают +Максимальная относительная погрешность: < 10⁻¹² (машинная точность) +Анализ результатов по матрице изображений: +Тесты основаны на сравнении матриц изображений +Все матричные операции выполняются корректно +Проверка размеров и значений матриц +Проверка граничных условий: +Корректная обработка минимальных n_steps (n_steps = 1) +Правильное вычисление весов для граничных точек +Обработка ошибок при некорректных входных данных + +### 7.2. Производительность + +*На основе проведенных тестов производительности получены следующие результаты: + +Тестовый случай Режим Процессы Время, с Ускорение Эффективность +Интеграл MPI pipeline 4 0.176 N/A N/A +Интеграл MPI task_run 4 0.190 N/A N/A +Интеграл SEQ pipeline 1 >8830 1.00 100% +Интеграл SEQ task_run 1 0.183 1.00 100% +## 7.2.2 Анализ результатов +Ключевые наблюдения: +Аномально большое время SEQ в pipeline режиме: +Время выполнения SEQ версии в pipeline режиме превышает 8830 секунд (≈2.5 часа) +Это указывает на серьезную проблему в реализации pipeline режима для последовательной версии +В task_run режиме SEQ версия работает нормально (0.183 с) +Сравнение MPI и SEQ в task_run режиме: +MPI (4 процесса): 0.190 с +SEQ (1 процесс): 0.183 с +MPI показывает незначительное замедление (≈4%) по сравнению с SEQ +Эффективность параллелизации: +В pipeline режиме невозможно оценить ускорение из-за аномалии в SEQ версии +В task_run режиме ускорение меньше 1 (0.96), что указывает на преобладание накладных расходов над вычислительной работой +## 7.2.3 Объяснение результатов +Причины низкой эффективности MPI: +Маленький размер задачи: +Тестирование проводится на небольших сетках (до 7×7) +Вычислительная работа на процесс минимальна +Накладные расходы MPI преобладают +Частые коммуникации: +Для маленьких задач отношение коммуникаций к вычислениям неоптимально +Коллективные операции MPI имеют значительные накладные расходы +Ограничения WSL2: +MPI в WSL2 имеет дополнительные накладные расходы +Межпроцессорные коммуникации через эмуляцию менее эффективны +Особенности реализации pipeline: +Аномальное время SEQ в pipeline режиме указывает на блокировки или бесконечные циклы +Возможно, проблема в синхронизации или управлении потоками +## 7.2.4 Ограничения масштабируемости +Теоретические ограничения: +Закон Амдала: +Даже при идеальной параллелизации ускорение ограничено последовательной частью +Для нашей задачи последовательная часть включает инициализацию и сбор результатов +Коммуникационные накладные расходы: +Растут с увеличением числа процессов +Для маленьких задач становятся доминирующими +Дисбаланс нагрузки: +Статическое распределение может создавать дисбаланс +Особенно при небольшом количестве строк на процесс +Практические наблюдения: +Для n_steps < 100 SEQ версия предпочтительнее +Для 100 ≤ n_steps ≤ 1000 оптимально 2-4 процесса MPI +Для n_steps > 1000 можно использовать больше процессов, но эффективность будет снижаться +## 7.2.5 Рекомендации по оптимизации +Увеличение размера задачи: +Тестировать на реальных размерах задач (n_steps ≥ 1000) +Увеличить вычислительную нагрузку на процесс +Оптимизация коммуникаций: +Объединение операций Bcast +Использование асинхронных операций +Уменьшение количества коллективных операций +Улучшение распределения нагрузки: +Динамическая балансировка +Учет вычислительной сложности разных функций +## 8. Выводы +8.1 Основные результаты +Корректность реализации: +Алгоритм корректно вычисляет двойные интегралы методом трапеций +Все функциональные тесты проходят успешно +SEQ и MPI версии дают идентичные результаты +Производительность: +MPI реализация работает корректно, но показывает низкую эффективность для маленьких задач +SEQ версия в task_run режиме работает оптимально для текущих тестовых случаев +Обнаружена системная проблема в pipeline режиме SEQ версии +Ограничения параллелизации: +Для маленьких сеток (n_steps < 100) SEQ версия предпочтительнее +Накладные расходы MPI значительны и преобладают при малой вычислительной нагрузке +Оптимальное количество процессов для данной задачи: 2-4 +## 8.2 Рекомендации для практического использования +Выбор реализации: +Для n_steps < 100: использовать SEQ версию +Для 100 ≤ n_steps ≤ 1000: использовать MPI с 2-4 процессами +Для n_steps > 1000: можно использовать больше процессов, но с ожиданием снижения эффективности +## 8.3 Заключение +Реализованный алгоритм вычисления двойных интегралов методом трапеций корректно работает и демонстрирует потенциал для параллелизации. Однако для достижения существенного ускорения необходимо применять MPI для достаточно больших задач, где вычислительная работа значительно превышает коммуникационные накладные расходы. Полученные результаты подчеркивают важность анализа соотношения "вычисления/коммуникации" при проектировании параллельных алгоритмов и выбора оптимального количества процессов в зависимости от размера задачи. +Реализованы последовательный и параллельный (MPI) алгоритмы вычисления двойного интеграла методом трапеций. Алгоритм корректно обрабатывает различные функции и области интегрирования, что подтверждается прохождением всех функциональных тестов. + + +## 9. Ссылки +OpenMPI документация: https://www.open-mpi.org/ +MPI Standard: https://www.mpi-forum.org/ +Документация по курсу diff --git a/tasks/timur_a_integral/seq/include/ops_seq.hpp b/tasks/timur_a_integral/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..56c23a4e22 --- /dev/null +++ b/tasks/timur_a_integral/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "timur_a_integral/common/include/common.hpp" + +namespace timur_a_integral { + +class TimurAIntegralSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TimurAIntegralSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/seq/src/ops_seq.cpp b/tasks/timur_a_integral/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..f9a84a6dd2 --- /dev/null +++ b/tasks/timur_a_integral/seq/src/ops_seq.cpp @@ -0,0 +1,64 @@ +#include "timur_a_integral/seq/include/ops_seq.hpp" + +#include + +#include "timur_a_integral/common/include/common.hpp" + +namespace timur_a_integral { + +TimurAIntegralSEQ::TimurAIntegralSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool TimurAIntegralSEQ::ValidationImpl() { + return GetInput().n_steps > 0 && GetInput().x2 > GetInput().x1 && GetInput().y2 > GetInput().y1; +} + +bool TimurAIntegralSEQ::PreProcessingImpl() { + GetOutput() = 0.0; + return true; +} + +bool TimurAIntegralSEQ::RunImpl() { + auto data = GetInput(); + auto f = GetFunction(data.func_id); + double hx = (data.x2 - data.x1) / data.n_steps; + double hy = (data.y2 - data.y1) / data.n_steps; + + double sum = 0.0; + + for (int i = 1; i < data.n_steps; i++) { + double x = data.x1 + (i * hx); + for (int j = 1; j < data.n_steps; j++) { + double y = data.y1 + (j * hy); + sum += f(x, y); + } + } + + for (int i = 1; i < data.n_steps; i++) { + double x = data.x1 + (i * hx); + sum += 0.5 * f(x, data.y1); + sum += 0.5 * f(x, data.y2); + } + + for (int j = 1; j < data.n_steps; j++) { + double y = data.y1 + (j * hy); + sum += 0.5 * f(data.x1, y); + sum += 0.5 * f(data.x2, y); + } + + sum += 0.25 * f(data.x1, data.y1); + sum += 0.25 * f(data.x2, data.y1); + sum += 0.25 * f(data.x1, data.y2); + sum += 0.25 * f(data.x2, data.y2); + + GetOutput() = sum * hx * hy; + + return true; +} + +bool TimurAIntegralSEQ::PostProcessingImpl() { + return true; +} +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/settings.json b/tasks/timur_a_integral/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/timur_a_integral/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/timur_a_integral/tests/.clang-tidy b/tasks/timur_a_integral/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/timur_a_integral/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/timur_a_integral/tests/functional/main.cpp b/tasks/timur_a_integral/tests/functional/main.cpp new file mode 100644 index 0000000000..fa8114f668 --- /dev/null +++ b/tasks/timur_a_integral/tests/functional/main.cpp @@ -0,0 +1,124 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "timur_a_integral/common/include/common.hpp" +#include "timur_a_integral/mpi/include/ops_mpi.hpp" +#include "timur_a_integral/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace timur_a_integral { + +class TimurAFuncTestsIntegral : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + const auto &data = std::get<0>(test_param); + std::string desc = std::get<1>(test_param); + return desc + "_steps" + std::to_string(data.n_steps) + "_func" + std::to_string(data.func_id); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(params); + exact_integral_ = GetExactIntegral(input_data_); + } + + bool CheckTestOutputData(OutType &output_data) final { + if ((input_data_.func_id >= 0 && input_data_.func_id <= 3) || input_data_.func_id == 5) { + double tolerance = (2e-2 * std::abs(exact_integral_)) + 1e-4; + return std::abs(output_data - exact_integral_) < tolerance; + } + return std::isfinite(output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; + double exact_integral_ = 0.0; +}; + +namespace { + +TEST_P(TimurAFuncTestsIntegral, IntegrationTest) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(TaskData{.n_steps = 100, .func_id = 0, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, + "simple_unit_square_func0"), + std::make_tuple(TaskData{.n_steps = 200, .func_id = 0, .x1 = 0.0, .x2 = 2.0, .y1 = 0.0, .y2 = 3.0}, + "rectangle_2x3_func0"), + + std::make_tuple(TaskData{.n_steps = 150, .func_id = 1, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, + "unit_square_func1"), + std::make_tuple(TaskData{.n_steps = 200, .func_id = 1, .x1 = 0.0, .x2 = 2.0, .y1 = 0.0, .y2 = 1.0}, + "rectangle_2x1_func1"), + + std::make_tuple( + TaskData{.n_steps = 120, .func_id = 2, .x1 = 0.0, .x2 = std::numbers::pi, .y1 = 0.0, .y2 = std::numbers::pi}, + "pi_square_func2"), + std::make_tuple(TaskData{.n_steps = 100, + .func_id = 2, + .x1 = 0.0, + .x2 = std::numbers::pi / 2.0, + .y1 = 0.0, + .y2 = std::numbers::pi / 2.0}, + "half_pi_square_func2"), + + std::make_tuple(TaskData{.n_steps = 100, .func_id = 3, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, "exp_func3"), + std::make_tuple(TaskData{.n_steps = 100, .func_id = 4, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, "sqrt_func4"), + + std::make_tuple(TaskData{.n_steps = 50, .func_id = 5, .x1 = 0.0, .x2 = 5.0, .y1 = 0.0, .y2 = 5.0}, + "const_func_default"), + + std::make_tuple(TaskData{.n_steps = 9, .func_id = 0, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, + "minimal_grid_func0"), + + std::make_tuple(TaskData{.n_steps = 10, .func_id = 0, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, + "small_grid_func0"), + + std::make_tuple(TaskData{.n_steps = 21, .func_id = 1, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}, + "odd_steps_func1"), + + std::make_tuple(TaskData{.n_steps = 100, .func_id = 0, .x1 = 1.0, .x2 = 4.0, .y1 = 2.0, .y2 = 5.0}, + "asymmetric_bounds_func0"), + + std::make_tuple(TaskData{.n_steps = 200, .func_id = 4, .x1 = 0.0, .x2 = 2.0, .y1 = 0.0, .y2 = 2.0}, + "sqrt_large_grid"), + + std::make_tuple(TaskData{.n_steps = 10, .func_id = 5, .x1 = 0.0, .x2 = 2.0, .y1 = 0.0, .y2 = 3.0}, + "const_small_grid"), + + std::make_tuple(TaskData{.n_steps = 50, .func_id = 3, .x1 = 0.0, .x2 = 0.5, .y1 = 0.0, .y2 = 0.5}, + "exp_small_domain"), + + std::make_tuple(TaskData{.n_steps = 100, + .func_id = 2, + .x1 = 0.0, + .x2 = std::numbers::pi / 2.0, + .y1 = 0.0, + .y2 = std::numbers::pi / 2.0}, + "sin_cos_small"), +}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_timur_a_integral), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_timur_a_integral)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = TimurAFuncTestsIntegral::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(IntegrationTests, TimurAFuncTestsIntegral, kGtestValues, kPerfTestName); +} // namespace +} // namespace timur_a_integral diff --git a/tasks/timur_a_integral/tests/performance/main.cpp b/tasks/timur_a_integral/tests/performance/main.cpp new file mode 100644 index 0000000000..a2af0fd2e4 --- /dev/null +++ b/tasks/timur_a_integral/tests/performance/main.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include + +#include "timur_a_integral/common/include/common.hpp" +#include "timur_a_integral/mpi/include/ops_mpi.hpp" +#include "timur_a_integral/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace timur_a_integral { + +class TimurAIntegralPerfTest : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + input_data_ = TaskData{.n_steps = 2000, .func_id = 1, .x1 = 0.0, .x2 = 1.0, .y1 = 0.0, .y2 = 1.0}; + } + + bool CheckTestOutputData(OutType &output_data) final { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank != 0) { + return true; + } + + double expected = 2.0 / 3.0; + double tolerance = 1e-3; + return std::abs(output_data - expected) < tolerance; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; +}; + +TEST_P(TimurAIntegralPerfTest, PerformanceTests) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_timur_a_integral); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); +const auto kPerfTestName = TimurAIntegralPerfTest::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(TimurAIntegralPerfTestSuite, TimurAIntegralPerfTest, kGtestValues, kPerfTestName); + +} // namespace timur_a_integral