Skip to content

Commit dc98c98

Browse files
pt11241brb484ul
andauthored
Хруев Антон. Технология SEQ|MPI.Многошаговая схема решения двумерных задач глобальной оптимизации. Распараллеливание по характеристикам. Вариант 13 (#294)
<!-- Требования к названию pull request: "<Фамилия> <Имя>. Технология <TECHNOLOGY_NAME:SEQ|OMP|TBB|STL|MPI>. <Полное название задачи>. Вариант <Номер>" --> ## Описание <!-- Пожалуйста, предоставьте подробное описание вашей реализации, включая: - основные детали решения (описание выбранного алгоритма) - применение технологии параллелизма (если применимо) --> - **Задача**: Многошаговая схема решения двумерных задач глобальной оптимизации. Распараллеливание по характеристикам. - **Вариант**: 13 - **Технология**: SEQ, MPI - **Описание**: **SEQ:** Основная идея алгоритма: Двумерная задач оптимизации сводится к одномерной с помощью развертки Гильберта. Алгоритм состоит из: инициализации, где в список испытаний добавляются точки ```t=0``` и ```t=1```, для которых вычисляются значения целевой функции; оценки константы Липшица; вычисления характеристик интервалов и выбора интервала с максимальной характеристикой; построение новой точки. **MPI:** Распараллеливание выполняется по интервалам с наибольшими характеристиками. Алгоритм включает инициализацию, вычисление параметра М (константа Липшица), вычисление характеристик, распределение работы, где каждый процесс получает интервал для вычисления новой точки. После проводится обмен результатами через ```MPI_Allgather```. Более подробное описание находится в отчёте. --- ## Чек-лист <!-- Пожалуйста, убедитесь, что следующие пункты выполнены **до** отправки pull request'а и запроса его ревью: --> - [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, являются точными и достоверными <!-- ПРИМЕЧАНИЕ: Ложные сведения в этом чек-листе могут привести к отклонению PR и получению нулевого балла за соответствующую задачу. --> --------- Co-authored-by: brb484ul <pppphp2800@gmail.com>
1 parent f1de516 commit dc98c98

11 files changed

Lines changed: 933 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <cmath>
5+
#include <cstdint>
6+
#include <string>
7+
#include <tuple>
8+
9+
#include "task/include/task.hpp"
10+
11+
namespace khruev_a_global_opt {
12+
13+
struct SearchData {
14+
int func_id;
15+
double ax, bx;
16+
double ay, by;
17+
double epsilon;
18+
int max_iter;
19+
double r;
20+
};
21+
22+
struct SearchResult {
23+
double x;
24+
double y;
25+
double value;
26+
int iter_count;
27+
};
28+
29+
using InType = SearchData;
30+
using OutType = SearchResult;
31+
using BaseTask = ppc::task::Task<InType, OutType>;
32+
using TestType = std::tuple<std::string, int, double, double, double, double, double>;
33+
34+
// 1. Кривая Гильберта: переводит t [0,1] -> (x, y) [0,1]x[0,1]
35+
// Вспомогательная функция для поворота/отражения квадранта
36+
// n: размер текущего квадрата (степень двойки)
37+
// x, y: координаты
38+
// rx, ry: биты, определяющие квадрант
39+
inline void Rotate(uint64_t n, uint64_t &x, uint64_t &y, uint64_t rx, uint64_t ry) {
40+
if (ry == 0) {
41+
if (rx == 1) {
42+
x = n - 1 - x;
43+
y = n - 1 - y;
44+
}
45+
// Swap x и y
46+
uint64_t t = x;
47+
x = y;
48+
y = t;
49+
}
50+
}
51+
52+
// 1. Кривая Гильберта: переводит t [0,1] -> (x, y) [0,1]x[0,1]
53+
// Используем 32-битный порядок кривой (сетка 2^32 x 2^32), что покрывает точность double
54+
inline void D2xy(double t, double &x, double &y) {
55+
// Ограничиваем t диапазоном [0, 1]
56+
t = std::max(t, 0.0);
57+
t = std::min(t, 1.0);
58+
59+
// Порядок кривой N = 32. Всего точек 2^64 (влезает в uint64_t).
60+
// Масштабируем t [0, 1] в целое число s [0, 2^64 - 1]
61+
// Используем (2^64 - 1) как множитель.
62+
const auto max_dist = static_cast<uint64_t>(-1);
63+
const double two_to_64 = ldexp(1.0, 64);
64+
double val = t * two_to_64;
65+
uint64_t s = 0;
66+
if (val >= two_to_64) {
67+
s = max_dist;
68+
} else {
69+
s = static_cast<uint64_t>(val);
70+
}
71+
72+
uint64_t ix = 0;
73+
uint64_t iy = 0;
74+
75+
// Итеративный алгоритм от младших битов к старшим (bottom-up)
76+
// n - это размер под-квадрата на текущем уровне
77+
for (uint64_t nn = 1; nn < (1ULL << 32); nn <<= 1) {
78+
uint64_t rx = 1 & (s / 2);
79+
uint64_t ry = 1 & (s ^ rx);
80+
81+
Rotate(nn, ix, iy, rx, ry);
82+
83+
ix += nn * rx;
84+
iy += nn * ry;
85+
86+
s /= 4;
87+
}
88+
89+
// Нормализуем обратно в [0, 1]
90+
// Делим на 2^32 (размер сетки)
91+
const double scale = 1.0 / 4294967296.0; // 1.0 / 2^32
92+
x = static_cast<double>(ix) * scale;
93+
y = static_cast<double>(iy) * scale;
94+
}
95+
96+
inline double TargetFunction(int id, double x, double y) {
97+
if (id == 1) {
98+
// квадратичная
99+
return ((x - 0.5) * (x - 0.5)) + ((y - 0.5) * (y - 0.5));
100+
}
101+
if (id == 2) {
102+
// Rastrigin-like
103+
constexpr double kA = 10.0;
104+
constexpr double kTwoPi = 6.2831853071795864769;
105+
return (2.0 * kA) + ((x * x) - (kA * std::cos(kTwoPi * x))) + ((y * y) - (kA * std::cos(kTwoPi * y)));
106+
}
107+
if (id == 3) {
108+
// BoothFunc
109+
const double t1 = x + (2.0 * y) - 7.0;
110+
const double t2 = (2.0 * x) + y - 5.0;
111+
return (t1 * t1) + (t2 * t2);
112+
}
113+
if (id == 4) {
114+
// MatyasFunc
115+
return (0.26 * ((x * x) + (y * y))) - (0.48 * x * y);
116+
}
117+
if (id == 5) { // Himme
118+
const double t1 = ((x * x) + y - 11.0);
119+
const double t2 = (x + (y * y) - 7.0);
120+
return (t1 * t1) + (t2 * t2);
121+
}
122+
123+
return 0.0;
124+
}
125+
126+
// Структура для хранения испытаний
127+
struct Trial {
128+
double x; // координата на отрезке [0, 1]
129+
double z; // значение функции
130+
int id; // исходный индекс
131+
132+
bool operator<(const Trial &other) const {
133+
return x < other.x;
134+
}
135+
};
136+
137+
} // namespace khruev_a_global_opt
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ФИ1",
7+
"task_number": "3"
8+
}
9+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "khruev_a_global_opt/common/include/common.hpp"
6+
#include "task/include/task.hpp"
7+
8+
namespace khruev_a_global_opt {
9+
10+
class KhruevAGlobalOptMPI : public BaseTask {
11+
public:
12+
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
13+
return ppc::task::TypeOfTask::kMPI;
14+
}
15+
16+
explicit KhruevAGlobalOptMPI(const InType &in);
17+
bool ValidationImpl() override;
18+
bool PreProcessingImpl() override;
19+
bool RunImpl() override;
20+
bool PostProcessingImpl() override;
21+
22+
private:
23+
struct IntervalInfo {
24+
double R;
25+
int index; // индекс в массиве trials_ (указывает на правый конец интервала)
26+
27+
bool operator>(const IntervalInfo &other) const {
28+
return R > other.R; // Для сортировки по убыванию
29+
}
30+
};
31+
32+
struct Point {
33+
double x, z;
34+
};
35+
36+
std::vector<Trial> trials_;
37+
OutType result_{};
38+
39+
double CalculateFunction(double t);
40+
void AddTrialUnsorted(double t, double z);
41+
42+
double ComputeM();
43+
[[nodiscard]] std::vector<IntervalInfo> ComputeIntervals(double m) const;
44+
bool LocalShouldStop(const std::vector<IntervalInfo> &intervals, int num_to_check);
45+
[[nodiscard]] double GenerateNewX(int idx, double m) const;
46+
void CollectAndAddPoints(const std::vector<Point> &global_res, int &k);
47+
};
48+
49+
} // namespace khruev_a_global_opt
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#include "khruev_a_global_opt/mpi/include/ops_mpi.hpp"
2+
3+
#include <mpi.h>
4+
5+
#include <algorithm>
6+
#include <cmath>
7+
#include <cstddef>
8+
#include <functional>
9+
#include <utility>
10+
#include <vector>
11+
12+
#include "khruev_a_global_opt/common/include/common.hpp"
13+
14+
namespace khruev_a_global_opt {
15+
16+
KhruevAGlobalOptMPI::KhruevAGlobalOptMPI(const InType &in) {
17+
SetTypeOfTask(GetStaticTypeOfTask());
18+
GetInput() = in;
19+
}
20+
21+
bool KhruevAGlobalOptMPI::ValidationImpl() {
22+
return GetInput().max_iter > 0 && GetInput().epsilon > 0 && GetInput().r > 1.0;
23+
}
24+
25+
bool KhruevAGlobalOptMPI::PreProcessingImpl() {
26+
trials_.clear();
27+
return true;
28+
}
29+
30+
double KhruevAGlobalOptMPI::CalculateFunction(double t) {
31+
double u = 0;
32+
double v = 0;
33+
D2xy(t, u, v);
34+
double real_x = GetInput().ax + (u * (GetInput().bx - GetInput().ax));
35+
double real_y = GetInput().ay + (v * (GetInput().by - GetInput().ay));
36+
return TargetFunction(GetInput().func_id, real_x, real_y);
37+
}
38+
39+
void KhruevAGlobalOptMPI::AddTrialUnsorted(double t, double z) {
40+
Trial tr{};
41+
tr.x = t;
42+
tr.z = z;
43+
trials_.push_back(tr);
44+
}
45+
46+
double KhruevAGlobalOptMPI::ComputeM() {
47+
double max_slope = 0.0;
48+
for (size_t i = 1; i < trials_.size(); ++i) {
49+
double dx = trials_[i].x - trials_[i - 1].x;
50+
double dz = std::abs(trials_[i].z - trials_[i - 1].z);
51+
if (dx > 1e-12) {
52+
max_slope = std::max(max_slope, dz / dx);
53+
}
54+
}
55+
return (max_slope > 0) ? GetInput().r * max_slope : 1.0;
56+
}
57+
58+
std::vector<KhruevAGlobalOptMPI::IntervalInfo> KhruevAGlobalOptMPI::ComputeIntervals(double m) const {
59+
std::vector<IntervalInfo> intervals;
60+
intervals.reserve(trials_.size() - 1);
61+
for (size_t i = 1; i < trials_.size(); ++i) {
62+
double dx = trials_[i].x - trials_[i - 1].x;
63+
double z_r = trials_[i].z;
64+
double z_l = trials_[i - 1].z;
65+
66+
// Классическая формула Стронгина
67+
double r = (m * dx) + (((z_r - z_l) * (z_r - z_l)) / (m * dx)) - (2.0 * (z_r + z_l));
68+
intervals.push_back({r, static_cast<int>(i)});
69+
}
70+
return intervals;
71+
}
72+
73+
bool KhruevAGlobalOptMPI::LocalShouldStop(const std::vector<IntervalInfo> &intervals, int num_to_check) {
74+
bool local_stop = true;
75+
for (int i = 0; i < num_to_check; ++i) {
76+
int idx = intervals[i].index;
77+
if (trials_[idx].x - trials_[idx - 1].x > GetInput().epsilon) {
78+
local_stop = false;
79+
break;
80+
}
81+
}
82+
return local_stop;
83+
}
84+
85+
double KhruevAGlobalOptMPI::GenerateNewX(int idx, double m) const {
86+
double x_r = trials_[idx].x;
87+
double x_l = trials_[idx - 1].x;
88+
double z_r = trials_[idx].z;
89+
double z_l = trials_[idx - 1].z;
90+
91+
// Формула вычисления новой точки испытания
92+
double new_x = (0.5 * (x_r + x_l)) - ((z_r - z_l) / (2.0 * m));
93+
94+
// Защита от выхода за границы интервала
95+
if (new_x <= x_l || new_x >= x_r) {
96+
new_x = 0.5 * (x_r + x_l);
97+
}
98+
return new_x;
99+
}
100+
101+
void KhruevAGlobalOptMPI::CollectAndAddPoints(const std::vector<Point> &global_res, int &k) {
102+
for (auto global_re : global_res) {
103+
if (global_re.x >= 0.0) {
104+
AddTrialUnsorted(global_re.x, global_re.z);
105+
k++;
106+
}
107+
}
108+
}
109+
110+
bool KhruevAGlobalOptMPI::RunImpl() {
111+
int rank = 0;
112+
int size = 0;
113+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
114+
MPI_Comm_size(MPI_COMM_WORLD, &size);
115+
116+
// Начальные испытания
117+
if (trials_.empty()) {
118+
AddTrialUnsorted(0.0, CalculateFunction(0.0));
119+
AddTrialUnsorted(1.0, CalculateFunction(1.0));
120+
}
121+
std::ranges::sort(trials_, [](const Trial &a, const Trial &b) { return a.x < b.x; });
122+
123+
int k = 2; // Уже провели 2 испытания
124+
125+
while (k < GetInput().max_iter) {
126+
// 1. Вычисляем M (одинаково на всех процессах)
127+
double m = ComputeM();
128+
129+
// 2. Вычисляем характеристики R
130+
auto intervals = ComputeIntervals(m);
131+
132+
// 3. Сортируем интервалы по убыванию характеристики
133+
std::ranges::sort(intervals, std::greater<>());
134+
135+
// 4. Проверка критерия остановки (по самому широкому из лучших интервалов)
136+
int num_to_check = std::min(static_cast<int>(intervals.size()), size);
137+
bool local_stop = LocalShouldStop(intervals, num_to_check);
138+
139+
// Синхронная остановка
140+
int stop_signal = 0;
141+
int local_stop_signal = local_stop ? 1 : 0;
142+
MPI_Allreduce(&local_stop_signal, &stop_signal, 1, MPI_INT, MPI_LAND, MPI_COMM_WORLD);
143+
if (stop_signal != 0) {
144+
break;
145+
}
146+
147+
// 5. Генерация новых точек
148+
Point my_point{.x = -1.0, .z = 0.0};
149+
150+
if (std::cmp_less(rank, static_cast<int>(intervals.size()))) {
151+
int idx = intervals[rank].index;
152+
double new_x = GenerateNewX(idx, m);
153+
154+
my_point.x = new_x;
155+
my_point.z = CalculateFunction(new_x);
156+
}
157+
158+
// 6. Сбор результатов со всех процессов
159+
std::vector<Point> global_res(size);
160+
MPI_Allgather(&my_point, 2, MPI_DOUBLE, global_res.data(), 2, MPI_DOUBLE, MPI_COMM_WORLD);
161+
162+
// 7. Обновление списка
163+
CollectAndAddPoints(global_res, k);
164+
std::ranges::sort(trials_, [](const Trial &a, const Trial &b) { return a.x < b.x; });
165+
}
166+
167+
// Поиск финального минимума среди всех испытаний
168+
auto it = std::ranges::min_element(trials_, {}, &Trial::z);
169+
170+
double u = 0;
171+
double v = 0;
172+
D2xy(it->x, u, v);
173+
result_.x = GetInput().ax + (u * (GetInput().bx - GetInput().ax));
174+
result_.y = GetInput().ay + (v * (GetInput().by - GetInput().ay));
175+
result_.value = it->z;
176+
result_.iter_count = k;
177+
178+
GetOutput() = result_;
179+
return true;
180+
}
181+
182+
bool KhruevAGlobalOptMPI::PostProcessingImpl() {
183+
return true;
184+
}
185+
186+
} // namespace khruev_a_global_opt

0 commit comments

Comments
 (0)