Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <tuple>
#include <vector>

#include "task/include/task.hpp"

namespace nazarova_k_conv_hull_const_grah_processes {

struct Point {
int x = 0;
int y = 0;
};

inline bool operator==(const Point& a, const Point& b) {
return a.x == b.x && a.y == b.y;
}

inline bool operator!=(const Point& a, const Point& b) {
return !(a == b);
}

struct Input {
std::vector<Point> points;
};

using InType = Input;
using OutType = std::vector<Point>; // convex hull in CCW order, without repeating the first point
using TestType = std::tuple<int, std::string>; // (n, label) for gtest naming
using BaseTask = ppc::task::Task<InType, OutType>;

} // namespace nazarova_k_conv_hull_const_grah_processes
9 changes: 9 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"student": {
"first_name": "Ксения",
"last_name": "Назарова",
"middle_name": "Олеговна",
"group_number": "3823Б1ПР3",
"task_number": "3"
}
}
25 changes: 25 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <vector>

#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp"

namespace nazarova_k_conv_hull_const_grah_processes {

class NazarovaKConvHullConstGrahMPI : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kMPI;
}
explicit NazarovaKConvHullConstGrahMPI(const InType &in);

private:
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;

std::vector<Point> points_;
};

} // namespace nazarova_k_conv_hull_const_grah_processes
197 changes: 197 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/mpi/src/ops_mpi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#include <mpi.h>

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <ranges>
#include <vector>

#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp"
#include "Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp"

