<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=RS42</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=RS42"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/RS42"/>
	<updated>2026-10-22T16:10:31Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24778</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24778"/>
		<updated>2017-11-30T23:25:04Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, версия MPI: intel mpi 5.0.1, компилятор: intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента (64 узла для размерности 2 для &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; точек, 4 узла для размерности 3 для &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; и для размерности 4 с &amp;lt;math&amp;gt;2^{22}&amp;lt;/math&amp;gt; точек), когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42&amp;diff=24777</id>
		<title>Участник:RS42</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42&amp;diff=24777"/>
		<updated>2017-11-30T23:24:20Z</updated>

		<summary type="html">&lt;p&gt;RS42: Содержимое страницы заменено на «Тимофеев Александр Евгеньевич, группа 403»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24776</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24776"/>
		<updated>2017-11-30T23:20:13Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, версия MPI: intel mpi 5.0.1, компилятор: intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента (64 узла для размерности 2 для &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; точек, 4 узла для размерности 3 для &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; и для размерности 4 с &amp;lt;math&amp;gt;2^{22}&amp;lt;/math&amp;gt; точек), когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24775</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24775"/>
		<updated>2017-11-30T23:16:37Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, версия MPI: intel mpi 5.0.1, компилятор: intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента (64 узла для размерности 2, 4 узла для размерностей 3 и 4), когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24774</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24774"/>
		<updated>2017-11-30T23:13:31Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, версия MPI: intel mpi 5.0.1, компилятор: intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента (64 узла для размерности 2, 8 — для 3, 4 — для 4), когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24772</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24772"/>
		<updated>2017-11-30T23:07:44Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, версия MPI: intel mpi 5.0.1, компилятор: intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24769</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24769"/>
		<updated>2017-11-30T22:52:12Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном. Хотя графики с линейным масштабом по времени только для того, чтобы быстро взглянуть и не задумываясь убедиться в том, что действительно есть какое-то ускорение. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24768</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24768"/>
		<updated>2017-11-30T22:47:37Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пунктах 1.3 и 1.8 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24767</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24767"/>
		<updated>2017-11-30T22:46:10Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}, 2^{23}, 2^{22}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24766</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24766"/>
		<updated>2017-11-30T22:44:22Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Это заметно на графиках, если зафиксировать число процессов. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24765</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24765"/>
		<updated>2017-11-30T22:41:44Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^{24}&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24764</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24764"/>
		<updated>2017-11-30T22:40:51Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
Из сделанных предположений следует, что сильную масштабируемость для данной реализации мы можем наблюдать в том случае, если доля вычислений распределения точек составляет основную часть вычислений. На графиках видно, как при &amp;lt;math&amp;gt;2^24&amp;lt;/math&amp;gt; входных точках время работы обратно пропорционально числу процессов до определенного момента, когда время работы упирается во время последовательной части программы (см. закон Амдала), и дальнейшим ростом числа вычислительных узлов никак не помочь. И чем меньше входных точек, тем слабее проявляется сильная масштабируемость.&lt;br /&gt;
С увеличением размерности задачи ситуация только ухудшается в силу возрастающей сложности операции построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24761</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24761"/>
		<updated>2017-11-30T22:22:23Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bcast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24760</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24760"/>
		<updated>2017-11-30T22:18:32Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах. Если проводить тестирование на точках, лежащих на сфере, то с увеличением&lt;br /&gt;
числа точек доля времени, ушедшего на построение новых граней, будет практически полностью составлять время работы всей программы.&lt;br /&gt;
&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24759</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24759"/>
		<updated>2017-11-30T22:14:02Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах.&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24758</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24758"/>
		<updated>2017-11-30T22:13:25Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Из описаний в пункте 1.3 можно сделать предположения о работе алгоритма:&lt;br /&gt;
