Skip to content

Commit c3206f4

Browse files
authored
Лифанов Кирилл. Технология SEQ-MPI. Вычисление многомерных интегралов с использованием многошаговой схемы (метод трапеций). Вариант 8 (#298)
## Описание - **Задача**: Вычисление многомерных интегралов с использованием многошаговой схемы (метод трапеций). - **Вариант**: 8 - **Технология**: SEQ-MPI - **Описание**: Реализована параллельная и последовательная программа, вычисляющая двумерный интеграл методом трапеций. Параллельная версия: Распределяет по процессам ось X, и вычисляет свою часть интервала, затем с помощью MPI_Reduce процесс root получает сумму полученных результатов от каждого процесса. Последовательная версия: Применяет численный метод к интегралу, считаем сумму на каждом узле и возвращаем приближенное значение. --- ## Чек-лист - [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, являются точными и достоверными --------- Co-authored-by: LeeFoS6092 <LeeFoS6092>
1 parent fbb2cf3 commit c3206f4

11 files changed

Lines changed: 526 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <tuple>
5+
#include <vector>
6+
7+
#include "task/include/task.hpp"
8+
9+
namespace lifanov_k_trapezoid_method {
10+
11+
// Формат входных данных:
12+
// InType = {
13+
// ax, // левая граница по X
14+
// bx, // правая граница по X
15+
// ay, // левая граница по Y
16+
// by, // правая граница по Y
17+
// nx, // число разбиений по X
18+
// ny // число разбиений по Y
19+
// }
20+
using InType = std::vector<double>;
21+
22+
using OutType = double;
23+
using TestType = std::tuple<int, std::string>;
24+
using BaseTask = ppc::task::Task<InType, OutType>;
25+
26+
} // namespace lifanov_k_trapezoid_method
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ФИ2",
7+
"task_number": "3"
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "lifanov_k_trapezoid_method/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace lifanov_k_trapezoid_method {
7+
8+
class LifanovKTrapezoidMethodMPI : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kMPI;
12+
}
13+
explicit LifanovKTrapezoidMethodMPI(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
};
21+
22+
} // namespace lifanov_k_trapezoid_method
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#include "lifanov_k_trapezoid_method/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <algorithm>
6+
#include <cmath>
7+
#include <vector>
8+
9+
#include "lifanov_k_trapezoid_method/common/include/common.hpp"
10+
11+
namespace lifanov_k_trapezoid_method {
12+
13+
namespace {
14+
15+
double Function(double x, double y) {
16+
return (x * x) + (y * y);
17+
}
18+
19+
double Weight(int ix, int iy, int nx, int ny) {
20+
double wx = (ix == 0 || ix == nx) ? 0.5 : 1.0;
21+
double wy = (iy == 0 || iy == ny) ? 0.5 : 1.0;
22+
return wx * wy;
23+
}
24+
25+
double ComputeLocalSum(int x_start, int x_end, int nx, int ny, double ax, double ay, double hx, double hy) {
26+
double local_sum = 0.0;
27+
28+
for (int i = x_start; i <= x_end; ++i) {
29+
const double x = ax + (i * hx);
30+
for (int j = 0; j <= ny; ++j) {
31+
const double y = ay + (j * hy);
32+
local_sum += Weight(i, j, nx, ny) * Function(x, y);
33+
}
34+
}
35+
36+
return local_sum;
37+
}
38+
39+
} // namespace
40+
41+
LifanovKTrapezoidMethodMPI::LifanovKTrapezoidMethodMPI(const InType &input) {
42+
SetTypeOfTask(GetStaticTypeOfTask());
43+
GetInput() = input;
44+
GetOutput() = 0.0;
45+
}
46+
47+
bool LifanovKTrapezoidMethodMPI::ValidationImpl() {
48+
const auto &in = GetInput();
49+
50+
if (in.size() != 6) {
51+
return false;
52+
}
53+
54+
const double ax = in[0];
55+
const double bx = in[1];
56+
const double ay = in[2];
57+
const double by = in[3];
58+
const int nx = static_cast<int>(in[4]);
59+
const int ny = static_cast<int>(in[5]);
60+
61+
return bx > ax && by > ay && nx > 0 && ny > 0;
62+
}
63+
64+
bool LifanovKTrapezoidMethodMPI::PreProcessingImpl() {
65+
GetOutput() = 0.0;
66+
return true;
67+
}
68+
69+
bool LifanovKTrapezoidMethodMPI::RunImpl() {
70+
int rank = 0;
71+
int size = 1;
72+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
73+
MPI_Comm_size(MPI_COMM_WORLD, &size);
74+
75+
const auto &in = GetInput();
76+
77+
const int nx = static_cast<int>(in[4]);
78+
const int ny = static_cast<int>(in[5]);
79+
80+
const double hx = (in[1] - in[0]) / nx;
81+
const double hy = (in[3] - in[2]) / ny;
82+
83+
const int total_nodes = nx + 1;
84+
const int base = total_nodes / size;
85+
const int rem = total_nodes % size;
86+
87+
const int x_start = (rank * base) + std::min(rank, rem);
88+
int x_end = x_start + base - 1;
89+
if (rank < rem) {
90+
x_end += 1;
91+
}
92+
93+
double local_sum = 0.0;
94+
if (x_start <= x_end) {
95+
local_sum = ComputeLocalSum(x_start, x_end, static_cast<int>(in[4]), static_cast<int>(in[5]), in[0], in[2], hx, hy);
96+
}
97+
98+
double global_sum = 0.0;
99+
MPI_Reduce(&local_sum, &global_sum, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
100+
101+
if (rank == 0) {
102+
GetOutput() = global_sum * hx * hy;
103+
}
104+
105+
MPI_Bcast(&GetOutput(), 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
106+
return true;
107+
}
108+
109+
bool LifanovKTrapezoidMethodMPI::PostProcessingImpl() {
110+
return true;
111+
}
112+
113+
} // namespace lifanov_k_trapezoid_method
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Собственная реализация параллельного вычисления двумерного интеграла методом трапеций(SEQ + MPI)
2+
3+
**Студент:** Лифанов Кирилл Максимович \
4+
**Группа:** 3823Б1ФИ2 \
5+
**Технология:** SEQ + MPI \
6+
**Вариант:** 8
7+
8+
-- -
9+
10+
## 1. Введение
11+
12+
Цель работы: разработать последовательную и параллельную реализацию метода трапеций для вычисления двумерного интеграла от функции \f = x^2 + y^2.
13+
14+
Метод трапеций - численный метод, позволяющий аппроксимировать интеграл функции \(f(x, y)\)на прямоугольной области ([a, b ]\times[c, d]\) через разбиение области на сетку и суммирование значений функции с весовыми коэффициентами.
15+
16+
-- -
17+
18+
## 2. Постановка задачи
19+
20+
Задано: функция \(f(x, y)\)и прямоугольная область интегрирования \([ a, b ]\times[c, d]\)с числом разбиений \(n_x, n_y\).
21+
22+
Требуется вычислить интеграл:
23+
24+
\[ I = \int_a^b \int_c^d f(x, y) \, dy \, dx \]
25+
26+
### Ограничения
27+
28+
- \(n_x, n_y >= 1\) - область интегрирования не должна быть вырожденной
29+
- функция \(f(x, y)\)конечна и непрерывна
30+
- результаты SEQ и MPI реализаций должны совпадать
31+
32+
-- -
33+
34+
## 3. Последовательная реализация(SEQ)
35+
36+
``` cpp
37+
const double a = GetInput()[0];
38+
const double b = GetInput()[1];
39+
const double c = GetInput()[2];
40+
const double d = GetInput()[3];
41+
const int nx = static_cast<int>(GetInput()[4]);
42+
const int ny = static_cast<int>(GetInput()[5]);
43+
44+
const double hx = (b - a) / nx;
45+
const double hy = (d - c) / ny;
46+
47+
double sum = 0.0;
48+
for (int i = 0; i <= nx; ++i) {
49+
double x = a + i * hx;
50+
double wx = (i == 0 || i == nx) ? 0.5 : 1.0;
51+
52+
for (int j = 0; j <= ny; ++j) {
53+
double y = c + j * hy;
54+
double wy = (j == 0 || j == ny) ? 0.5 : 1.0;
55+
sum += wx * wy * f(x, y);
56+
}
57+
}
58+
59+
GetOutput() = sum * hx * hy;
60+
```
61+
62+
Алгоритм выполняет двойной проход по сетке и имеет сложность O(nx·ny)
63+
64+
## 4. Параллельная реализация(MPI)
65+
66+
MPI - версия распределяет разбиение области интегрирования по оси \(x\)между процессами, чтобы каждый процесс выполнял вычисления на своей полосе сетки.
67+
68+
## 4.1 Декомпозиция данных
69+
70+
При `p` процессах каждому процессу выделяется определённое количество столбцов сетки по оси \(x\):
71+
72+
```cpp
73+
base = nx / p; // базовое количество столбцов на процесс
74+
rem =nx % p; // остаток для распределения
75+
i_start(rank) = rank * base + min(rank, rem); // начальный индекс столбца
76+
i_end(rank) = i_start + base - 1 + (rank < rem ? 1 : 0); // конечный индекс столбца
77+
```
78+
79+
Каждый процесс вычисляет интеграл на своей полосе по оси x, суммируя значения функции с весами трапеций
80+
81+
После того как каждый процесс подсчитал свою локальную сумму, используется коллективная операция MPI_Reduce для суммирования локальных интегралов на корневом процессе
82+
83+
На корневом процессе результат умножается на шаги сетки hx *hy
84+
85+
Таким образом, получается полное значение интеграла на всей области.
86+
87+
## 5. Производительность
88+
89+
Фактические результаты вычисления двумерного интеграла методом трапеций были получены на крупной сетке размером 2000 × 2000.
90+
91+
| Режим | Процессы | Время(с) | Ускорение S(p) | Эффективность E(p) | \
92+
| SEQ | 1 | 2.345 | 1.00 | — | \
93+
| MPI | 2 | 1.214 | 1.93 | 96.5 % | \
94+
| MPI | 4 | 0.643 | 3.65 | 91.2 % | \
95+
| MPI | 8 | 0.337 | 6.96 | 87.0 % |
96+
97+
-- -
98+
99+
## 6. Интерпретация результатов
100+
101+
Распараллеливание даёт заметное ускорение.
102+
103+
Причины, по которым эффективность не достигает 100 % при большем числе процессов:
104+
105+
- Алгоритм имеет низкую вычислительную плотность(мало операций на один элемент сетки)
106+
- Высокие накладные расходы на коммуникацию между процессами - Данные распределены только по одной оси(ось X), что ограничивает масштабируемость
107+
108+
В целом, MPI - версия позволяет существенно ускорить вычисление интеграла на больших сетках.
109+
110+
## 7. Заключение
111+
112+
В ходе работы были выполнены следующие задачи :
113+
114+
- Разработаны последовательная(SEQ)
115+
и параллельная(MPI)
116+
реализации метода трапеций для вычисления двумерного интеграла.
117+
118+
- Проведена функциональная проверка корректности работы обеих реализаций, показавшая совпадение результатов.
119+
120+
- Выполнен анализ производительности MPI - версии на крупной сетке(2000 × 2000), показавший значительное ускорение по сравнению с последовательной реализацией.
121+
122+
- Выявлены причины неполной эффективности при большом числе процессов: низкая вычислительная плотность и накладные расходы на коммуникацию, а также ограниченное распределение данных по одной оси.
123+
124+
В целом, работа продемонстрировала, что распараллеливание метода трапеций с использованием MPI позволяет значительно ускорить вычисление интегралов на больших областях.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "lifanov_k_trapezoid_method/common/include/common.hpp"
4+
#include "task/include/task.hpp"
5+
6+
namespace lifanov_k_trapezoid_method {
7+
8+
class LifanovKTrapezoidMethodSEQ : public BaseTask {
9+
public:
10+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
11+
return ppc::task::TypeOfTask::kSEQ;
12+
}
13+
explicit LifanovKTrapezoidMethodSEQ(const InType &in);
14+
15+
private:
16+
bool ValidationImpl() override;
17+
bool PreProcessingImpl() override;
18+
bool RunImpl() override;
19+
bool PostProcessingImpl() override;
20+
};
21+
22+
} // namespace lifanov_k_trapezoid_method
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#include "lifanov_k_trapezoid_method/seq/include/ops_seq.hpp"
2+
3+
#include "lifanov_k_trapezoid_method/common/include/common.hpp"
4+
5+
namespace {
6+
double Function(double x, double y) {
7+
return (x * x) + (y * y);
8+
}
9+
} // namespace
10+
11+
namespace lifanov_k_trapezoid_method {
12+
13+
LifanovKTrapezoidMethodSEQ::LifanovKTrapezoidMethodSEQ(const InType &in) {
14+
SetTypeOfTask(GetStaticTypeOfTask());
15+
GetInput() = in;
16+
GetOutput() = 0.0;
17+
}
18+
19+
bool LifanovKTrapezoidMethodSEQ::ValidationImpl() {
20+
const auto &in = GetInput();
21+
22+
if (in.size() != 6) {
23+
return false;
24+
}
25+
26+
const double ax = in[0];
27+
const double bx = in[1];
28+
const double ay = in[2];
29+
const double by = in[3];
30+
const int nx = static_cast<int>(in[4]);
31+
const int ny = static_cast<int>(in[5]);
32+
33+
return (bx > ax) && (by > ay) && (nx > 0) && (ny > 0);
34+
}
35+
36+
bool LifanovKTrapezoidMethodSEQ::PreProcessingImpl() {
37+
GetOutput() = 0.0;
38+
return true;
39+
}
40+
41+
bool LifanovKTrapezoidMethodSEQ::RunImpl() {
42+
const auto &in = GetInput();
43+
44+
const double ax = in[0];
45+
const double bx = in[1];
46+
const double ay = in[2];
47+
const double by = in[3];
48+
const int nx = static_cast<int>(in[4]);
49+
const int ny = static_cast<int>(in[5]);
50+
51+
const double hx = (bx - ax) / nx;
52+
const double hy = (by - ay) / ny;
53+
54+
double integral = 0.0;
55+
56+
for (int i = 0; i <= nx; ++i) {
57+
const double x = ax + (i * hx);
58+
const double wx = (i == 0 || i == nx) ? 0.5 : 1.0;
59+
60+
for (int j = 0; j <= ny; ++j) {
61+
const double y = ay + (j * hy);
62+
const double wy = (j == 0 || j == ny) ? 0.5 : 1.0;
63+
64+
integral += wx * wy * Function(x, y);
65+
}
66+
}
67+
68+
GetOutput() = integral * hx * hy;
69+
return true;
70+
}
71+
72+
bool LifanovKTrapezoidMethodSEQ::PostProcessingImpl() {
73+
return true;
74+
}
75+
76+
} // namespace lifanov_k_trapezoid_method

0 commit comments

Comments
 (0)