diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/common/include/common.hpp b/tasks/kutergin_v_trapezoid_method_of_integration/common/include/common.hpp new file mode 100644 index 0000000000..b6f2c401c0 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/common/include/common.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace kutergin_v_trapezoid_seq { + +struct InputData { + double a; + double b; + int n; +}; + +using InType = InputData; +using OutType = double; +using BaseTask = ppc::task::Task; +using TestType = std::tuple; // Тип для тестовых параметров {входные данные, ожидаемый + // результат, имя_теста} + +} // namespace kutergin_v_trapezoid_seq diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/data/input.txt b/tasks/kutergin_v_trapezoid_method_of_integration/data/input.txt new file mode 100644 index 0000000000..c66edf58fb --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/data/input.txt @@ -0,0 +1 @@ +0.0 10.0 3000000 \ No newline at end of file diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/info.json b/tasks/kutergin_v_trapezoid_method_of_integration/info.json new file mode 100644 index 0000000000..15ee440abc --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Валентин", + "last_name": "Кутергин", + "middle_name": "Тимофеевич", + "group_number": "3823Б1ФИ3 ", + "task_number": "1" + } +} diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/mpi/include/trapezoid_integration_mpi.hpp b/tasks/kutergin_v_trapezoid_method_of_integration/mpi/include/trapezoid_integration_mpi.hpp new file mode 100644 index 0000000000..53bb0e2dae --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/mpi/include/trapezoid_integration_mpi.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "../../common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kutergin_v_trapezoid_mpi { + +double Func(double x); + +class TrapezoidIntegrationMPI + : public kutergin_v_trapezoid_seq::BaseTask // наследник BaseTask (псевдоним для ppc::task::Task) +{ + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit TrapezoidIntegrationMPI( + const kutergin_v_trapezoid_seq::InType &in); // конструктор принимает InType (псевдоним для структуры InputData) + + protected: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kutergin_v_trapezoid_mpi diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/mpi/src/trapezoid_integration_mpi.cpp b/tasks/kutergin_v_trapezoid_method_of_integration/mpi/src/trapezoid_integration_mpi.cpp new file mode 100644 index 0000000000..89fcb64f18 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/mpi/src/trapezoid_integration_mpi.cpp @@ -0,0 +1,90 @@ +#include "../include/trapezoid_integration_mpi.hpp" + +#include + +#include + +#include "../../common/include/common.hpp" + +namespace kutergin_v_trapezoid_mpi { + +double Func(double x) // интегрируемая функция для примера +{ + return x * x; +} + +TrapezoidIntegrationMPI::TrapezoidIntegrationMPI(const kutergin_v_trapezoid_seq::InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи + GetInput() = in; // сохранение входных данных + GetOutput() = 0.0; // инициализация выходных данных +} + +bool TrapezoidIntegrationMPI::ValidationImpl() { + return (GetInput().a <= GetInput().b) && (GetInput().n > 0); +} + +bool TrapezoidIntegrationMPI::PreProcessingImpl() { + return true; +} + +bool TrapezoidIntegrationMPI::RunImpl() { + int process_rank = 0; + int process_count = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); + MPI_Comm_size(MPI_COMM_WORLD, &process_count); + + kutergin_v_trapezoid_seq::InType broadcast_data; + if (process_rank == 0) { + broadcast_data = GetInput(); + } + + // Каждое поле рассылается отдельно + MPI_Bcast(&broadcast_data.a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&broadcast_data.b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&broadcast_data.n, 1, MPI_INT, 0, MPI_COMM_WORLD); + + double a = broadcast_data.a; + double b = broadcast_data.b; + int n = broadcast_data.n; + double h = (b - a) / n; + + const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов + const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов + + const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс + + int start_index = 0; + if (process_rank < remainder) { + start_index = process_rank * (base_n + 1); + } else { + start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n); + } + + double local_a = a + (start_index * h); // начало отрезка для текущего процесса + + // локальные вычисления + double local_sum = 0.0; + if (local_n > 0) { + local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0; + } + + for (int i = 1; i < local_n; ++i) { + local_sum += Func(local_a + (i * h)); + } + + // агрегация + double global_sum = 0.0; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + if (process_rank == 0) { + GetOutput() = global_sum * h; + } + + return true; +} + +bool TrapezoidIntegrationMPI::PostProcessingImpl() { + return true; +} + +} // namespace kutergin_v_trapezoid_mpi diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/report.md b/tasks/kutergin_v_trapezoid_method_of_integration/report.md new file mode 100644 index 0000000000..bf70d86113 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/report.md @@ -0,0 +1,125 @@ +# Отчет по лабораторной работе №1 +## "Интегрирование методом трапеций" + +**Студент:** Кутергин Валентин +**Группа:** 3823Б1ФИ3 +**Вариант:** 20 + +### 1. Введение +**Мотивация:** Численное интегрирование — фундаментальная задача во многих научных и инженерных областях. Цель данной работы — реализовать параллельную версию алгоритма интегрирования методом трапеций с использованием технологии MPI и исследовать эффективность распараллеливания по сравнению с последовательным вариантом. + +**Проблема:** Метод трапеций, как и многие численные методы, хорошо поддается декомпозиции. Однако, каждая отдельная итерация (вычисление площади одной трапеции) имеет очень низкую вычислительную сложность. Это создает риск, что накладные расходы на организацию параллельного взаимодействия (коммуникации) могут превысить выгоду от параллельных вычислений. + +**Ожидаемый результат:** Ожидается, что MPI-версия покажет ускорение только на задачах с очень большим количеством итераций (`n`), где время вычислений станет сопоставимым с затратами на коммуникацию. Для задач с низкой или средней вычислительной нагрузкой параллельная версия, скорее всего, будет уступать последовательной. + +### 2. Постановка задачи +**Задано:** Функция `f(x)`, отрезок интегрирования `[a, b]` и количество шагов разбиения `n`. + +**Требуется:** Численно вычислить определенный интеграл функции `f(x)` на заданном отрезке. + +**Входные данные:** `double a`, `double b`, `int n`. +**Выходные данные:** `double result` — приближенное значение интеграла. + +### 3. Базовый алгоритм (Последовательный) +Последовательный алгоритм реализует формулу метода трапеций: +`Интеграл ≈ h * [ (f(a) + f(b))/2 + f(x_1) + f(x_2) + ... + f(x_{n-1}) ]`, где `h = (b-a)/n`. + +Алгоритм состоит из одного цикла, который итеративно вычисляет и суммирует значения функции в точках разбиения. Сложность алгоритма — `O(n)`. + +### 4. Схема распараллеливания +**Декомпозиция данных:** +Основная вычислительная нагрузка — это суммирование `n` значений функции. Эта сумма была разделена между `P` MPI-процессами. Используется стратегия **блочного распределения**: общее число итераций `n` делится на `P`. + +**Обработка остатка:** +Чтобы алгоритм работал для любых `n` и `P`, реализована корректная обработка остатка от деления. Первые `n % P` процессов получают на одну итерацию больше, чем остальные. Это обеспечивает максимально равномерное распределение нагрузки. + +**Вычисления на процессе:** +Каждый процесс независимо вычисляет свою **локальную сумму** (`local_sum`) на своем уникальном подотрезке. + +**Коммуникация:** +* В начале работы (этап `PreProcessing`) корневой процесс (`rank 0`) рассылает всем остальным исходные данные (`a`, `b`, `n`) с помощью коллективной операции **`MPI_Bcast`**. +* После завершения локальных вычислений все процессы участвуют в коллективной операции **`MPI_Reduce`** с операцией `MPI_SUM`. Все `local_sum` суммируются, и итоговый результат (`global_sum`) становится доступен только на корневом процессе. + +### 5. Экспериментальная установка +* **Hardware/OS:** AMD Ryzen 5 7500F, 6 ядер, 32GB RAM, Windows 10 +* **Toolchain & Environment:** + * Работа производилась внутри **Dev Container** (Ubuntu) + * Компилятор: **GCC 14.2.0** + * MPI-библиотека: **Open MPI 3.1** + * Тип сборки: **Release** +* **Data:** Для замеров производительности использовалась функция `f(x) = x*x` на отрезке `[0.0, 10.0]` с `n = 25,200,000`. + +### 6. Результаты и обсуждение +#### 6.1 Корректность +Корректность обеих реализаций (`seq` и `mpi`) проверена набором функциональных тестов, включая: +* Тесты с фиксированными эталонными значениями. +* Тесты со случайно сгенерированными границами интегрирования и числом шагов `n`. +Все тесты успешно пройдены, что подтверждает корректность вычислений. + +#### 6.2 Производительность +Замеры производительности проводились на системе, указанной в п.5. В таблице представлено время выполнения только вычислительной части (`RunImpl`). + +| Mode | Count | Time, s | Speedup | Efficiency | +| :--- | :--- | :--- | :--- | :--- | +| seq | 1 | 0.0000289 | 1.00 | N/A | +| mpi | 4 | 0.0002408 | **0.12** | **3.0%** | + +* **Speedup** = `Time(seq) / Time(mpi)` +* **Efficiency** = `Speedup / Count` + +Как видно из результатов, параллельная реализация на 4-х процессах оказалась значительно (приблизительно в 8.3 раза) **медленнее** последовательной. Ускорение составило всего 0.12, что говорит о сильной деградации производительности. + +### 7. Выводы +Реализация параллельного алгоритма интегрирования методом трапеций с использованием MPI продемонстрировала свою **неэффективность для данной задачи**. + +Основная причина — **крайне низкая вычислительная интенсивность** операции. Время, затрачиваемое на вычисление `x*x`, ничтожно мало по сравнению с **накладными расходами на MPI-коммуникации**. Операции `MPI_Bcast` и `MPI_Reduce` требуют упаковки данных, пересылки и синхронизации, что в данном случае "съедает" весь потенциальный выигрыш от параллельных вычислений и приводит к существенному замедлению. + +ДЭффективность MPI достигается только тогда, когда время, затраченное на локальные вычисления, **значительно превышает** время на коммуникацию между процессами. + +### 8. Приложение: Исходный код MPI-реализации (`RunImpl`) +```cpp +bool TrapezoidIntegrationMPI::RunImpl() { + int process_rank = 0; + int process_count = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); + MPI_Comm_size(MPI_COMM_WORLD, &process_count); + + double a = GetInput().a; + double b = GetInput().b; + int n = GetInput().n; + double h = (b - a) / n; + + const int base_n = n / process_count; // целое часть от деления числа разбиений на число процессов + const int remainder = n % process_count; // остаток от деления числа разбиений на число процессов + + const int local_n = base_n + (process_rank < remainder ? 1 : 0); // количество разбиений (трапеций) на один процесс + + int start_index = 0; + if (process_rank < remainder) { + start_index = process_rank * (base_n + 1); + } else { + start_index = (remainder * (base_n + 1)) + ((process_rank - remainder) * base_n); + } + + double local_a = a + (start_index * h); // начало отрезка для текущего процесса + + // локальные вычисления + double local_sum = 0.0; + if (local_n > 0) { + local_sum = (Func(local_a) + Func(local_a + (local_n * h))) / 2.0; + } + + for (int i = 1; i < local_n; ++i) { + local_sum += Func(local_a + (i * h)); + } + + // агрегация + double global_sum = 0.0; + MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + if (process_rank == 0) { + GetOutput() = global_sum * h; + } + + return true; +} \ No newline at end of file diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/seq/include/trapezoid_integration_sequential.hpp b/tasks/kutergin_v_trapezoid_method_of_integration/seq/include/trapezoid_integration_sequential.hpp new file mode 100644 index 0000000000..333722aaef --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/seq/include/trapezoid_integration_sequential.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "../../common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kutergin_v_trapezoid_seq { + +double Func(double x); + +class TrapezoidIntegrationSequential + : public BaseTask // наследник BaseTask (псевдоним для ppc::task::Task) +{ + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TrapezoidIntegrationSequential( + const InType &in); // конструктор принимает InType (псевдоним для структуры InputData) + + protected: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace kutergin_v_trapezoid_seq diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/seq/src/trapezoid_integration_sequential.cpp b/tasks/kutergin_v_trapezoid_method_of_integration/seq/src/trapezoid_integration_sequential.cpp new file mode 100644 index 0000000000..614743e7e3 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/seq/src/trapezoid_integration_sequential.cpp @@ -0,0 +1,48 @@ +#include "../include/trapezoid_integration_sequential.hpp" + +#include "../../common/include/common.hpp" + +namespace kutergin_v_trapezoid_seq { + +double Func(double x) // интегрируемая функция для примера +{ + return x * x; +} + +TrapezoidIntegrationSequential::TrapezoidIntegrationSequential(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); // установка типа задачи + GetInput() = in; // сохранение входных данных + GetOutput() = 0.0; // инициализация выходных данных +} + +bool TrapezoidIntegrationSequential::ValidationImpl() { + return (GetInput().b >= GetInput().a) && + (GetInput().n > 0); // проверка b >= a (границ интегрирования) и n > 0 (число разбиений) +} + +bool TrapezoidIntegrationSequential::PreProcessingImpl() { + return true; +} + +bool TrapezoidIntegrationSequential::RunImpl() { + double a = GetInput().a; + double b = GetInput().b; + int n = GetInput().n; + + double h = (b - a) / n; + double integral_res = (Func(a) + Func(b)) / 2.0; + + for (int i = 1; i < n; ++i) { + integral_res += Func(a + (i * h)); + } + + GetOutput() = integral_res * h; + + return true; +} + +bool TrapezoidIntegrationSequential::PostProcessingImpl() { + return true; +} + +} // namespace kutergin_v_trapezoid_seq diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/settings.json b/tasks/kutergin_v_trapezoid_method_of_integration/settings.json new file mode 100644 index 0000000000..5d7b790399 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/settings.json @@ -0,0 +1,8 @@ +{ + "tasks_type": "processes", + "tasks": + { + "seq": "enabled", + "mpi": "enabled" + } +} \ No newline at end of file diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/tests/functional/main.cpp b/tasks/kutergin_v_trapezoid_method_of_integration/tests/functional/main.cpp new file mode 100644 index 0000000000..1b11ff9913 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/tests/functional/main.cpp @@ -0,0 +1,155 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../common/include/common.hpp" +#include "../../mpi/include/trapezoid_integration_mpi.hpp" +#include "../../seq/include/trapezoid_integration_sequential.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace kutergin_v_trapezoid_seq { + +TEST(KuterginTrapezoidFunc, SeqVersionWorks) { + ASSERT_DOUBLE_EQ(kutergin_v_trapezoid_seq::Func(0.0), 0.0); + ASSERT_DOUBLE_EQ(kutergin_v_trapezoid_seq::Func(2.0), 4.0); + ASSERT_DOUBLE_EQ(kutergin_v_trapezoid_seq::Func(-3.0), 9.0); +} + +TEST(KuterginTrapezoidFunc, MpiVersionWorks) { + ASSERT_DOUBLE_EQ(kutergin_v_trapezoid_mpi::Func(0.0), 0.0); + ASSERT_DOUBLE_EQ(kutergin_v_trapezoid_mpi::Func(2.0), 4.0); +} + +TEST(KuterginTrapezoidValidation, SeqFailsOnInvalidBounds) { + InType invalid_input{1.0, 0.0, 100}; + TrapezoidIntegrationSequential task(invalid_input); + EXPECT_FALSE(task.Validation()); + + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +TEST(KuterginTrapezoidValidation, SeqFailsOnZeroSteps) { + InType invalid_input{0.0, 1.0, 0}; + TrapezoidIntegrationSequential task(invalid_input); + EXPECT_FALSE(task.Validation()); + + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +TEST(KuterginTrapezoidValidation, SeqFailsOnNegativeSteps) { + InType invalid_input{0.0, 1.0, -100}; + TrapezoidIntegrationSequential task(invalid_input); + EXPECT_FALSE(task.Validation()); + + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +TEST(KuterginTrapezoidValidation, MPIFailsOnNegativeSteps) { + InType invalid_input{0.0, 1.0, -100}; + kutergin_v_trapezoid_mpi::TrapezoidIntegrationMPI task(invalid_input); + EXPECT_FALSE(task.Validation()); + + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +class KuterginVRunFuncTestsSEQ + : public ppc::util::BaseRunFuncTests // наследник ppc::util::BaseRunFuncTests +{ + public: + static std::string PrintTestParam(const TestType &test_param) // вывод имен тестов + { + return std::get<2>(test_param); + } + + protected: + // подготовка входных данных перед каждым тестом + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>( + GetParam()); // получение параметров для текущего теста + input_data_ = std::get<0>(params); // извлечение входных данных + expected_output_ = std::get<1>(params); // извлечение ожидаемого результата + } + + // проверка результата после выполнения задачи + bool CheckTestOutputData(OutType &output_data) final { + int process_rank = 0; + if (ppc::util::IsUnderMpirun()) { + MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); + } + if (process_rank == 0) // проверку выполняет только нулевой процесс + { + std::cout << "-> RANK 0 EXPECTED: " << expected_output_ << '\n'; + std::cout << "-> RANK 0 ACTUAL: " << output_data << '\n'; + return std::abs(output_data - expected_output_) < 1e-6; + } + return true; // у всех остальных процессов все в порядке + } + + // возврат подготовленных входных данных для задачи + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; + OutType expected_output_{}; +}; + +namespace // анонимное пространство имен +{ + +TEST_P(KuterginVRunFuncTestsSEQ, TrapezoidTest) // параметризованный тест +{ + ExecuteTest(GetParam()); +} + +// массив с наборами тестовых данных +const std::array kTestCases = { + // Успешные тесты + std::make_tuple(InputData{.a = 0.0, .b = 3.0, .n = 10000}, 9.0, "f_x_squared_0_to_3_n_10000"), + std::make_tuple(InputData{.a = -1.0, .b = 1.0, .n = 20000}, 0.666666, "f_x_squared_neg1_to_1_n_20000"), + std::make_tuple(InputData{.a = 5.0, .b = 10.0, .n = 30000}, 291.666666, "f_x_squared_5_to_10_n_30000"), + + // Тесты на крайние случаи + std::make_tuple(InputData{.a = 0.0, .b = 0.0, .n = 1000}, 0.0, "edge_zero_length_interval"), + std::make_tuple(InputData{.a = 99.999, .b = 100.0, .n = 1000}, 9.9999, "edge_very_small_interval"), + std::make_tuple(InputData{.a = 0.0, .b = 1.0, .n = 1}, 0.5, "edge_single_trapezoid"), + + // Тесты с "неудобным" n для проверки распределения остатка + std::make_tuple(InputData{.a = 0.0, .b = 1.0, .n = 1001}, 0.333333, "remainder_check_n_is_odd"), + std::make_tuple(InputData{.a = 0.0, .b = 2.0, .n = 50}, 2.6672, "remainder_check_small_n"), + std::make_tuple(InputData{.a = 0.0, .b = 3.0, .n = 99}, 9.000459, "another_remainder_check")}; + +// используем фреймворк для подготовки задач к запуску +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask( + kTestCases, PPC_SETTINGS_kutergin_v_trapezoid_method_of_integration), + ppc::util::AddFuncTask( + kTestCases, PPC_SETTINGS_kutergin_v_trapezoid_method_of_integration)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kTestName = KuterginVRunFuncTestsSEQ::PrintFuncTestName; + +// "регистрация" набора тестов и параметров в GTest +// NOLINTNEXTLINE(modernize-type-traits, cppcoreguidelines-avoid-non-const-global-variables) +INSTANTIATE_TEST_SUITE_P(TrapezoidIntegrationSEQ, KuterginVRunFuncTestsSEQ, kGtestValues, kTestName); +} // namespace + +} // namespace kutergin_v_trapezoid_seq diff --git a/tasks/kutergin_v_trapezoid_method_of_integration/tests/performance/main.cpp b/tasks/kutergin_v_trapezoid_method_of_integration/tests/performance/main.cpp new file mode 100644 index 0000000000..b1efa5ba18 --- /dev/null +++ b/tasks/kutergin_v_trapezoid_method_of_integration/tests/performance/main.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include +#include +#include + +#include "../../common/include/common.hpp" +#include "../../mpi/include/trapezoid_integration_mpi.hpp" +#include "../../seq/include/trapezoid_integration_sequential.hpp" +#include "util/include/perf_test_util.hpp" +#include "util/include/util.hpp" + +namespace kutergin_v_trapezoid_seq { + +class KuterginVRunPerfTests + : public ppc::util::BaseRunPerfTests // наследник ppc::util::BaseRunPerfTests +{ + protected: + // подготовка входных данных перед каждым тестом + void SetUp() override { + const auto path_to_input = + ppc::util::GetAbsoluteTaskPath(PPC_ID_kutergin_v_trapezoid_method_of_integration, + "input.txt"); // получение абсолютного пути к файлу с данными + + std::ifstream file(path_to_input); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + path_to_input); + } + file >> input_data_.a >> input_data_.b >> input_data_.n; + file.close(); + } + + // проверка результата после выполнения задачи + bool CheckTestOutputData(OutType &output_data) final { + int process_rank = 0; + if (ppc::util::IsUnderMpirun()) { + MPI_Comm_rank(MPI_COMM_WORLD, &process_rank); + } + if (process_rank == 0) // проверку выполняет только нулевой процесс + { + const double expected = + (input_data_.b * input_data_.b * input_data_.b / 3.0) - (input_data_.a * input_data_.a * input_data_.a / 3.0); + return std::abs(output_data - expected) < 1e-5; + } + return true; // у всех остальных процессов все в порядке + } + + // возврат подготовленных входных данных для задачи + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; +}; + +namespace { + +TEST_P(KuterginVRunPerfTests, PerfTest) // параметризованный тест +{ + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_kutergin_v_trapezoid_method_of_integration); + +// NOLINTNEXTLINE(modernize-type-traits, cppcoreguidelines-avoid-non-const-global-variables) +INSTANTIATE_TEST_SUITE_P(TrapezoidPerf, KuterginVRunPerfTests, ppc::util::TupleToGTestValues(kAllPerfTasks), + KuterginVRunPerfTests::CustomPerfTestName); + +} // namespace + +} // namespace kutergin_v_trapezoid_seq