Skip to content

Commit f1de516

Browse files
authored
Кутузов Иван. Технология SEQ|MPI. Вычисление многомерных интегралов с использованием многошаговой схемы (метод Симпсона). Вариант 9 (#293)
## Описание - **Задача**: Вычисление многомерных интегралов с использованием многошаговой схемы (метод Симпсона). - **Вариант**: 9 - **Технология**: SEQ, MPI - **Описание:** * *SEQ*: используется двойной цикл по координатам сетки. В каждой точке вычисляется значение функции, которое добавляется к общей сумме с учётом весов численной схемы. Итоговая сумма умножается на $\dfrac{\Delta x \cdot \Delta y}{9}$, где $\Delta x$ и $\Delta y$ - размеры ячейки сетки по соответствующим осям. * *MPI*: Входные данные рассылаются с процесса с рангом 0 на остальные процессы при помощь `MPI_Bcast`. Параллелизм применяется к внешнему циклу обхода сетки. Каждый процесс вычисляет начало и конец своего участка индексов по следующему принципу: каждому из $n$ процессов раздаются равные интервалы индексов. Остаток $r$, $r < n$ раздаётся первым $r$ процессам поровну (то есть по одному доп. индексу). Реализация выполнена так, что индексы, выданные на обработку одному процессу, идут подряд. Затем процессы выполняют свою часть вычислений аналогично последовательному алгоритму, суммируя результат в `local_sum` - локальную сумму. Далее при помощи вызова `MPI_Reduce` значения из `local_sum` со всех процессов суммируются в переменной `sum` на процессе с рангом 0. Затем результат домножается на $\dfrac{\Delta x \cdot \Delta y}{9}$ и рассылается на остальные процессы для прохождения проверок корректности. --- ## Чек-лист - [x] **Статус CI**: Все CI-задачи (сборка, тесты, генерация отчёта) успешно проходят на моей ветке в моем форке - [x] **Директория и именование задачи**: Я создал директорию с именем `<фамилия>_<первая_буква_имени>_<короткое_название_задачи>` - [x] **Полное описание задачи**: Я предоставил полное описание задачи в теле pull request - [x] **clang-format**: Мои изменения успешно проходят `clang-format` локально в моем форке (нет ошибок форматирования) - [x] **clang-tidy**: Мои изменения успешно проходят `clang-tidy` локально в моем форке (нет предупреждений/ошибок) - [x] **Функциональные тесты**: Все функциональные тесты успешно проходят локально на моей машине - [x] **Тесты производительности**: Все тесты производительности успешно проходят локально на моей машине - [x] **Ветка**: Я работаю в ветке, названной точно так же, как директория моей задачи (например, `nesterov_a_vector_sum`), а не в `master` - [x] **Правдивое содержание**: Я подтверждаю, что все сведения, указанные в этом pull request, являются точными и достоверными
1 parent c41643a commit f1de516

11 files changed

Lines changed: 637 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#pragma once
2+
3+
#include <cmath>
4+
#include <tuple>
5+
#include <utility>
6+
7+
#include "task/include/task.hpp"
8+
9+
namespace kutuzov_i_simpson_integration {
10+
11+
// n, x_min-x_max, y_min-y_max, function_id
12+
using InType = std::tuple<int, std::pair<double, double>, std::pair<double, double>, int>;
13+
using OutType = double;
14+
// n, x_min-x_max, y_min-y_max, function_id
15+
using TestType = std::tuple<int, std::pair<double, double>, std::pair<double, double>, int>;
16+
using BaseTask = ppc::task::Task<InType, OutType>;
17+
18+
inline double FunctionPolynomial(double x, double y) {
19+
return pow(x, 3) + (0.5 * pow(x, 2)) + (3.0 * pow(y, 6)) + (15.0 * y) + 37.0;
20+
}
21+
22+
inline double FunctionTrigonometric(double x, double y) {
23+
return pow(sin(x), 5) + (1.3 * cos(0.7 * x) * sin(1.4 * y)) - atan((15.0 * x) + (7.0 * y));
24+
}
25+
26+
inline double FunctionExponents(double x, double y) {
27+
return (1.7 * exp(3.7 * x)) + (exp(3.0 * x * y) * log(pow(x + y, 2) + 1.0)) -
28+
log(pow((17.0 * x) - (8.0 * y), 4) + 0.1);
29+
}
30+
31+
inline double FunctionComplex(double x, double y) {
32+
double sum = 0.0;
33+
for (int i = 1; i <= 200; i++) {
34+
double add = (sin((0.3 * pow(x * i, 4) * pow(y, 2)) + (0.5 * cos(y / i) * pow(x, 7)) + (1.8 * pow(y, 5))));
35+
if (i % 2 == 0) {
36+
sum += add;
37+
} else {
38+
sum -= add;
39+
}
40+
}
41+
return sum;
42+
}
43+
44+
inline double CallFunction(int function_id, double x, double y) {
45+
switch (function_id) {
46+
case 1:
47+
return FunctionPolynomial(x, y);
48+
case 2:
49+
return FunctionTrigonometric(x, y);
50+
case 3:
51+
return FunctionExponents(x, y);
52+
case 4:
53+
return FunctionComplex(x, y);
54+
default:
55+
return 1.0;
56+
}
57+
}
58+
59+
} // namespace kutuzov_i_simpson_integration
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"student": {
3+
"first_name": "Иван",
4+
"last_name": "Кутузов",
5+
"middle_name": "Арсеньевич",
6+
"group_number": "3823Б1ФИ3",
7+
"task_number": "3"
8+
}
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "kutuzov_i_simpson_integration/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace kutuzov_i_simpson_integration {
7+
8+
class KutuzovISimpsonIntegrationMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit KutuzovISimpsonIntegrationMPI(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
21+
static double GetWeight(int i, int n);
22+
};
23+
24+
} // namespace kutuzov_i_simpson_integration
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include "kutuzov_i_simpson_integration/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <algorithm>
6+
#include <tuple>
7+
#include <vector>
8+
9+
#include "kutuzov_i_simpson_integration/common/include/common.hpp"
10+
11+
namespace kutuzov_i_simpson_integration {
12+
13+
KutuzovISimpsonIntegrationMPI::KutuzovISimpsonIntegrationMPI(const InType &in) {
14+
SetTypeOfTask(GetStaticTypeOfTask());
15+
GetInput() = in;
16+
GetOutput() = {};
17+
}
18+
19+
bool KutuzovISimpsonIntegrationMPI::ValidationImpl() {
20+
int n = std::get<0>(GetInput());
21+
double x_min = std::get<0>(std::get<1>(GetInput()));
22+
double x_max = std::get<1>(std::get<1>(GetInput()));
23+
double y_min = std::get<0>(std::get<2>(GetInput()));
24+
double y_max = std::get<1>(std::get<2>(GetInput()));
25+
int function_id = std::get<3>(GetInput());
26+
27+
if (x_min >= x_max) {
28+
return false;
29+
}
30+
if (y_min >= y_max) {
31+
return false;
32+
}
33+
if (n <= 0 || n % 2 != 0) {
34+
return false;
35+
}
36+
if (function_id <= 0 || function_id > 4) {
37+
return false;
38+
}
39+
return true;
40+
}
41+
42+
bool KutuzovISimpsonIntegrationMPI::PreProcessingImpl() {
43+
GetOutput() = 0.0;
44+
return true;
45+
}
46+
47+
bool KutuzovISimpsonIntegrationMPI::RunImpl() {
48+
int rank = 0;
49+
int process_count = 0;
50+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
51+
MPI_Comm_size(MPI_COMM_WORLD, &process_count);
52+
53+
int n = 0;
54+
double x_min = 0.0;
55+
double x_max = 0.0;
56+
double y_min = 0.0;
57+
double y_max = 0.0;
58+
int function_id = 0;
59+
60+
// To reduce the number of MPI_Bcast calls we can send similar data in bulk
61+
std::vector<double> data_package(4, 0.0);
62+
if (rank == 0) {
63+
n = std::get<0>(GetInput());
64+
x_min = std::get<1>(GetInput()).first;
65+
x_max = std::get<1>(GetInput()).second;
66+
y_min = std::get<2>(GetInput()).first;
67+
y_max = std::get<2>(GetInput()).second;
68+
function_id = std::get<3>(GetInput());
69+
70+
data_package = {x_min, x_max, y_min, y_max};
71+
}
72+
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
73+
MPI_Bcast(&function_id, 1, MPI_INT, 0, MPI_COMM_WORLD);
74+
MPI_Bcast(data_package.data(), 4, MPI_DOUBLE, 0, MPI_COMM_WORLD);
75+
x_min = data_package[0];
76+
x_max = data_package[1];
77+
y_min = data_package[2];
78+
y_max = data_package[3];
79+
80+
int task_per_process = (n + 1) / process_count;
81+
int tail = (n + 1) - (task_per_process * process_count);
82+
int task_start = (task_per_process * rank) + std::min(rank, tail);
83+
int task_end = task_start + task_per_process + (rank < tail ? 1 : 0);
84+
85+
double step_x = (x_max - x_min) / n;
86+
double step_y = (y_max - y_min) / n;
87+
88+
double local_sum = 0.0;
89+
for (int i = task_start; i < task_end; i++) {
90+
double x = x_min + (step_x * i);
91+
92+
for (int j = 0; j <= n; j++) {
93+
double y = y_min + (step_y * j);
94+
double a = GetWeight(i, n) * GetWeight(j, n) * CallFunction(function_id, x, y);
95+
local_sum += a;
96+
}
97+
}
98+
99+
double sum = 0.0;
100+
MPI_Reduce(&local_sum, &sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
101+
sum *= step_x * step_y / 9;
102+
MPI_Bcast(&sum, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
103+
GetOutput() = sum;
104+
105+
return true;
106+
}
107+
108+
bool KutuzovISimpsonIntegrationMPI::PostProcessingImpl() {
109+
return true;
110+
}
111+
112+
double KutuzovISimpsonIntegrationMPI::GetWeight(int i, int n) {
113+
if (i == 0 || i == n) {
114+
return 1.0;
115+
}
116+
if (i % 2 == 1) {
117+
return 4.0;
118+
}
119+
return 2.0;
120+
}
121+
122+
} // namespace kutuzov_i_simpson_integration
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Вычисление многомерных интегралов с использованием многошаговой схемы (метод Симпсона).
2+
3+
- Student: Кутузов Иван Арсеньевич, group 3823Б1ФИ3
4+
- Technology: SEQ | MPI
5+
- Variant: 9
6+
7+
## 1. Introduction
8+
9+
Мотивация: Двумерные численные интегралы широко применяются в физических и инженерных задачах. Даже для двумерного случая при высокой точности вычисления могут быть вычислительно затратными, поэтому использование параллельных методов позволяет существенно сократить время расчёта.
10+
11+
Проблема: Последовательные алгоритмы численного интегрирования для двумерных интегралов плохо масштабируются при увеличении числа разбиений сетки. Требуется эффективная параллельная реализация многошаговой схемы.
12+
13+
Ожидаемый результат: Получение корректного и быстрого параллельного алгоритма вычисления двумерного интеграла с ускорением относительно последовательной версии.
14+
15+
## 2. Problem Statement
16+
17+
$$
18+
I = \int_{a}^{b} \int_{c}^{d} f(x, y) , dx , dy.
19+
$$
20+
21+
Функция $f(x, y)$ считается непрерывной на области интегрирования. Для численного решения используется многошаговая схема с равномерной сеткой по осям $x$ и $y$.
22+
23+
Входные данные:
24+
* $n$ - число ячеек сетки по одной оси (общая сетка $n \times n$ ячеек);
25+
* $x_{min} - x_{max}$ - начало и конец области интегрирования по оси $x$;
26+
* $y_{min} - y_{max}$ - начало и конец области интегрирования по оси $y$;
27+
* Функция $f(x)$;
28+
29+
В качестве функции подаётся `function_id`, определяющий какую из 4-х заготовленных функций использовать.
30+
31+
Результатом является приближённое значение определённого интеграла $I$.
32+
33+
34+
## 3. Baseline Algorithm (Sequential)
35+
SEQ реализация использует двойной цикл по координатам сетки. В каждой точке вычисляется значение функции, которое добавляется к общей сумме с учётом весов численной схемы. Итоговая сумма умножается на $\dfrac{\Delta x \cdot \Delta y}{9}$, где $\Delta x$ и $\Delta y$ - размеры ячейки сетки по соответствующим осям.
36+
37+
## 4. Parallelization Scheme
38+
MPI реализация выполняется в 3 основных этапа:
39+
1. **Рассылка данных:** входные данные рассылаются с процесса с рангом 0 на остальные процессы при помощь `MPI_Bcast`.
40+
2. **Параллельное решение:** параллелизм применяется к внешнему циклу обхода сетки. Каждый процесс вычисляет начало и конец своего участка индексов по следующему принципу: каждому из $n$ процессов раздаются равные интервалы индексов. Остаток $r$, $r < n$ раздаётся первым $r$ процессам поровну (то есть по одному доп. индексу). Реализация выполнена так, что индексы, выданные на обработку одному процессу, идут подряд. Затем процессы выполняют свою часть вычислений аналогично последовательному алгоритму, суммируя результат в `local_sum` - локальную сумму.
41+
3. **Сбор результатов:** при помощи вызова `MPI_Reduce` значения из `local_sum` со всех процессов суммируются в переменной `sum` на процессе с рангом 0. Затем результат домножается на $\dfrac{\Delta x \cdot \Delta y}{9}$ и рассылается на остальные процессы для прохождения проверок корректности.
42+
43+
## 5. Experimental Setup
44+
* Hardware/OS: 12th gen Intel(R) Core(TM) i5-12500H, 12 ядер, 32 GB RAM, Windows 11 x64, laptop;
45+
* Toolchain: compiler, version, build type (Release/RelWithDebInfo)
46+
* Cmake 3.28.3
47+
* Компилятор: g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
48+
* Использовался Docker-контейнер.
49+
* Режим сборки: Release.
50+
51+
## 6. Results and Discussion
52+
### 6.1 Correctness
53+
Корректность проверена при помощи модульного тестирования, реализованного с использованием библиотеки GoogleTest с значением $n$, на различных размерах области интегрирования и на различных функциях.
54+
55+
### 6.2 Performance
56+
57+
Измерение производительности происводилоось на задаче подсчёта интеграла функции
58+
```c++
59+
double f(double x, double y) {
60+
double sum = 0.0;
61+
for (int i = 1; i <= 200; i++) {
62+
double add = (sin(0.3 * pow(x * i, 4) * pow(y, 2) + 0.5 * cos(y / i) * pow(x, 7) + 1.8 * pow(y, 5)));
63+
if (i % 2 == 0) {
64+
sum += add;
65+
} else {
66+
sum -= add;
67+
}
68+
}
69+
return sum;
70+
}
71+
```
72+
на области интегрирования $x_{min} = y_{min} = -10, \ \ \ x_{max} = y_{max} = 10$, при $n = 5$.
73+
74+
| Mode | Process count | Time, s | Speedup | Efficiency |
75+
| - | - | - | - | - |
76+
| seq | 1 | 8.948 | 1.000 | N/A |
77+
| mpi | 2 | 4.521 | 1.979 | 98.9% |
78+
| mpi | 3 | 3.099 | 2.887 | 96.2% |
79+
| mpi | 4 | 2.355 | 3.799 | 94.9% |
80+
| mpi | 5 | 2.213 | 4.043 | 80.8% |
81+
| mpi | 6 | 1.949 | 4.591 | 76.5% |
82+
83+
С ростом числа процессов от одного до четырёх наблюдается ускорение близкое к идеальному. При большем увеличении числа процессов накладные расходы на рассылку и сбор данных перевешивают, и эффективность параллелизма снижается.
84+
85+
86+
## 7. Conclusions
87+
В работе рассмотрено вычисление двумерного интеграла с использованием многошаговой численной схемы. Параллельный алгоритм позволяет значительно сократить время вычислений при сохранении корректности результатов.
88+
89+
## 8. References
90+
* "Параллельное программирование для кластерных систем" ННГУ им. Лобачевского, ИИТММ.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "kutuzov_i_simpson_integration/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace kutuzov_i_simpson_integration {
7+
8+
class KutuzovISimpsonIntegrationSEQ : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kSEQ;
12+
}
13+
explicit KutuzovISimpsonIntegrationSEQ(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
21+
static double GetWeight(int i, int n);
22+
};
23+
24+
} // namespace kutuzov_i_simpson_integration

0 commit comments

Comments
 (0)