diff --git a/tasks/eremin_v_rectangle_method/common/include/common.hpp b/tasks/eremin_v_rectangle_method/common/include/common.hpp new file mode 100644 index 0000000000..8d3a46c063 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace eremin_v_rectangle_method { + +using InType = std::tuple>; +using OutType = double; +using TestType = std::tuple, std::function>; +using BaseTask = ppc::task::Task; + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/data/int.png b/tasks/eremin_v_rectangle_method/data/int.png new file mode 100644 index 0000000000..6285cb5c64 Binary files /dev/null and b/tasks/eremin_v_rectangle_method/data/int.png differ diff --git a/tasks/eremin_v_rectangle_method/data/square.png b/tasks/eremin_v_rectangle_method/data/square.png new file mode 100644 index 0000000000..7c3f3f1137 Binary files /dev/null and b/tasks/eremin_v_rectangle_method/data/square.png differ diff --git a/tasks/eremin_v_rectangle_method/info.json b/tasks/eremin_v_rectangle_method/info.json new file mode 100644 index 0000000000..e9cfa820d8 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Василий", + "last_name": "Еремин", + "middle_name": "Евгеньевич", + "group_number": "3823Б1ФИ2", + "task_number": "1" + } +} diff --git a/tasks/eremin_v_rectangle_method/mpi/include/ops_mpi.hpp b/tasks/eremin_v_rectangle_method/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..7bcb818fa2 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "eremin_v_rectangle_method/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace eremin_v_rectangle_method { + +class EreminVRectangleMethodMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit EreminVRectangleMethodMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/mpi/src/ops_mpi.cpp b/tasks/eremin_v_rectangle_method/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..62932b5ad8 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/mpi/src/ops_mpi.cpp @@ -0,0 +1,70 @@ +#include "eremin_v_rectangle_method/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "eremin_v_rectangle_method/common/include/common.hpp" + +namespace eremin_v_rectangle_method { + +EreminVRectangleMethodMPI::EreminVRectangleMethodMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool EreminVRectangleMethodMPI::ValidationImpl() { + auto &input = GetInput(); + return (std::get<0>(input) < std::get<1>(input)) && (std::get<2>(input) > 0) && (std::get<2>(input) <= 100000000) && + (std::get<0>(input) >= -1e9) && (std::get<0>(input) <= 1e9) && (std::get<1>(input) >= -1e9) && + (std::get<1>(input) <= 1e9) && (GetOutput() == 0); +} + +bool EreminVRectangleMethodMPI::PreProcessingImpl() { + return true; +} + +bool EreminVRectangleMethodMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + double lower_bound = 0.0; + double upper_bound = 0.0; + int steps = 0; + double result = 0.0; + + if (rank == 0) { + auto &input = GetInput(); + lower_bound = std::get<0>(input); + upper_bound = std::get<1>(input); + steps = std::get<2>(input); + } + MPI_Bcast(&lower_bound, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&upper_bound, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(&steps, 1, MPI_INT, 0, MPI_COMM_WORLD); + + const auto in_function = std::get<3>(GetInput()); + + double step_size = (upper_bound - lower_bound) / static_cast(steps); + double local_result = 0.0; + + for (int i = rank; i < steps; i += size) { + local_result += in_function(lower_bound + ((static_cast(i) + 0.5) * step_size)); + } + + local_result *= step_size; + + MPI_Allreduce(&local_result, &result, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); + GetOutput() = result; + return true; +} + +bool EreminVRectangleMethodMPI::PostProcessingImpl() { + return true; +} + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/report.md b/tasks/eremin_v_rectangle_method/report.md new file mode 100644 index 0000000000..8b2635b022 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/report.md @@ -0,0 +1,157 @@ +# Интегрирование – метод прямоугольников + +- **Студент:** Еремин Василий Егвеньевич, группа 3823Б1ФИ2 +- **Технология:** SEQ | MPI +- **Вариант:** №19 + +## 1. Введение + +**Цель работы:** Реализация и сравнительный анализ последовательной и параллельной версий алгоритма численного интегрирования методом прямоугольников. + +**Задачи:** +1. Реализовать последовательную версию алгоритма метода прямоугольников +2. Реализовать параллельную версию с использованием технологии MPI +3. Провести сравнительный анализ производительности и эффективности обеих реализаций + +## 2. Постановка задачи + +**Задача**: Требуется вычислить значение определенного интеграла методом прямоугольников + +![Интеграл](data/int.png) + +где: +- `f(x)` — интегрируемая функция +- `a`, `b` — нижний и верхний пределы интегрирования соответственно + +**Описание метода решения:** Из трех методов (левых, правых и средних прямоугольников) был выбран *метод средних прямоугольников*. + +Область интегрирования разбивается на `n` равных отрезков. Сложная площадь под кривой разбивается на множество простых прямоугольников - на каждом отрезке строится прямоугольник высотой, равной значению функции в середине отрезка. Общая площадь вычисляется как сумма площадей всех этих прямоугольников. + +![Метод средних прямоугольников](data/square.png) + +**Входные данные:** +- `a` — нижний предел интегрирования (вещественное число) +- `b` — верхний предел интегрирования (вещественное число) +- `n` — количество отрезков разбиения (целое число) +- `f(x)` — интегрируемая функция + +**Выходные данные:** +- Приближенное значение интеграла (вещественное число) + +**Ограничения:** +- `-1 000 000 000 ≤ a < b ≤ 1 000 000 000` +- `0 < n ≤ 100 000 000` + + +## 3. Описание алгоритма (последовательного) +**Алгоритм последовательного вычисления:** +1. **Найдем шаг интегрирования:** `h = (b - a) / n` +2. **Вычисляем сумму значений функции:** через цикл находим площадь каждого прямоугольника по формуле `f(a + (i + 0.5) * h)` +3. **Умножаем сумму на шаг:** `result = h * sum` + +**Реализация на C++:** + +```cpp +double rectangle_method(double a, double b, double n) { + double result = 0.0; + double h = (b - a) / n; + + for (int i = 0; i < n; i++) { + result += f(a + ((i + 0.5) * h)); + } + + result *= h; + return result; +} +``` + +## 4. Схема распараллеливания (MPI) +**Алгоритм параллельного вычисления:** +Все тоже самое, но +1. **Рассылка праметров:** Процесс с рангом 0 получает и рассылает параметры `a`, `b`, `n` всем процессам для правильной синхронизации. +2. **Каждый процесс вычисляет шаг интегрирования:** `h = (b - a) / n` +3. **Локальное вычисление:** Каждый процесс независимо вычисляет свою частичную сумму по формуле `f(a + (i + 0.5) * h)` +4. **Сбор результатов:** Все процессы обмениваются результатами и получают общую сумму +5. **Финальный расчет:** `result = h * sum` + +**Принцип разделения отрезков:** + +```cpp +for (int i = rank; i < n; i += size) { + local_result += f(a + ((i + 0.5) * step_size)); + } +``` + +**Схема распределения вычислений:** +- Процесс 0: обрабатывает отрезки 0, size, 2×size, ... +- Процесс 1: обрабатывает отрезки 1, size+1, 2×size+1, ... +- Процесс k: обрабатывает отрезки k, size+k, 2×size+k, ... + +## 5. Детали реализации +**Дополнительные функции в тестах:** +- `InFunction(x)` - подынтегральная функция (что интегрируем) +- `Function(x)` - первообразная (аналитическое решение для проверки) + +## 6. Экспериментальная среда +- Hardware/OS: Apple M1 Pro, 6 ядер производительности и 2 эффективности, 16 ГБ, Ubuntu 24.04.2 (DevContainer) +- Toolchain: GCC 13.3.0, C++20, CMake 3.28.3, build type Release +- Environment: PPC_NUM_PROC = 8 + +## 7. Результаты и обсуждение + +### 7.1 Проверка корректности + +**Методы верификации корректности:** + +1. **Сравнение с аналитическим решением:** + - Используется функция `Function(x)` - первообразная от `InFunction(x)` + - Ожидаемый результат вычисляется по формуле Ньютона-Лейбница: `Function(b) - Function(a)` + - Позволяет получить эталонное значение для сравнения + +2. **Автоматизированное тестирование:** + - Реализованы юнит-тесты для 5 различных тестовых случаев + - Тестируются разные интервалы и количество разбиений + - Проверяются как последовательная, так и MPI версии + +3. **Допустимая погрешность:** + ```cpp + double tolerance = std::max(std::abs(expected_result_) * 0.01, 1e-8); + ``` + - Относительная погрешность: 1% + - Абсолютная погрешность: не менее 1e-8 + - Учитывает масштаб ожидаемого результата + +**Тестовые случаи:** + - **Разные интервалы:** [0.0, 1.0], [0.0, 2.0], [-10.0, 100.0], [1.0, 4.0], [-2.0, 3.0] + - **Разное количество разбиений:** от 1,000 до 100,000 + - **Разные тестовые функции:** + - `x^2` + - `sin(x)` + - `e^x` + - `x * e^x` + - `x^3 - 4x + 1` + - `e^x * (sin(x) + cos(x)) + 3x^2 * cos(x) - x^3 * sin(x)` + +**Результат:** Все тесты проходят успешно, что подтверждает корректность обеих реализаций. + +### 7.2 Производительность +- **Сложная тестовая функция:** `e^x * (x^2 * sin(x) + 2x * sin(x) + x^2 * cos(x)) + 4x^3 * cos(2x) - 2x^4 * sin(2x)` +- **Количество разбиений** `100 000 000` + +**Полученные результаты:** + +| **Режим** | **Количество процессов** | **Время, с** | **Speedup** | **Efficiency** | +|-----------|--------------------------|--------------|-------------|----------------| +| SEQ | 1 | 2.182 | 1.00 | N/A | +| MPI | 2 | 1.049 | 2.08 | 104% | +| MPI | 4 | 0.566 | 3.92 | 98% | +| MPI | 6 | 0.378 | 5.77 | 96% | + + +## 8. Заключение +В рамках данной работы были успешно реализованы последовательная и параллельная версии алгоритма интегрирования методом прямоугольников. Проведенные эксперименты подтвердили значительное ускорение MPI-реализации и корректность работы обоих вариантов алгоритма. + +## 9. Источники +1. [Wikipedia](https://ru.wikipedia.org/wiki/Метод_прямоугольников) +2. [Презентация по курсу](https://learning-process.github.io/parallel_programming_slides/slides/01-intro.pdf) + diff --git a/tasks/eremin_v_rectangle_method/seq/include/ops_seq.hpp b/tasks/eremin_v_rectangle_method/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..02c1aa4f2e --- /dev/null +++ b/tasks/eremin_v_rectangle_method/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "eremin_v_rectangle_method/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace eremin_v_rectangle_method { + +class EreminVRectangleMethodSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit EreminVRectangleMethodSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/seq/src/ops_seq.cpp b/tasks/eremin_v_rectangle_method/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..4808e63d5a --- /dev/null +++ b/tasks/eremin_v_rectangle_method/seq/src/ops_seq.cpp @@ -0,0 +1,49 @@ +#include "eremin_v_rectangle_method/seq/include/ops_seq.hpp" + +#include +#include + +#include "eremin_v_rectangle_method/common/include/common.hpp" + +namespace eremin_v_rectangle_method { + +EreminVRectangleMethodSEQ::EreminVRectangleMethodSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool EreminVRectangleMethodSEQ::ValidationImpl() { + auto &input = GetInput(); + return (std::get<0>(input) < std::get<1>(input)) && (std::get<2>(input) > 0) && (std::get<2>(input) <= 100000000) && + (std::get<0>(input) >= -1e9) && (std::get<0>(input) <= 1e9) && (std::get<1>(input) >= -1e9) && + (std::get<1>(input) <= 1e9) && (GetOutput() == 0); +} + +bool EreminVRectangleMethodSEQ::PreProcessingImpl() { + return true; +} + +bool EreminVRectangleMethodSEQ::RunImpl() { + auto &input = GetInput(); + const double lower_bound = std::get<0>(input); + const double upper_bound = std::get<1>(input); + const int steps = std::get<2>(input); + const auto &in_function = std::get<3>(input); + double step_size = (upper_bound - lower_bound) / steps; + double result = 0.0; + + for (int i = 0; i < steps; i++) { + result += in_function(lower_bound + ((static_cast(i) + 0.5) * step_size)); + } + + result *= step_size; + GetOutput() = result; + return true; +} + +bool EreminVRectangleMethodSEQ::PostProcessingImpl() { + return true; +} + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/settings.json b/tasks/eremin_v_rectangle_method/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/eremin_v_rectangle_method/tests/.clang-tidy b/tasks/eremin_v_rectangle_method/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/eremin_v_rectangle_method/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/eremin_v_rectangle_method/tests/functional/main.cpp b/tasks/eremin_v_rectangle_method/tests/functional/main.cpp new file mode 100644 index 0000000000..5619e18601 --- /dev/null +++ b/tasks/eremin_v_rectangle_method/tests/functional/main.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "eremin_v_rectangle_method/common/include/common.hpp" +#include "eremin_v_rectangle_method/mpi/include/ops_mpi.hpp" +#include "eremin_v_rectangle_method/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace eremin_v_rectangle_method { + +class EreminVRunFuncTestsRectangleMethod : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + std::string result = "test_" + std::to_string(std::get<0>(test_param)) + "_from_" + + std::to_string(std::get<1>(test_param)) + "_to_" + std::to_string(std::get<2>(test_param)) + + "_steps_" + std::to_string(std::get<3>(test_param)); + std::ranges::replace(result, '.', '_'); + std::ranges::replace(result, '-', 'm'); + return result; + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + + double lower_bound = std::get<1>(params); + double upper_bound = std::get<2>(params); + int steps = std::get<3>(params); + auto in_function = std::get<4>(params); + auto function = std::get<5>(params); + input_data_ = std::make_tuple(lower_bound, upper_bound, steps, in_function); + + expected_result_ = function(upper_bound) - function(lower_bound); + } + + bool CheckTestOutputData(OutType &output_data) final { + double tolerance = std::max(std::abs(expected_result_) * 0.01, 1e-8); + return std::abs(output_data - expected_result_) <= tolerance; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_result_{}; + + static double Function(double x) { + return (std::exp(x) * std::sin(x)) + (x * x * x * std::cos(x)); + } + static double InFunction(double x) { + return (std::exp(x) * (std::sin(x) + std::cos(x))) + (3 * x * x * std::cos(x)) - (x * x * x * std::sin(x)); + } +}; + +namespace { + +TEST_P(EreminVRunFuncTestsRectangleMethod, RectangleMethod) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(1, 0.0, 1.0, 1000, [](double x) { return x * x; }, [](double x) { return x * x * x / 3; }), + std::make_tuple(2, 0.0, 2.0, 10000, [](double x) { return std::sin(x); }, [](double x) { return -std::cos(x); }), + + std::make_tuple(3, 1.0, 4.0, 50000, [](double x) { return std::exp(x); }, [](double x) { return std::exp(x); }), + std::make_tuple(4, -100.0, 400.0, 70000, [](double x) { return x * std::exp(x); }, + [](double x) { return std::exp(x) * (x - 1); }), + std::make_tuple(5, -2.0, 3.0, 80000, [](double x) { return (x * x * x) - (4 * x) + 1; }, + [](double x) { return (x * x * x * x / 4.0) - (2 * x * x) + x; }), + std::make_tuple(6, -10.0, 100.0, 100000, [](double x) { + return (std::exp(x) * (std::sin(x) + std::cos(x))) + (3 * x * x * std::cos(x)) - (x * x * x * std::sin(x)); +}, [](double x) { return (std::exp(x) * std::sin(x)) + (x * x * x * std::cos(x)); })}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_eremin_v_rectangle_method), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_eremin_v_rectangle_method)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = EreminVRunFuncTestsRectangleMethod::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(RectangleMethodTests, EreminVRunFuncTestsRectangleMethod, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace eremin_v_rectangle_method diff --git a/tasks/eremin_v_rectangle_method/tests/performance/main.cpp b/tasks/eremin_v_rectangle_method/tests/performance/main.cpp new file mode 100644 index 0000000000..2a0e4e320a --- /dev/null +++ b/tasks/eremin_v_rectangle_method/tests/performance/main.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include +#include + +#include "eremin_v_rectangle_method/common/include/common.hpp" +#include "eremin_v_rectangle_method/mpi/include/ops_mpi.hpp" +#include "eremin_v_rectangle_method/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace eremin_v_rectangle_method { + +class EreminVRunPerfTestsRectangleMethod : public ppc::util::BaseRunPerfTests { + void SetUp() override { + input_data_ = std::make_tuple(0.0, 10.0, 100000000, InFunction); + expected_result_ = Function(10.0) - Function(0.0); + } + + bool CheckTestOutputData(OutType &output_data) final { + double tolerance = std::max(std::abs(expected_result_) * 0.01, 1e-8); + return std::abs(output_data - expected_result_) <= tolerance; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; + OutType expected_result_{}; + + static double Function(double x) { + return (x * x * std::exp(x) * std::sin(x)) + (x * x * x * x * std::cos(2 * x)); + } + static double InFunction(double x) { + double term1 = std::exp(x) * (x * x * std::sin(x) + 2 * x * std::sin(x) + x * x * std::cos(x)); + double term2 = (4 * x * x * x * std::cos(2 * x)) - (2 * x * x * x * x * std::sin(2 * x)); + return term1 + term2; + } +}; + +TEST_P(EreminVRunPerfTestsRectangleMethod, RunPerfModesRectangleMethod) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_eremin_v_rectangle_method); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = EreminVRunPerfTestsRectangleMethod::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTestsRectangleMethod, EreminVRunPerfTestsRectangleMethod, kGtestValues, kPerfTestName); + +} // namespace eremin_v_rectangle_method