1. С ростом числа исходных точек растет время как операции распределения точек, так и операции построения новых граней.&lt;br /&gt;
  В силу того, что тестирование проводилось на точках, распределенных в шаре, доля времени операции распределения точек от общего времени будет только&lt;br /&gt;
  увеличиваться, и в итоге сложность всего алгоритма растет линейно на данных тестах.&lt;br /&gt;
2. Чем больше размерность задачи, тем большая доля времени уходит на построение новых граней.&lt;br /&gt;
3. При избыточном числе процессов следует ожидать даже замедление работы программы из-за возросших накладных расходов на пересылки.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24756</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24756"/>
		<updated>2017-11-30T21:54:56Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24755</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24755"/>
		<updated>2017-11-30T21:54:35Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, что объясняется значительным превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24754</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24754"/>
		<updated>2017-11-30T21:52:27Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней и в самом начале работы алгоритма, когда один процесс производит рассылку выбранного им стартового симплекса.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
На Рис. 1 и Рис. 3 можно отметить аномалии при 4096 точках во входных данных, это объясняется значительным превышением времени на рассылки данных с помощью MPI_Bast() по отношению как к операции построения новых граней, так и операции распределения точек.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24753</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24753"/>
		<updated>2017-11-30T21:46:01Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней.&lt;br /&gt;
&lt;br /&gt;
Далее для наглядности представлены графики времени выполнения алгоритма как в логарифмическом масштабе времени, так и в линейном.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24752</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24752"/>
		<updated>2017-11-30T21:42:06Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3.&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24751</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24751"/>
		<updated>2017-11-30T21:41:09Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2. Все тесты проводились для чисел процессов &amp;lt;math&amp;gt;2^k, k = 0,..,9&amp;lt;/math&amp;gt; и количества точек во входных данных &amp;lt;math&amp;gt;2^k, k = 12,..,24, d = 2, 3 &amp;lt;/math&amp;gt; и&lt;br /&gt;
&amp;lt;math&amp;gt;2^k, k = 12,..,22, d = 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png| Рис. 1. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM2_time_lin.png| Рис. 2. &amp;lt;math&amp;gt;d=2&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png| Рис. 3. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM3_time_lin.png| Рис. 4. &amp;lt;math&amp;gt;d=3&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png| Рис. 5. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
Файл:DIM4_time_lin.png| Рис. 6. &amp;lt;math&amp;gt;d=4&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24741</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24741"/>
		<updated>2017-11-30T21:30:21Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
Операции обмена совершались только для передачи всем процессам вершины для построения новых граней.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png&lt;br /&gt;
Файл:DIM2_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png&lt;br /&gt;
Файл:DIM3_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png&lt;br /&gt;
Файл:DIM4_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24740</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24740"/>
		<updated>2017-11-30T21:26:47Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM2_time_log.png&lt;br /&gt;
Файл:DIM2_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM3_time_log.png&lt;br /&gt;
Файл:DIM3_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery perrow=2 widths=&amp;quot;500px&amp;quot; heights=&amp;quot;500px&amp;quot;&amp;gt;&lt;br /&gt;
Файл:DIM4_time_log.png&lt;br /&gt;
Файл:DIM4_time_lin.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24738</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24738"/>
		<updated>2017-11-30T21:21:18Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
&lt;br /&gt;
[[Файл:DIM2_time_log.png|left|500px]][[Файл:DIM2_time_lin.png|right|500px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:DIM3_time_log.png|left|500px]][[Файл:DIM3_time_lin.png|right|500px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:DIM4_time_log.png|left|500px]][[Файл:DIM4_time_lin.png|right|500px]]&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM4_time_log.png&amp;diff=24737</id>
		<title>Файл:DIM4 time log.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM4_time_log.png&amp;diff=24737"/>
		<updated>2017-11-30T21:16:44Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM4_time_lin.png&amp;diff=24736</id>
		<title>Файл:DIM4 time lin.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM4_time_lin.png&amp;diff=24736"/>
		<updated>2017-11-30T21:16:33Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM3_time_lin.png&amp;diff=24735</id>
		<title>Файл:DIM3 time lin.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM3_time_lin.png&amp;diff=24735"/>
		<updated>2017-11-30T21:16:22Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM2_time_lin.png&amp;diff=24732</id>
		<title>Файл:DIM2 time lin.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM2_time_lin.png&amp;diff=24732"/>
		<updated>2017-11-30T20:37:23Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM3_time_log.png&amp;diff=24731</id>
		<title>Файл:DIM3 time log.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM3_time_log.png&amp;diff=24731"/>
		<updated>2017-11-30T20:37:13Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM2_time_log.png&amp;diff=24730</id>
		<title>Файл:DIM2 time log.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:DIM2_time_log.png&amp;diff=24730"/>
		<updated>2017-11-30T20:36:51Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24577</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24577"/>
		<updated>2017-11-29T22:21:35Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики вычислительной среды:&lt;br /&gt;
