diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp b/tasks/Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp new file mode 100644 index 0000000000..50dc0eb5bd --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#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 points; +}; + +using InType = Input; +using OutType = std::vector; // convex hull in CCW order, without repeating the first point +using TestType = std::tuple; // (n, label) for gtest naming +using BaseTask = ppc::task::Task; + +} // namespace nazarova_k_conv_hull_const_grah_processes diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/info.json b/tasks/Nazarova_K_Conv_hull_const_Grah/info.json new file mode 100644 index 0000000000..3678106444 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Ксения", + "last_name": "Назарова", + "middle_name": "Олеговна", + "group_number": "3823Б1ПР3", + "task_number": "3" + } +} diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp b/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..332ccb5e69 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#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 points_; +}; + +} // namespace nazarova_k_conv_hull_const_grah_processes diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/src/ops_mpi.cpp b/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..95f3af608b --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/mpi/src/ops_mpi.cpp @@ -0,0 +1,197 @@ +#include + +#include +#include +#include +#include +#include +#include + +#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(a.x) - static_cast(o.x); + const std::int64_t ay = static_cast(a.y) - static_cast(o.y); + const std::int64_t bx = static_cast(b.x) - static_cast(o.x); + const std::int64_t by = static_cast(b.y) - static_cast(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(a.x) - static_cast(b.x); + const std::int64_t dy = static_cast(a.y) - static_cast(b.y); + return (dx * dx) + (dy * dy); +} + +std::vector GrahamScan(std::vector 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 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(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(rank) * n) / static_cast(size); + const std::size_t end = (static_cast(rank + 1) * n) / static_cast(size); + + std::vector 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 local_hull = GrahamScan(local_points); + + std::vector 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(sendbuf.size()); + std::vector recvcounts; + if (rank == 0) { + recvcounts.resize(static_cast(size), 0); + } + MPI_Gather(&sendcount, 1, MPI_INT, (rank == 0 ? recvcounts.data() : nullptr), 1, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector displs; + std::vector recvbuf; + int total = 0; + if (rank == 0) { + displs.resize(static_cast(size), 0); + for (int i = 0; i < size; i++) { + displs[static_cast(i)] = total; + total += recvcounts[static_cast(i)]; + } + recvbuf.resize(static_cast(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 hull; + if (rank == 0) { + std::vector candidates; + candidates.reserve(static_cast(total / 2)); + for (int i = 0; i + 1 < total; i += 2) { + const auto idx = static_cast(i); + candidates.push_back(Point{.x = recvbuf[idx], .y = recvbuf[idx + 1U]}); + } + hull = GrahamScan(candidates); + } + + int hull_count = (rank == 0) ? static_cast(hull.size()) : 0; + MPI_Bcast(&hull_count, 1, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector hull_buf; + hull_buf.resize(static_cast(hull_count) * 2U, 0); + if (rank == 0) { + for (int i = 0; i < hull_count; i++) { + const auto idx = static_cast(i); + hull_buf[2U * idx] = hull[idx].x; + hull_buf[(2U * idx) + 1U] = hull[idx].y; + } + } + MPI_Bcast(hull_buf.data(), static_cast(hull_buf.size()), MPI_INT, 0, MPI_COMM_WORLD); + + std::vector out; + out.reserve(static_cast(hull_count)); + for (int i = 0; i < hull_count; i++) { + const auto idx = static_cast(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 + diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/report.md b/tasks/Nazarova_K_Conv_hull_const_Grah/report.md new file mode 100644 index 0000000000..441fce97a3 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/report.md @@ -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 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-версия использует схему «локальная оболочка → сбор кандидатов → итоговая оболочка на корневом процессе → рассылка результата». diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp b/tasks/Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..db5c1c7c81 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#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 points_; +}; + +} // namespace nazarova_k_conv_hull_const_grah_processes diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/seq/src/ops_seq.cpp b/tasks/Nazarova_K_Conv_hull_const_Grah/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..342cec3338 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/seq/src/ops_seq.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include + +#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp" +#include "Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.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(a.x) - static_cast(o.x); + const std::int64_t ay = static_cast(a.y) - static_cast(o.y); + const std::int64_t bx = static_cast(b.x) - static_cast(o.x); + const std::int64_t by = static_cast(b.y) - static_cast(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(a.x) - static_cast(b.x); + const std::int64_t dy = static_cast(a.y) - static_cast(b.y); + return (dx * dx) + (dy * dy); +} + +std::vector GrahamScan(std::vector pts) { + std::ranges::sort(pts, LessXY); + pts.erase(std::ranges::unique(pts).begin(), pts.end()); + + if (pts.size() <= 1U) { + return pts; + } + + // Pivot: lowest (y, x) + 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; // CCW first + } + // Same ray: keep closer first, so farther one survives when popping collinear points. + 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 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(0)) { + hull.pop_back(); + } + hull.push_back(pts[i]); + } + + // Degenerate case: all points collinear -> keep endpoints + if (hull.size() == 1U) { + return hull; + } + if (hull.size() == 2U) { + if (LessPivot(hull[1], hull[0])) { + std::swap(hull[0], hull[1]); + } + } + + return hull; +} + +} // namespace + +NazarovaKConvHullConstGrahSEQ::NazarovaKConvHullConstGrahSEQ(const InType& in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput().clear(); +} + +bool NazarovaKConvHullConstGrahSEQ::ValidationImpl() { + // Any number of points is allowed (including empty). + return true; +} + +bool NazarovaKConvHullConstGrahSEQ::PreProcessingImpl() { + points_ = GetInput().points; + GetOutput().clear(); + return true; +} + +bool NazarovaKConvHullConstGrahSEQ::RunImpl() { + GetOutput() = GrahamScan(points_); + return true; +} + +bool NazarovaKConvHullConstGrahSEQ::PostProcessingImpl() { + // Hull cannot have more points than input (after duplicate removal). + return GetOutput().size() <= GetInput().points.size(); +} + +} // namespace nazarova_k_conv_hull_const_grah_processes diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/settings.json b/tasks/Nazarova_K_Conv_hull_const_Grah/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/tests/.clang-tidy b/tasks/Nazarova_K_Conv_hull_const_Grah/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/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/Nazarova_K_Conv_hull_const_Grah/tests/functional/main.cpp b/tasks/Nazarova_K_Conv_hull_const_Grah/tests/functional/main.cpp new file mode 100644 index 0000000000..793243a6f8 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/tests/functional/main.cpp @@ -0,0 +1,209 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp" +#include "Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp" +#include "Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.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(a.x) - static_cast(o.x); + const std::int64_t ay = static_cast(a.y) - static_cast(o.y); + const std::int64_t bx = static_cast(b.x) - static_cast(o.x); + const std::int64_t by = static_cast(b.y) - static_cast(o.y); + return (ax * by) - (ay * bx); +} + +std::vector ReferenceHull(std::vector pts) { + std::ranges::sort(pts, LessXY); + pts.erase(std::ranges::unique(pts).begin(), pts.end()); + + if (pts.size() <= 1U) { + return pts; + } + + std::vector lower; + for (const auto& p : pts) { + while (lower.size() >= 2U && Cross(lower[lower.size() - 2U], lower[lower.size() - 1U], p) <= 0) { + lower.pop_back(); + } + lower.push_back(p); + } + + std::vector upper; + for (std::size_t idx = pts.size(); idx > 0; idx--) { + const auto& p = pts[idx - 1U]; + while (upper.size() >= 2U && Cross(upper[upper.size() - 2U], upper[upper.size() - 1U], p) <= 0) { + upper.pop_back(); + } + upper.push_back(p); + } + + lower.pop_back(); + upper.pop_back(); + + std::vector hull; + hull.reserve(lower.size() + upper.size()); + hull.insert(hull.end(), lower.begin(), lower.end()); + hull.insert(hull.end(), upper.begin(), upper.end()); + return hull; +} + +std::int64_t TwiceArea(const std::vector& h) { + if (h.size() < 3U) { + return 0; + } + std::int64_t area2 = 0; + for (std::size_t i = 0; i < h.size(); i++) { + const std::size_t j = (i + 1U) % h.size(); + area2 += (static_cast(h[i].x) * static_cast(h[j].y)) - + (static_cast(h[i].y) * static_cast(h[j].x)); + } + return area2; +} + +std::vector CanonicalHull(std::vector h) { + if (h.size() <= 1U) { + return h; + } + + if (h.size() == 2U && LessPivot(h[1], h[0])) { + std::swap(h[0], h[1]); + return h; + } + + const auto it = std::ranges::min_element(h, LessPivot); + std::ranges::rotate(h, it); + + if (TwiceArea(h) < 0) { + std::ranges::reverse(h.begin() + 1, h.end()); + } + + return h; +} + +bool HullEqual(const std::vector& a, const std::vector& b) { + return CanonicalHull(a) == CanonicalHull(b); +} + +Input MakeCase(const TestType& params) { + const int n = std::get<0>(params); + const std::string label = std::get<1>(params); + + Input in; + if (label == "empty") { + return in; + } + if (label == "single") { + in.points = {Point{.x=0, .y=0}}; + return in; + } + if (label == "square_with_inside") { + // Corners + points on edges + inside + duplicates. + in.points = {Point{.x=0, .y=0}, Point{.x=0, .y=10}, Point{.x=10, .y=10}, Point{.x=10, .y=0}, + Point{.x=0, .y=0}, Point{.x=10, .y=10}, // duplicates + Point{.x=5, .y=0}, Point{.x=0, .y=5}, Point{.x=10, .y=5}, Point{.x=5, .y=10}, // on edges + Point{.x=5, .y=5}, Point{.x=6, .y=6}, Point{.x=4, .y=7}}; // inside + return in; + } + if (label == "collinear") { + in.points = {Point{.x=0, .y=0}, Point{.x=1, .y=0}, Point{.x=2, .y=0}, Point{.x=3, .y=0}, Point{.x=-1, .y=0}, Point{.x=2, .y=0}, Point{.x=0, .y=0}}; + return in; + } + + // random_n + // NOLINTNEXTLINE(cert-msc51-cpp) + std::mt19937 gen(123U + static_cast(n)); + std::uniform_int_distribution dist(-1000, 1000); + in.points.resize(static_cast(n)); + for (auto& p : in.points) { + p.x = dist(gen); + p.y = dist(gen); + } + return in; +} + +} // namespace + +class NazarovaKConvHullConstGrahRunFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType& test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + const TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = MakeCase(params); + expected_ = CanonicalHull(ReferenceHull(input_data_.points)); + } + + bool CheckTestOutputData(OutType& output_data) final { + return HullEqual(output_data, expected_); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{}; + OutType expected_; +}; + +namespace { + +TEST_P(NazarovaKConvHullConstGrahRunFuncTests, BuildConvexHull) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(0, "empty"), + std::make_tuple(1, "single"), + std::make_tuple(0, "square_with_inside"), + std::make_tuple(0, "collinear"), + std::make_tuple(10, "random_n"), + std::make_tuple(100, "random_n")}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_Nazarova_K_Conv_hull_const_Grah), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_Nazarova_K_Conv_hull_const_Grah)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + NazarovaKConvHullConstGrahRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(ConvHullGrahamTests, NazarovaKConvHullConstGrahRunFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace nazarova_k_conv_hull_const_grah_processes diff --git a/tasks/Nazarova_K_Conv_hull_const_Grah/tests/performance/main.cpp b/tasks/Nazarova_K_Conv_hull_const_Grah/tests/performance/main.cpp new file mode 100644 index 0000000000..5430dfd880 --- /dev/null +++ b/tasks/Nazarova_K_Conv_hull_const_Grah/tests/performance/main.cpp @@ -0,0 +1,112 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Nazarova_K_Conv_hull_const_Grah/common/include/common.hpp" +#include "Nazarova_K_Conv_hull_const_Grah/mpi/include/ops_mpi.hpp" +#include "Nazarova_K_Conv_hull_const_Grah/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.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; +} + +std::int64_t TwiceArea(const std::vector& h) { + if (h.size() < 3U) { + return 0; + } + std::int64_t area2 = 0; + for (std::size_t i = 0; i < h.size(); i++) { + const std::size_t j = (i + 1U) % h.size(); + area2 += (static_cast(h[i].x) * static_cast(h[j].y)) - + (static_cast(h[i].y) * static_cast(h[j].x)); + } + return area2; +} + +std::vector CanonicalHull(std::vector h) { + if (h.size() <= 1U) { + return h; + } + if (h.size() == 2U && LessPivot(h[1], h[0])) { + std::swap(h[0], h[1]); + return h; + } + + const auto it = std::ranges::min_element(h, LessPivot); + std::ranges::rotate(h, it); + if (TwiceArea(h) < 0) { + std::ranges::reverse(h.begin() + 1, h.end()); + } + return h; +} + +bool HullEqual(const std::vector& a, const std::vector& b) { + return CanonicalHull(a) == CanonicalHull(b); +} + +} // namespace + +class NazarovaKConvHullConstGrahRunPerfTests : public ppc::util::BaseRunPerfTests { + + static constexpr int kN = 1250000; + InType input_data_{}; + OutType expected_; + + void SetUp() override { + + std::mt19937 gen(777U); + + static constexpr int kR = 1000000; + std::uniform_int_distribution dist(1, kR - 1); + + input_data_.points.resize(static_cast(kN)); + input_data_.points[0] = Point{.x = 0, .y = 0}; + input_data_.points[1] = Point{.x = kR, .y = 0}; + input_data_.points[2] = Point{.x = kR, .y = kR}; + input_data_.points[3] = Point{.x = 0, .y = kR}; + + for (std::size_t i = 4; i < input_data_.points.size(); i++) { + input_data_.points[i] = Point{.x = dist(gen), .y = dist(gen)}; + } + + expected_ = CanonicalHull(std::vector{Point{.x = 0, .y = 0}, Point{.x = kR, .y = 0}, Point{.x = kR, .y = kR}, + Point{.x = 0, .y = kR}}); + } + + bool CheckTestOutputData(OutType& output_data) final { + return HullEqual(output_data, expected_); + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(NazarovaKConvHullConstGrahRunPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_Nazarova_K_Conv_hull_const_Grah); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = NazarovaKConvHullConstGrahRunPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, NazarovaKConvHullConstGrahRunPerfTests, kGtestValues, kPerfTestName); + +} // namespace nazarova_k_conv_hull_const_grah_processes