namespace nazarova_k_conv_hull_const_grah_processes {
namespace {

inline bool LessPivot(const Point& a, const Point& b) {
if (a.y != b.y) {
return a.y < b.y;
}
return a.x < b.x;
}

inline bool LessXY(const Point& a, const Point& b) {
if (a.x != b.x) {
return a.x < b.x;
}
return a.y < b.y;
}

inline std::int64_t Cross(const Point& o, const Point& a, const Point& b) {
const std::int64_t ax = static_cast<std::int64_t>(a.x) - static_cast<std::int64_t>(o.x);
const std::int64_t ay = static_cast<std::int64_t>(a.y) - static_cast<std::int64_t>(o.y);
const std::int64_t bx = static_cast<std::int64_t>(b.x) - static_cast<std::int64_t>(o.x);
const std::int64_t by = static_cast<std::int64_t>(b.y) - static_cast<std::int64_t>(o.y);
return (ax * by) - (ay * bx);
}

inline std::int64_t Dist2(const Point& a, const Point& b) {
const std::int64_t dx = static_cast<std::int64_t>(a.x) - static_cast<std::int64_t>(b.x);
const std::int64_t dy = static_cast<std::int64_t>(a.y) - static_cast<std::int64_t>(b.y);
return (dx * dx) + (dy * dy);
}

std::vector<Point> GrahamScan(std::vector<Point> pts) {
std::ranges::sort(pts, LessXY);
pts.erase(std::ranges::unique(pts).begin(), pts.end());

if (pts.size() <= 1U) {
return pts;
}

const auto pivot_it = std::ranges::min_element(pts, LessPivot);
std::iter_swap(pts.begin(), pivot_it);
const Point pivot = pts.front();

auto angle_less = [&pivot](const Point& a, const Point& b) {
const std::int64_t cr = Cross(pivot, a, b);
if (cr != 0) {
return cr > 0;
}
return Dist2(pivot, a) < Dist2(pivot, b);
};

std::ranges::sort(std::next(pts.begin()), pts.end(), angle_less);

if (pts.size() == 2U) {
return pts;
}

std::vector<Point> hull;
hull.reserve(pts.size());
hull.push_back(pts[0]);
hull.push_back(pts[1]);
for (std::size_t i = 2; i < pts.size(); i++) {
while (hull.size() >= 2U &&
Cross(hull[hull.size() - 2U], hull[hull.size() - 1U], pts[i]) <= static_cast<std::int64_t>(0)) {
hull.pop_back();
}
hull.push_back(pts[i]);
}

if (hull.size() == 2U && LessPivot(hull[1], hull[0])) {
std::swap(hull[0], hull[1]);
}

return hull;
}

} // namespace

NazarovaKConvHullConstGrahMPI::NazarovaKConvHullConstGrahMPI(const InType& in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput().clear();
}

bool NazarovaKConvHullConstGrahMPI::ValidationImpl() {
return true;
}

bool NazarovaKConvHullConstGrahMPI::PreProcessingImpl() {
points_ = GetInput().points;
GetOutput().clear();
return true;
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
bool NazarovaKConvHullConstGrahMPI::RunImpl() {
int rank = 0;
int size = 1;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

const std::size_t n = points_.size();
const std::size_t start = (static_cast<std::size_t>(rank) * n) / static_cast<std::size_t>(size);
const std::size_t end = (static_cast<std::size_t>(rank + 1) * n) / static_cast<std::size_t>(size);

std::vector<Point> local_points;
local_points.reserve(end - start);
for (std::size_t i = start; i < end; i++) {
local_points.push_back(points_[i]);
}

const std::vector<Point> local_hull = GrahamScan(local_points);

std::vector<int> sendbuf;
sendbuf.reserve(local_hull.size() * 2U);
for (const auto& p : local_hull) {
sendbuf.push_back(p.x);
sendbuf.push_back(p.y);
}

int sendcount = static_cast<int>(sendbuf.size());
std::vector<int> recvcounts;
if (rank == 0) {
recvcounts.resize(static_cast<std::size_t>(size), 0);
}
MPI_Gather(&sendcount, 1, MPI_INT, (rank == 0 ? recvcounts.data() : nullptr), 1, MPI_INT, 0, MPI_COMM_WORLD);

std::vector<int> displs;
std::vector<int> recvbuf;
int total = 0;
if (rank == 0) {
displs.resize(static_cast<std::size_t>(size), 0);
for (int i = 0; i < size; i++) {
displs[static_cast<std::size_t>(i)] = total;
total += recvcounts[static_cast<std::size_t>(i)];
}
recvbuf.resize(static_cast<std::size_t>(total), 0);
}

MPI_Gatherv(sendcount > 0 ? sendbuf.data() : nullptr, sendcount, MPI_INT, (rank == 0 ? recvbuf.data() : nullptr),
(rank == 0 ? recvcounts.data() : nullptr), (rank == 0 ? displs.data() : nullptr), MPI_INT, 0,
MPI_COMM_WORLD);

std::vector<Point> hull;
if (rank == 0) {
std::vector<Point> candidates;
candidates.reserve(static_cast<std::size_t>(total / 2));
for (int i = 0; i + 1 < total; i += 2) {
const auto idx = static_cast<std::size_t>(i);
candidates.push_back(Point{.x = recvbuf[idx], .y = recvbuf[idx + 1U]});
}
hull = GrahamScan(candidates);
}

int hull_count = (rank == 0) ? static_cast<int>(hull.size()) : 0;
MPI_Bcast(&hull_count, 1, MPI_INT, 0, MPI_COMM_WORLD);

std::vector<int> hull_buf;
hull_buf.resize(static_cast<std::size_t>(hull_count) * 2U, 0);
if (rank == 0) {
for (int i = 0; i < hull_count; i++) {
const auto idx = static_cast<std::size_t>(i);
hull_buf[2U * idx] = hull[idx].x;
hull_buf[(2U * idx) + 1U] = hull[idx].y;
}
}
MPI_Bcast(hull_buf.data(), static_cast<int>(hull_buf.size()), MPI_INT, 0, MPI_COMM_WORLD);

std::vector<Point> out;
out.reserve(static_cast<std::size_t>(hull_count));
for (int i = 0; i < hull_count; i++) {
const auto idx = static_cast<std::size_t>(i);
out.push_back(Point{.x = hull_buf[2U * idx], .y = hull_buf[(2U * idx) + 1U]});
}
GetOutput() = out;

return true;
}

bool NazarovaKConvHullConstGrahMPI::PostProcessingImpl() {
return GetOutput().size() <= GetInput().points.size();
}

} // namespace nazarova_k_conv_hull_const_grah_processes

101 changes: 101 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Построение выпуклой оболочки — проход Грэхема (MPI)

- Студент: Назарова Ксения Олеговна, группа 3823Б1ПР3
- Технология: SEQ | MPI
- Задача: 24

## Введение

Цель работы — реализовать построение выпуклой оболочки множества точек на плоскости последовательным (SEQ) и параллельным (MPI) способами.

## Постановка задачи

Дано множество точек \(\{(x_i, y_i)\}\). Требуется построить выпуклую оболочку — минимальный выпуклый многоугольник, содержащий все точки.

Входные данные:

```cpp
struct Point { int x, y; };
struct Input { std::vector<Point> points; };
```

Выходные данные: вершины выпуклой оболочки **в порядке обхода против часовой стрелки (CCW)** без повторения первой точки.

## Базовый алгоритм (SEQ): Graham scan

Алгоритм:
- выбирается опорная точка (минимальная по \(y\), затем по \(x\));
- остальные точки сортируются по полярному углу относительно опорной (при равном угле — по расстоянию);
- стек/вектор оболочки строится последовательным добавлением точек с удалением последних при правом повороте/коллинеарности.

Сложность: \(O(n \log n)\) по времени (за счёт сортировки), \(O(n)\) по памяти.

## Схема распараллеливания (MPI)

Так как входной набор точек доступен всем процессам в рамках тестового окружения, распараллеливание сделано в два этапа:

1) **Локальная обработка**: каждый процесс берёт свой блок точек и строит локальную оболочку (Graham scan).
2) **Объединение**: локальные оболочки (их вершины) собираются на `rank=0` через `MPI_Gatherv`, на корневом процессе строится итоговая оболочка, затем результат рассылается всем процессам через `MPI_Bcast`.