суперкомпьютер Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
Для минимизации обменов производились избыточные вычисления: на каждом из узлов по ходу работы алгоритма строилась одна и та же оболочка.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24496</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24496"/>
		<updated>2017-11-29T17:49:34Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики среды:&lt;br /&gt;
Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24495</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24495"/>
		<updated>2017-11-29T17:49:21Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Характеристики среды:&lt;br /&gt;
Ломоносов, intel mpi 5.0.1, intel icc 15.0.0, ключи: -std=c++11, -O3&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24492</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24492"/>
		<updated>2017-11-29T16:59:55Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
Тестирование проводилось на множестве исходных точек, равномерно распределенных в единичном &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерном шаре. Для генерации входных данных использовался пакет Geometric Object Generators из библиотеки CGAL 4.8.2.&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24488</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24488"/>
		<updated>2017-11-29T16:35:04Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая может быть оценена&lt;br /&gt;
как &amp;lt;math&amp;gt;O(f_r)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24487</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24487"/>
		<updated>2017-11-29T16:11:44Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
Параллельная сложность алгоритма в случае рассмотренной выше схемы будет равна сложности построения новых граней, которая ...&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24459</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24459"/>
		<updated>2017-11-29T14:56:53Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Для начала рассмотрим простой параллельный вариант, требующий наименьшее количество пересылок.&lt;br /&gt;
