diff --git a/modules/mpi/gusev_n_dijkstras_algorithm/CMakeLists.txt b/modules/mpi/gusev_n_dijkstras_algorithm/CMakeLists.txt new file mode 100644 index 0000000..48e9fb6 --- /dev/null +++ b/modules/mpi/gusev_n_dijkstras_algorithm/CMakeLists.txt @@ -0,0 +1,41 @@ +get_filename_component(ProjectId ${CMAKE_CURRENT_SOURCE_DIR} NAME) + +set(LATEX_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") +if (NOT EXISTS ${LATEX_OUTPUT_PATH}) + file(MAKE_DIRECTORY ${LATEX_OUTPUT_PATH}) +endif () + +if (USE_LATEX) + message( STATUS "-- " ${ProjectId} ) + file(GLOB_RECURSE report_files "*.tex") + + foreach (report ${report_files}) + get_filename_component(report_name ${report} NAME_WE) + list(APPEND list_report_names ${report_name}) + endforeach () + + add_custom_target( ${ProjectId}_prebuild + COMMAND ${PDFLATEX_COMPILER} -draftmode -interaction=nonstopmode ${report_files} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${report_files}) + + add_custom_target( ${ProjectId}_pdf + COMMAND ${PDFLATEX_COMPILER} ${report_files} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${report_files}) + + add_custom_target(${ProjectId}_all_formats ALL) + add_dependencies(${ProjectId}_all_formats ${ProjectId}_pdf) + + foreach (report_name ${list_report_names}) + add_custom_command( + TARGET ${ProjectId}_all_formats + POST_BUILD + COMMAND mv "${CMAKE_CURRENT_SOURCE_DIR}/${report_name}.aux" "${LATEX_OUTPUT_PATH}/${report_name}.aux" + COMMAND mv "${CMAKE_CURRENT_SOURCE_DIR}/${report_name}.log" "${LATEX_OUTPUT_PATH}/${report_name}.log" + COMMAND mv "${CMAKE_CURRENT_SOURCE_DIR}/${report_name}.pdf" "${LATEX_OUTPUT_PATH}/${report_name}.pdf" + ) + endforeach () +else() + message( STATUS "-- ${ProjectId} - NOT BUILD!" ) +endif() \ No newline at end of file diff --git a/modules/mpi/gusev_n_dijkstras_algorithm/gusev_n_dijkstras_algorithm.tex b/modules/mpi/gusev_n_dijkstras_algorithm/gusev_n_dijkstras_algorithm.tex new file mode 100644 index 0000000..ed923dc --- /dev/null +++ b/modules/mpi/gusev_n_dijkstras_algorithm/gusev_n_dijkstras_algorithm.tex @@ -0,0 +1,277 @@ +\documentclass[12pt]{article} +\usepackage[utf8]{inputenc} +\usepackage[russian]{babel} +\usepackage{amsmath} +\usepackage{geometry} +\usepackage{titlesec} +\usepackage{hyperref} +\usepackage{tocloft} +\usepackage{xcolor} +\usepackage{listings} +\geometry{a4paper, left=25mm, right=15mm, top=20mm, bottom=20mm} + +\hypersetup{ + colorlinks=true, + linkcolor=black, + urlcolor=black, + pdftitle={Отчет по проекту}, + pdfauthor={Гусев Никита}, + pdfsubject={Поиск кратчайших путей из одной вершины, Алгоритм Дейкстры} +} + +\lstset{ + language=C++, + basicstyle=\ttfamily\small, + keywordstyle=\bfseries\color{blue}, + commentstyle=\itshape\color{green!50!black}, + stringstyle=\color{red!60!black}, + numbers=left, + numberstyle=\tiny, + stepnumber=1, + numbersep=8pt, + backgroundcolor=\color{gray!10}, + showspaces=false, + showstringspaces=false, + breaklines=true, + frame=single, + tabsize=2, + captionpos=b +} + +\renewcommand{\cftsecaftersnum}{.} +\renewcommand{\cftsecleader}{\cftdotfill{\cftdotsep}} +\titleformat{\section}{\large\bfseries}{\thesection.}{1em}{} + +\title{Отчёт по теме: \\ Поиск кратчайших путей из одной вершины \\ (Алгоритм Дейкстры с использованием CRS графов) \\ ННГУ им. Лобачевского \\ Параллельное программирование} +\author{Гусев Никита} + +\begin{document} + +\maketitle +\begin{abstract} + В данном отчете рассматривается реализация алгоритма Дейкстры для поиска кратчайших путей из одной вершины в графах, представленных в формате CRS (Compressed Row Storage). Описание включает в себя структуру данных, используемую для представления графа, а также тестирование производительности и валидацию алгоритма. +\end{abstract} +\newpage +\tableofcontents +\newpage + +\section{Введение} +Поиск кратчайших путей в графах является одной из ключевых задач в области компьютерных наук и оптимизации. Графы представляют собой мощный инструмент для моделирования различных систем, таких как транспортные сети, социальные сети и компьютерные сети. Алгоритм Дейкстры, предложенный Эдсгером Дейкстрой в 1956 году, является одним из самых известных методов решения данной задачи, особенно для графов с неотрицательными весами рёбер. + +Дейкстра использует жадный подход, постепенно расширяя известные кратчайшие пути от начальной вершины ко всем остальным вершинам графа. В данной работе рассматривается параллельная реализация алгоритма Дейкстры с использованием библиотеки MPI (Message Passing Interface) для эффективной обработки больших графов, представленных в формате CRS. Параллельная обработка позволяет значительно ускорить вычисления на многоядерных системах и кластерах. + +\newpage +\section{Постановка задачи} +В данной работе мы рассматриваем параллельную реализацию алгоритма Дейкстры поиска кратчайшой величины, использующую библиотеку MPI (Message Passing Interface) для эффективной обработки графов в формате CRS. +\newpage +\section{Структура данных} +\subsection{SparseGraphCRS} +Граф представлен с использованием структуры данных \texttt{SparseGraphCRS}, которая включает в себя три основных массива: +\begin{itemize} + \item \texttt{values} - массив весов рёбер, + \item \texttt{col\_indices} - массив индексов столбцов (целевых вершин), + \item \texttt{row\_ptr} - массив указателей на начало каждой строки. +\end{itemize} + +\begin{lstlisting}[language=c++, caption={Определение структуры SparseGraphCRS}] +struct SparseGraphCRS { + std::vector values; + std::vector col_indices; + std::vector row_ptr; + int num_vertices; + + SparseGraphCRS(int n) : num_vertices(n) { row_ptr.resize(n + 1, 0); } + + void add_edge(int u, int v, double weight) { + values.push_back(weight); + col_indices.push_back(v); + for (size_t i = u + 1; i < row_ptr.size(); ++i) { + row_ptr[i]++; + } + } +}; +\end{lstlisting} +\subsection{Преимущества представления CRS} +Использование формата CRS имеет несколько ключевых преимуществ: +\begin{itemize} +\item \texttt{Экономия памяти}: Хранение только ненулевых значений значительно снижает потребление памяти по сравнению с полными матрицами. +\item \texttt{Быстрый доступ}: Позволяет эффективно получать доступ к рёбрам, исходящим из каждой вершины, что критично для алгоритмов поиска. +\item \texttt{Легкость в реализации}: Простота реализации операций добавления рёбер и доступа к данным упрощает разработку алгоритмов. +\end{itemize} +\newpage +\section{Реализация} +\subsection{Основные функции} +Класс \texttt{DijkstrasAlgorithmParallel} реализует алгоритм Дейкстры и включает в себя несколько ключевых методов: +\begin{itemize} + \item \texttt{validation()} - проверяет корректность входных данных. + \item \texttt{pre\_processing()} - выполняет предварительную обработку. + \item \texttt{run()} - основной метод, реализующий алгоритм Дейкстры. + \item \texttt{post\_processing()} - выполняет завершающие действия и возвращает результаты. +\end{itemize} +\subsection{Метод validation()} +Метод \texttt{validation()} проверяет данные на корректность: +\begin{lstlisting}[language=c++, caption={Сам метод}] +bool DijkstrasAlgorithmParallel::validation() { + internal_order_test(); + if (taskData->inputs.empty() || taskData->inputs[0] == nullptr || + taskData->inputs.size() != taskData->inputs_count.size() || taskData->inputs_count[0] != sizeof(SparseGraphCRS)) { + return false; + } + + auto* graph = reinterpret_cast(taskData->inputs[0]); + + if (graph->num_vertices == 0) { + return false; + } + + if (taskData->outputs.empty() || taskData->outputs[0] == nullptr || + taskData->outputs.size() != taskData->outputs_count.size()) { + return false; + } + + return true; +} +\end{lstlisting} +В этом методе проверяются:\\ +\begin{itemize} + \item Входные и выходные данные (не ноль, размер) + \item Количество вершин графа не ноль +\end{itemize} +\subsection{Метод run()} +Метод \texttt{run()} реализует основной цикл алгоритма Дейкстры, в котором происходит: +\begin{itemize} + \item Инициализация локальных расстояний и посещённых вершин. + \item Итеративный поиск минимальной вершины и обновление расстояний. +\end{itemize} +Основные моменты из кода: +\begin{lstlisting} +int vertices_per_proc = num_vertices / num_procs; +int start_vertex = rank * vertices_per_proc; +int end_vertex = (rank == num_procs - 1) ? num_vertices : start_vertex + vertices_per_proc; +\end{lstlisting} +Определяется, сколько вершин будет обрабатывать каждый процесс.\\ +start\_vertex и end\_vertex определяют диапазон вершин, который будет обрабатываться текущим процессом. +\begin{lstlisting} + if (rank == 0) { + local_distances[source_vertex] = 0.0; + } +\end{lstlisting} +Задаёт расстояние от начальной точки до самой себя = 0. +\begin{lstlisting} +local_distances.resize(num_vertices, std::numeric_limits::infinity()); +std::vector local_visited(num_vertices, false); +\end{lstlisting} +local\_distances инициализируется значениями бесконечности, указывая, что расстояния до всех вершин из начальной вершины еще не известны.\\ +local\_visited используется для отслеживания, какие вершины были посещены. +\begin{lstlisting}[language=c++, caption={Основной цикл, где происходит поиск минимума}] +bool DijkstrasAlgorithmParallel::run() { + // Initialize + ... + for (int iter = 0; iter < num_vertices - 1; ++iter) { + MinVertex local_min{std::numeric_limits::infinity(), -1}; + ... + // Searching for a global minimum + ... + // Updating distances + ... + } + return true; +} +\end{lstlisting} +\subsection{Метод post\_processing()} +Метод \texttt{post\_processing()} реализует копию вычисленных данных в output: +\begin{lstlisting}[language=c++, caption={Сам метод}] +bool DijkstrasAlgorithmParallel::post_processing() { + internal_order_test(); + int rank = world.rank(); + + if (rank == 0) { + auto* output = reinterpret_cast(taskData->outputs[0]); + std::copy(local_distances.begin(), local_distances.end(), output); + } + + return true; +} +\end{lstlisting} +\newpage +\section{Тестирование} +\subsection{Производительность} +Для оценки производительности алгоритма были проведены тесты, где генерировались случайные графы и измерялось время выполнения алгоритма. +В этих тестах используется два вида графов - цикличные и двудольные: +\begin{lstlisting}[language=c++, caption={Пример функционального теста}] +void create_cycle_graph( + std::shared_ptr& graph, + int num_vertices) { + for (int i = 0; i < num_vertices; ++i) { + double weight = static_cast(rand()) / RAND_MAX * 10.0 + 0.1; // Random weight between 0.1 and 10.0 + graph->add_edge(i, (i + 1) % num_vertices, weight); // Connect in a cycle + } +} + +void create_bipartite_graph( + std::shared_ptr& graph, + int num_vertices) { + int half = num_vertices / 2; + for (int u = 0; u < half; ++u) { + for (int v = half; v < num_vertices; ++v) { + double weight = static_cast(rand()) / RAND_MAX * 10.0 + 0.1; // Random weight between 0.1 and 10.0 + graph->add_edge(u, v, weight); + } + } +} +\end{lstlisting} +\subsection{Функциональные тесты} +Функциональные тесты проверяют корректность работы алгоритма на различных типах графов, включая: +\begin{itemize} + \item Связные графы + \item Несвязные графы + \item Пустые графы +\end{itemize} +Пример функционального теста, который проверяет работу алгоритма на простом графе: + +\begin{lstlisting}[language=c++, caption={Пример функционального теста}] +TEST(gusev_n_dijkstras_algorithm_mpi, TestDijkstra) { + boost::mpi::communicator world; + auto task_data = std::make_shared(); + + auto graph = std::make_shared(5); + graph->add_edge(0, 1, 2.0); + graph->add_edge(0, 2, 3.0); + graph->add_edge(1, 2, 1.0); + graph->add_edge(2, 0, 4.0); + graph->add_edge(3, 4, 5.0); + + task_data->inputs.push_back(reinterpret_cast(graph.get())); + task_data->inputs_count.push_back( + sizeof(gusev_n_dijkstras_algorithm_mpi::DijkstrasAlgorithmParallel::SparseGraphCRS)); + + std::vector output_data(5); + task_data->outputs.push_back(reinterpret_cast(output_data.data())); + task_data->outputs_count.push_back(output_data.size() * sizeof(double)); + + gusev_n_dijkstras_algorithm_mpi::DijkstrasAlgorithmParallel task(task_data); + ASSERT_TRUE(task.validation()); + ASSERT_TRUE(task.pre_processing()); + ASSERT_TRUE(task.run()); + ASSERT_TRUE(task.post_processing()); + + if (world.rank() == 0) { + std::vector expected_distances = {0.0, 2.0, 3.0, std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; + for (size_t i = 0; i < expected_distances.size(); ++i) { + if (std::isinf(expected_distances[i])) { + EXPECT_TRUE(std::isinf(output_data[i])) << "Distance to vertex " << i << " should be infinity"; + } else { + EXPECT_NEAR(output_data[i], expected_distances[i], 1e-6) << "Incorrect distance to vertex " << i; + } + } + } +} +\end{lstlisting} +Этот тест создает граф с 5 вершинами и несколькими ребрами, затем проверяет, правильно ли алгоритм находит кратчайшие расстояния от начальной вершины (0) до других вершин (некоторые вершины недостежимы, поэтому inf). +\newpage +\section{Заключение} +Реализация алгоритма Дейкстры с использованием CRS графов и параллельной обработки с помощью MPI позволяет эффективно находить кратчайшие пути в больших графах. Проведенные тесты подтвердили корректность и производительность алгоритма. + + +\end{document}