Замечание: объединение оболочек корректно, потому что глобальная оболочка содержится в оболочке множества кандидатов (вершин локальных оболочек).

## Детали реализации

Файлы реализации:
- SEQ: `tasks/Nazarova_K_Conv_hull_const_Grah/seq/src/ops_seq.cpp`
- MPI: `tasks/Nazarova_K_Conv_hull_const_Grah/mpi/src/ops_mpi.cpp`

## Проверка корректности

Функциональные тесты: `tasks/Nazarova_K_Conv_hull_const_Grah/tests/functional/main.cpp`.

Проверяются:
- пустой ввод и единичная точка;
- квадрат с внутренними точками и дубликатами;
- коллинеарные точки;
- случайные наборы точек (сравнение с эталонной реализацией monotone chain).

## Производительность

Performance-тесты: `tasks/Nazarova_K_Conv_hull_const_Grah/tests/performance/main.cpp`.

В тесте используется \(n=200000\) случайных точек; корректность результата проверяется сравнением с эталонной оболочкой.

Замеры (MPI запуск под `mpirun --oversubscribe -n 2`):

| Mode | Procs | Time, s | Speedup | Efficiency |
|----------|------:|-------------:|--------:|-----------:|
| pipeline | 1 | 0.8867654800 | 1.00 | N/A |
| pipeline | 2 | 0.4245403378 | 2.08 | 104.0% |
| task_run | 1 | 0.8099996090 | 1.00 | N/A |
| task_run | 2 | 0.4384538370 | 1.85 | 92.5% |

Где \(Speedup = T_{seq} / T_{mpi}\), \(Efficiency = Speedup / p\).

## Как воспроизвести

Сборка:

```bash
git submodule update --init --recursive
cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release
cmake --build build --config Release --parallel
```

Функциональные тесты:

```bash
./build/bin/ppc_func_tests --gtest_filter='*ConvHullGrahamTests*'
```

Performance-тесты (MPI часть под `mpirun`):

```bash
export OMPI_ALLOW_RUN_AS_ROOT=1
export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1
mpirun --oversubscribe -n 2 ./build/bin/ppc_perf_tests --gtest_filter='*Conv_hull_const_Grah*'
```

## Заключение

Реализованы SEQ и MPI версии построения выпуклой оболочки методом Грэхема; MPI-версия использует схему «локальная оболочка → сбор кандидатов → итоговая оболочка на корневом процессе → рассылка результата».
25 changes: 25 additions & 0 deletions tasks/Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <vector>

#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp"

namespace nazarova_k_conv_hull_const_grah_processes {

class NazarovaKConvHullConstGrahSEQ : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kSEQ;
}
explicit NazarovaKConvHullConstGrahSEQ(const InType &in);

private:
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;

std::vector<Point> points_;
};

} // namespace nazarova_k_conv_hull_const_grah_processes
Loading
Loading