[[Файл:Parallel1.png|center|500px]]&lt;br /&gt;
Мы можем распределить исходные точки по нескольким процессам, на каждом получить по одной выпуклой оболочке и переслать вершины итоговых построений одному процессу, который запустит процесс построения выпуклой оболочки, распределив полученные точки по множествам внешних точек&lt;br /&gt;
уже построенной им выпуклой оболочки. Такой процесс слияния оболочек можно производить и попарно между процессами. Но теперь стоит вопрос эффективности шага распределения точек по граням. В случае простого перебора сложность слишком высока и выигрыша по сравнению с последовательной реализацией нет. Было бы иначе, если существовал недорогой способ&lt;br /&gt;
пройти по выпуклой оболочке и собрать ее вершины таким образом, чтобы близкие друг к другу вершины были также близки и в передаваемом массиве.&lt;br /&gt;
Тогда процесс распределения можно организовать более эффективно. Возьмем некую точку &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и попытаемся определить, лежит ли она внутри выпуклой оболочки или же снаружи, и если снаружи, то найдем одну грань, которая видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Возьмем некую грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и посчитаем расстояние от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и соседних к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; граней до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;. Выберем наибольшее из найденных расстояний. Если оно положительно, то точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; снаружи. Если оно отрицательно и является расстоянием от &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; до &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, то в силу того, что мы рассматриваем выпуклую оболочку, грань &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет ближайшей к &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; и мы можем с уверенностью сказать, что &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; внутри. Если же наибольшее расстояние соответствует некоей иной грани из окрестности &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то продолжим поиск таким же способом. В силу того, что последовательные точки в массиве нераспределенных точек близки друг к другу, будет затрачено меньше времени на распределение, нежели полным перебором.&lt;br /&gt;
&lt;br /&gt;
Ранее было уже указано на то, что существенную часть работы алгоритма составляет процесс распределения точек по множествам внешних точек.&lt;br /&gt;
Этот процесс возможно распараллелить, когда как процесс построения новых граней имеет по большей части последовательную природу, и распараллелить там возможно разве что лишь решение системы. Итак, рассмотрим граф для процедуры распределения точек по множествам внешних точек. Эта процедура вызывается в двух местах алгоритма: один раз как распределение всех исходных точек по граням симплекса и при распределении точек из множеств старых граней по множествам для только что построенных.&lt;br /&gt;
[[Файл:Graph2.png|center|500px]]&lt;br /&gt;
Узлы с 1 по 5 содержат множества точек, которые требуется распределить по одному и тому же для каждого из узлов списку граней. Затем для каждой из граней этого списка определяется наиболее отдаленная точка, и алгоритм продолжает свою работу.&lt;br /&gt;
Конечно, можно рассмотреть и противоположную схему: рассылка точек по узлам с гранями, но реализация на практике такой схемы намного сложнее.&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Graph2.png&amp;diff=24457</id>
		<title>Файл:Graph2.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Graph2.png&amp;diff=24457"/>
		<updated>2017-11-29T14:40:13Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Parallel1.png&amp;diff=24449</id>
		<title>Файл:Parallel1.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Parallel1.png&amp;diff=24449"/>
		<updated>2017-11-29T13:48:33Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24446</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24446"/>
		<updated>2017-11-29T10:58:53Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребер. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24445</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24445"/>
		<updated>2017-11-29T10:54:05Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Математическое описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Следует отметить, что алгоритм представляет грани как &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-симплексы, а ребра как &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;-симплексы. Таким образом, если в трехмерном случае привычная нам грань, образованная из четырех точек в одной плоскости, будет четырехугольником, то в представлении через симплексы она разбита на две грани в виде двух треугольников, разделенных одним общим ребром. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24434</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24434"/>
		<updated>2017-11-28T22:16:31Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24433</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24433"/>
		<updated>2017-11-28T22:15:45Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24432</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24432"/>
		<updated>2017-11-28T22:15:38Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
!&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24431</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24431"/>
		<updated>2017-11-28T22:13:04Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Входные и выходные данные алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
Входные данные представляют из себя массив исходных точек. Требуется, чтобы из них можно было выбрать как минимум &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точку для начального &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-симплекса.&lt;br /&gt;
На выходе получаем список граней, список ребер и вершины полученной выпуклой оболочки. Для граней известны их соседи через ребра и уравнения гиперплоскости.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24430</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24430"/>
		<updated>2017-11-28T22:05:24Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Входные и выходные данные алгоритма ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24429</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24429"/>
		<updated>2017-11-28T22:00:03Z</updated>

		<summary type="html">&lt;p&gt;RS42: /* Свойства и структура алгоритмов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность алгоритма ==&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; число вершин итоговой выпуклой оболочки, а &amp;lt;math&amp;gt;f_r&amp;lt;/math&amp;gt; примем за максимальное число граней для &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt; вершин. Известно, что &amp;lt;math&amp;gt;f_r = O(r^{\lfloor d/2 \rfloor}/ \lfloor d/2 \rfloor!)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Авторы алгоритма приводят верхнюю оценку сложности для так называемого сбалансированного случая, и она составляет &amp;lt;math&amp;gt;O(n \ log \ r) &amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \leq 3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;O(n f_r / r)&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;d \geq 4 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24428</id>
		<title>Участник:RS42/Quickhull</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:RS42/Quickhull&amp;diff=24428"/>
		<updated>2017-11-28T21:39:01Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Тимофеев Александр Евгеньевич, группа 403&lt;br /&gt;
= Свойства и структура алгоритмов =&lt;br /&gt;
&lt;br /&gt;
== Общее описание алгоритма ==&lt;br /&gt;
Задача построения выпуклой оболочки является одной из важных проблем вычислительной геометрии. Выпуклую оболочку множества точек &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; можно определить следующим образом: это пересечение всевозможных выпуклых множеств, содержащих &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. В случае конечного числа &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; точек из &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;:&lt;br /&gt;
:&amp;lt;math&amp;gt;\mathrm{Conv}(X)=\left\{\left.\sum_{i=1}^{n} \alpha_i x_i\  \right| (\forall i: \alpha_i\ge 0)\wedge \sum_{i=1}^{n} \alpha_i=1 \right\}.&amp;lt;/math&amp;gt;&lt;br /&gt;
Алгоритм Quickhull позволяет провести построение в пространстве произвольной размерности &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;. Результатом работы является множество &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерных граней (facets) и вершин (vertices)&lt;br /&gt;
полученной выпуклой оболочки.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание алгоритма ==&lt;br /&gt;
Наложим на множество &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt; следующее условие: любые &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точки аффинно независимы (что тождественно тому, что они не лежат в одной &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt;-мерной плоскости).&lt;br /&gt;
Выпуклая оболочка будет задаваться списком вершин и граней. Для каждой грани известны ее вершины, ее соседние грани и уравнение гиперплоскости, в которой эта грань лежит.&lt;br /&gt;
Ребро (ridge) определяется как пересечение двух соседних граней и имеет размерность &amp;lt;math&amp;gt;d-2&amp;lt;/math&amp;gt;. Граница грани состоит из ребер.&lt;br /&gt;
&lt;br /&gt;
Нам понадобится вычисление ориентированного расстояния от некоей точки &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; до гиперплоскости (signed distance to hyperplane). Зададим начало координат в точке &amp;lt;math&amp;gt;O&amp;lt;/math&amp;gt;, плоскость определяется единичной нормалью&lt;br /&gt;
&amp;lt;math&amp;gt;\vec n&amp;lt;/math&amp;gt; и смещением &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; относительно начала координат. Тогда ориентированное расстояние будет равно &amp;lt;math&amp;gt;(\vec x, \vec n) + h&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\vec x = x - O&amp;lt;/math&amp;gt;. Точки на гиперплоскости удовлетворяют, собственно говоря, ее уравнению &amp;lt;math&amp;gt;(\vec x, \vec n) + h = 0 &amp;lt;/math&amp;gt;.&lt;br /&gt;
Если ориентированное расстояние положительно, то можно сказать, что точка находится выше плоскости (или плоскость видна из точки). В нашем случае все грани выпуклой оболочки во время построения будут иметь&lt;br /&gt;
такие нормали и смещения, что для точек внутри оболочки расстояния до каждой плоскости будут неположительными.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим сам алгоритм:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
создать начальный d-симплекс&lt;br /&gt;
для каждой грани F&lt;br /&gt;
  для каждой нераспределенной точки p&lt;br /&gt;
    если p находится над F&lt;br /&gt;
      добавить p во множество внешних точек грани F&lt;br /&gt;
для каждой грани F с непустым множеством внешних точек&lt;br /&gt;
  выбрать наиболее отдаленную точку p из списка внешних точек грани F&lt;br /&gt;
  добавить F во множество видимых граней V&lt;br /&gt;
  для каждого непосещенного соседа N для граней из V&lt;br /&gt;
    если N видна из p&lt;br /&gt;
      добавить N в V&lt;br /&gt;
  граница множества V будет набором граничных ребер H&lt;br /&gt;
  для каждого ребра R из H&lt;br /&gt;
    построить новую грань из R и p&lt;br /&gt;
     соединить построенную грань с ее соседями&lt;br /&gt;
  для каждой новой грани G&lt;br /&gt;
    для каждой нераспределенной точки q из объединения множеств внешних точек граней из V&lt;br /&gt;
      если G видна из q&lt;br /&gt;
        добавить q во множество внешних точек G&lt;br /&gt;
  удалить грани из V&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Стартовый симплекс, который по ходу работы алгоритма будет расти до окончательной оболочки, можно создать из любых &amp;lt;math&amp;gt;d+1&amp;lt;/math&amp;gt; точек, исходя из условия на &amp;lt;math&amp;gt;X&amp;lt;/math&amp;gt;. Но в реальности это условие общего положения (general position) можно заменить на требование возможности выбрать&lt;br /&gt;
&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-мерный симплекс. Имеет смысл выбрать его таким образом, чтобы как можно больше точек уже оказалось в нем, поэтому его вершинами следует сделать точки с минимальными и максимальными координатами. Если таких точек не будет хватать, произведем добор произвольными.&lt;br /&gt;
&lt;br /&gt;
Дальнейшие шаги алгоритма позволяют добавлять к промежуточной выпуклой оболочке новые вершины. По сути выбирается оптимальная в каком-то смысле внешняя точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, а затем определяется множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt; видимых из этой точки граней. Данные видимые грани отделены от невидимых&lt;br /&gt;
множеством граничных ребер (horizon ridges). Далее граничные ребра и точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; образуют&lt;br /&gt;
новые грани, видимые грани удаляются. Тем самым совершен переход к новой промежуточной выпуклой оболочке. Такое описание является обобщенным и ему также соответствует randomized incremental algorithm, в котором точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; выбирается случайным образом. В нашем случае точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; будет наиболее отдаленной точкой от плоскости текущей рассматриваемой грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, что однако же не гарантирует&lt;br /&gt;
присутствие &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; в итоговой выпуклой оболочке.&lt;br /&gt;
Также можно выбрать &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; как наиболее отдаленную точку среди всех пар (грань-&amp;quot;самая дальняя точка этой грани&amp;quot;), но такой выбор на каждом шаге только увеличит время работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Как решается проблема определения граничных ребер, когда уже выбрана точка &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;?&lt;br /&gt;
Множество внешних точек (outside set) грани &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; представляет из себя множество точек (возможно не всех), из которых эта грань видна. Поэтому если &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt; из множества внешних точек грани&lt;br /&gt;
&amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;, то это означает, что &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; будет видна из &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;, и следует начать поиск границы именно с соседей &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Пусть &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; - соседняя к &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; грань. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; видна, то следует проверить таким же образом и соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; не видна, то мы наткнулись на участок границы, и рассматривать соседей &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; нет смысла.&lt;br /&gt;
&lt;br /&gt;
Получив граничные ребра, строим новые грани. Требуется обновить списки соседей и понять, какие внешние точки были захвачены. Захватить мы могли только те точки, которые были внешними для уничтоженных на этом шаге граней. То есть множество таких точек - это объединение множеств внешних точек граней из &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;. Распределяем эти точки по множествам внешних точек для новых граней.&lt;br /&gt;
&lt;br /&gt;
Алгоритм будет продолжать свою работу до тех пор, пока не останется ни одного пустого множества внешних точек.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро алгоритма ==&lt;br /&gt;
В алгоритме можно выделить две трудоемкие части:&lt;br /&gt;
&lt;br /&gt;
1. Распределение точек по множествам внешних точек.&lt;br /&gt;
&lt;br /&gt;
Элементарная операция на данном этапе - вычисление расстояния от точки до плоскости, сложность &amp;lt;math&amp;gt;O(d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Построение новых граней.&lt;br /&gt;
&lt;br /&gt;
Основная операция - нахождение гиперплоскости по точкам, сложность &amp;lt;math&amp;gt;O(d^3)&amp;lt;/math&amp;gt;. На практике также присутствуют расходы по работе со структурами данных, представляющих выпуклую оболочку.&lt;br /&gt;
&lt;br /&gt;
== Макроструктура алгоритма ==&lt;br /&gt;
Рассмотрим подробнее процесс построения новой грани.&lt;br /&gt;
Она получается путем взятия одного ребра (&amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; точка) из множества граничных ребер и прибавлением к этому ребру выбранной на данном этапе вершины. Тем самым возможно определение &amp;lt;math&amp;gt;d-1&amp;lt;/math&amp;gt; нового ребра, которые вместе с исходным составляют одну грань. Но полученные ребра также принадлежат и другим граням из&lt;br /&gt;
строящегося конуса, которые попарно граничат с текущей гранью как раз по каждому из ребру. Для отслеживания уже построенных ребер можно использовать хэш-таблицу.&lt;br /&gt;
Нахождение уравнения гиперплоскости по точкам &amp;lt;math&amp;gt;{x_i}, i=1,..,d&amp;lt;/math&amp;gt; может сводиться к решению системы &amp;lt;math&amp;gt;An=0&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; - матрица размера &amp;lt;math&amp;gt;(d-1) \times d&amp;lt;/math&amp;gt;, строки которой представляют из себя транспонированные векторы&lt;br /&gt;
&amp;lt;math&amp;gt;x_i-x_1 , i=2,..,d&amp;lt;/math&amp;gt;. Например, это можно сделать с помощью &amp;lt;math&amp;gt;LUP&amp;lt;/math&amp;gt;. Затем несложно отыскать &amp;lt;math&amp;gt;f=-(n,x_1)&amp;lt;/math&amp;gt;. Далее следует убедиться, что ориентированное расстояние для точек внутри выпуклой оболочки будет отрицательно,&lt;br /&gt;
для этого используется некая точка &amp;lt;math&amp;gt;c&amp;lt;/math&amp;gt;, заведомо лежащая внутри.&lt;br /&gt;
Ее можно выбрать в самом начале в качестве среднего значения точек&lt;br /&gt;
начального симплекса.&lt;br /&gt;
&lt;br /&gt;
== Схема реализации последовательного алгоритма ==&lt;br /&gt;
[[Файл:Diagram1.png|center|1000px]]&lt;br /&gt;
&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
== Особенности реализации последовательного алгоритма ==&lt;br /&gt;
== Локальность данных и вычислений ==&lt;br /&gt;
== Возможные способы и особенности параллельной реализации алгоритма ==&lt;br /&gt;
== Масштабируемость алгоритма и его реализации ==&lt;br /&gt;
&lt;br /&gt;
== Динамические характеристики и эффективность реализации алгоритма ==&lt;br /&gt;
&lt;br /&gt;
== Выводы для классов архитектур ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Существующие реализации алгоритма ==&lt;br /&gt;
На момент написания данной статьи о существовании реализации алгоритма с помощью средств параллельных вычислений ничего неизвестно.&lt;br /&gt;
В [https://arxiv.org/pdf/1201.2936.pdf данной] статье предложена реализация с использованием CUDA.&lt;br /&gt;
&lt;br /&gt;
Последовательные реализации:&lt;br /&gt;
* [http://qhull.org Qhull] - каноничная реализация от авторов алгоритма, обладающая широкими возможностями. Есть готовые пакеты во многих дистрибутивах.&lt;br /&gt;
* https://github.com/tomilov/quickhull - более компактная реализация на C++, описание доступно в виде [https://habrahabr.ru/post/245221/ статьи].&lt;br /&gt;
&lt;br /&gt;
= Литература =&lt;br /&gt;
*Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., &amp;quot;The Quickhull algorithm for convex hulls,&amp;quot; ACM Trans. on Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org&lt;br /&gt;
*Clarkson , K. L., Mehlhorn , K., and Seidel , R. 1993. Four results on randomized incremental constructions. Comput. Geom. Theory Appl. 3, 185–211. Also in Lecture Notes in Computer Science. Vol. 577, pp. 463– 474.&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Diagram1.png&amp;diff=24427</id>
		<title>Файл:Diagram1.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Diagram1.png&amp;diff=24427"/>
		<updated>2017-11-28T21:35:36Z</updated>

		<summary type="html">&lt;p&gt;RS42: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>RS42</name></author>
	</entry>
</feed>