Изменения

Перейти к: навигация, поиск

Visibility graph и motion planning

3922 байта добавлено, 19:39, 4 сентября 2022
м
rollbackEdits.php mass rollback
== Visibility graph Нахождение любого пути между точками с препятствиями ==
{|align="right"
|-valign="top"
|[[Файл:notShort.png|300px|thumb|right|Такой путь не самый короткий]]
|}
Рассмотрим задачу нахождения кратчайшего пути от точки <tex> S </tex> до <tex> T </tex> с препятствиями. Для начала рассмотрим движение материальной точки. Случай, когда размером и формой движимого объекта пренебречь нельзя, случай произвольного полигона будет рассмотрен [[Visibility graph и motion planning#Motion planning|позднее]].
Для нахождения пути Эту задачу можно построить решить с помощью [[Трапецоидная карта | трапецоидную картутрапецоидной карты]]. По ней строится граф, ребра которого соединяют центры трапедоидов, соединить ребрами середины а также начальную и конечную вершины с серединами вертикальных сторон трапецоидов с их центрами и в этом . В таком графе любым алгоритмом поиска кратчайших путей (например, алгоритмом [[Алгоритм Дейкстры|Дейкстры]] или [[Алгоритм A*|A*]]) найти ищется путь от <tex> S </tex> до <tex> T </tex>. Однако, этот путь не будет абсолютно кратчайшиммежду начальной и конечной вершинами.
Этот алгоритм поиска пути через трапецоидную карту обширно используется, так как дает хорошее приближение, работает за <tex> O(n \log n) </tex> и занимает линейное количество памяти, в то время как точные решения в лучшем случае работают за <tex> O(n^2) </tex> времени и памяти (здесь и далее <tex> n </tex> Если точки лежат внутри одного трапецоида {{---}} количество всех вершин)ответ найден. Иначе идём из стартовой точки в центр её трапецоида, далее по построенным рёбрам ищем трапецоид содержащий финальную точку. Для этого можно использовать поиск в ширину или другой алгоритм нахождения кратчайшего пути в графе. В конечном итоге, соединяем середину последнего трапецоида с конечной вершиной.
Теперь рассмотрим Данный алгоритм работает за <tex> O(n \log n) </tex> и за линейное количество памяти и хорошо подходит для нахождения какого-нибудь пути между парой данных вершин. Но если нужно найти кратчайший путь, этот алгоритм не подходит, хоть и работает быстро. Однако, решения нахождения кратчайшего пути в лучшем случае работают за <tex> O(n^2) </tex> времени и памяти (здесь и далее <tex> n </tex> {{---}} количество всех вершин). == Нахождение кратчайшего пути между точками с препятствиями ===== Visibility graph ===Рассмотрим точное решение нахождения кратчайшего пути на плоскости между двумя точками с полигональными препятствиями с помощью построения графа видимости. После его построения, как и в случае с трапецоидной картой, кратчайший путь ищется любым стандартным алгоритмом поиска (например, алгоритмом [[Алгоритм Дейкстры|Дейкстры]] или [[Алгоритм A*|A*]]). Для этого нам потребуется лемма:простоты рассуждений начальную и конечную вершины будем считать вершинами полигонов.
{{Лемма
|about=О кратчайшем пути
|statement=
Любой кратчайший путь от <tex> S </tex> до <tex> T </tex> между двумя вершинами с полигональными препятствиями представляет собой ломаную, вершины которой {{---}} вершины полигонов.
|proof=
[[Файл:short.png|150px|thumb|right|Short cut]]
{{Определение
|definition =
Говорят, что вершина <tex> u </tex> ''видна''visibility graph(англ. mutually visible) из <tex> v </tex>, если отрезок <tex> uv </tex> не пересекает ни одного препятствия.}}{{Определение|definition =''Граф видимости'' (англ. visibility graph) {{---}} граф, вершины которого {{---}} вершины полигонов, а также <tex> S </tex> и <tex> T </tex>. Между вершинами <tex> u </tex> и <tex> v </tex> существует ребро, если из <tex> u </tex> <i> видна </i>(mutually visible) <tex> v </tex>.
}}
 
В худшем случае в таком графе может быть <tex> O(n^2) </tex> ребер. Однако по некоторым ребрам кратчайший путь точно не пройдет, и такие ребра из графа можно удалить.
{{Лемма
|about=О неиспользуемых вершинах
|statement=
[[Файл:edgeToDelete.png|150px|thumb|right|Удаляем <tex> BD </tex>]]
# Если существуют вершины <tex> A, B, C </tex> одного препятствия и вершина <tex> D </tex> такая, что поворот <tex> DBA </tex> не совпадает с поворотом <tex> DBC </tex>, то ребро <tex> DB </tex> не принадлежит кратчайшему пути и его можно удалить из графа. (См. поясняющую картинку справа)# Все внутренние вершины, кроме вырожденного случая, (начальная/конечная точка лежит внутри выпуклой оболочки фигуры) можно игнорировать.
|proof=
[[Файл:edgeNotToDelete.png|200px|thumb|right|Не удаляем <tex> BS </tex>]]
# Путь проходящий через ребро <tex> BD </tex> будет длиннее, чем через соседей точки <tex> B </tex>, так как по неравенству треугольника <tex> AB + BD < > AD </tex># Если случай не вырожденный, значит заход внутрь фигуры только увеличит суммарный путь, так как по неравенству треугольника расстояние между соседними выпуклыми вершинами всегда меньше суммы расстояний с учётом внутренней.
}}
По доказанным леммам любое ребро кратчайшего пути содержится в графе. Таким образом, таким образом для нахождения кратчайшего пути осталось найти кратчайший путь в этом графе от <tex> S </tex> начальной до <tex> T </tex>конечной вершины.
=== Построение visibility графа ===
==== Наивный алгоритм. <tex> O(n ^ 3) </tex> ====
Если делать наивно, т. е. для Для каждой пары вершин проверять проверяем, можно ли добавить ли такое ребро(между ними, то есть нет ли пересечений с полигонами. <tex> O(n^2) </tex> пар вершин и <tex> O(n)</tex> ребер, будет то есть <tex> O(n^3) </tex>.
==== Lee’s Algorithm. <tex> O(n ^ 2 \log n) </tex> ====
{|
|
[[Файл:Zam.png|300px|thumb|left|Заметание плоскости вращающимся лучом]]
[[Файл:Zamrefr1.png|250px|thumb|right|Обновление статуса заметающего луча]][[Файл:Zamrefr2.png|250px|thumb|right|Обновление статуса заметающего луча]][[Файл:Zamrefr3.png|250px|thumb|right|Обновление статуса заметающего луча]]Однако можно это сделать за <tex> O(n ^ 2 \log n) </tex>. Нам нужно решить следующую задачуИдея алгоритма проста: на плоскости дано множество отрезков (рёбер препятствий) и точка для каждой вершины найдем видимые из нее вершины. Если научиться делать это за <tex> v O(n \log n) </tex>. Найти все концы отрезков, видимые из точки задача решена, так как всего точек <tex> v n </tex>.
Будем заметать плоскость вращающимся лучом с началом в точке <tex> v </tex>. Статусом заметающего луча будут отрезкиДля каждой вершины будем рассматривать только правую половину плоскости, так как ребра, которые его пересекаютдолжны идти в левую половину, упорядоченные по возрастанию расстояния от точки <tex> v </tex> до точки пересечения. Точками событий будут концы отрезковисходить из вершин, для которых текущая вершина будет справа.
Итак, первым делом вершины Переформулируем задачу. Дано: точка <tex> w \in V </tex> сортируются по углу между лучом <tex> vw v </tex> и вертикальной полуосью, проходящей через <tex> v </tex> множество отрезков {{---}} луч <tex> l </tex> (достаточно рассматривать только точки, которые лежат правее <tex> v </tex>, так как все точки левее мы уже рассмотрели)ребер препятствий. Затем инициализируем статус отрезками, которые пересекают луч <tex> l </tex>. Далее начинается заметание плоскости. В порядке сортировки вершин для каждой из них выполняется проверкаНайти: видна ли вершина <tex> w </tex> из вершины <tex> v </tex>. Для этого нам достаточно проверить только пересечение заметающего луча с ближайшим отрезком в статусе. Если вершина видимамножество концов отрезков, необходимо добавить её в список видимых вершин. И, наконец, вне зависимости от видимости вершины, необходимо изменить статус заметающего луча. Для этого для текущей вершины <tex> w </tex> необходимо удалить из статуса все рёбра (отрезки), которые заканчиваются в этой вершине (лежат слева от прямой <tex> vw </tex>) и добавить все рёбра (отрезки), которые в ней начинаются (лежат справа от прямой <tex> vw v </tex>).
===== Псевдокод =====<pre>graph buildVisibilityGraph(Set<Segment> segments) Set<Vertex> vertices = getVertices(segments) //получаем все вершины препятствий vertices.add(s, t) //добавляем начальную и конечную вершину graph visibilityGraph(vertices) //изначально Для решения этой задачи будем использовать заметающий луч с началом в графе только вершины for Vertex v in vertices //для каждой вершины for Vertex w in getVisibleVertices(v) // добавляем в граф все видимые из нее вершины visibilityGraph.addEdge(v, w) return visibilityGraph</pre>Здесь функция getVisibleVertices(<tex> v </tex>) возвращет все видимые из точке <tex> v </tex> вершины и выглядит так:<pre>vector<Vertex> getVisibleVertices(vertex v) vector<Vertex> answer, currentVertices currentVertices.add(all w in vertices : w.x >= v.x) //рассматриваем только вершины правее v sort vertexes by angle //в порядке увеличения угла с // вертикальной прямойЕго статусом будут отрезки, проходящей через v heap<Segment> status status.add(all e in Edges : intersection(e, l) != null) //инициализируем статус for w in currentVertices if intersection(vw, status.closest) == null //если отрезок vw не пересекает ближайший // отрезок в статусе answer.add(w) // добавляем в ответ delete from status all edges ending in w //обновляем статус add in status all edges beginning in w return answer</pre>Вершин у нас <tex> n </tex>которые его пересекают, для каждой вершины мы выполяеем следующие шаги: сортировка (упорядоченные по возрастанию расстояния от точки <tex> O(n \log n) v </tex>), проверка до точки пересечения (<tex> O(1) </tex>), обновление статуса (суммарно выполняется за <tex> O(n \log n) </tex>, так как мы имеем <tex> O(n) </tex> ребер и каждое ребро мы добавляем и удаляем один раз (вставка происходит за <tex> O(\log n) </tex>, удаление можно сделать за <tex> O(1) </tex>, например, для каждой вершины сохраняя позицию входящих в нее . Точками событий будут концы отрезков)). Итого получаеем <tex> O(n) * O(n \log n) = O(n^2 \log n) </tex>. Что и требовалось доказать.
==== Overmars and Welzl’s Algorithm Пустим луч из рассматриваемой вершины <tex> Ov </tex> вертикально вверх и добавим в статус все отрезки, которые он пересекает, по увеличению расстояния до них. Теперь будем рассматривать точки <tex> w \in V </tex> в порядке сортировки по углу между <tex> v </tex> и вертикальной полуосью <tex> l </tex>. При таком обходе для проверки видимости вершины достаточно проверить пересечение с ближайшим к <tex> v </tex> отрезком, то есть первым в статусе(n ^ 2так как отрезки отсортированы по расстоянию до них) . Действительно, если вершина <tex> w </tex> ====[http:не видна, то отрезок <tex> vw </tex> пересекает несколько отрезков, лежащих перед <tex> w </igitur-archivetex>, а значит и ближайший.libraryВ противном случае все пересекаемые лучом отрезки лежат за вершиной <tex> w </tex> и пересечения отрезка <tex> vw </tex> с ближайшим отрезком не будет.uuВне зависимости от видимости вершины, необходимо изменить статус заметающего луча.nlДля этого необходимо удалить из статуса все отрезки, которые заканчиваются вершине <tex> w </mathtex> (лежат слева от прямой <tex> vw </2006-1214-201604tex>) и добавить все отрезки, которые в ней начинаются (лежат справа от прямой <tex> vw </overmars_88_new_methodstex>).pdf visibility graph при помощи rotation tree]
C помощью [http===== Псевдокод ===== graph buildVisibilityGraph(Set<Segment> segments) vertices = getVertices(segments) <tex> \cup\ \{s,\ t\} </tex> graph = visibilityGraph(vertices) '''for''' Vertex <tex>v</tex> '''in''' vertices '''for''' Vertex <tex>w</tex> '''in''' getVisibleVertices(<tex>v</tex>, segments) visibilityGraph.addEdge(<tex>v</tex>, <tex>w</tex>) '''return''' visibilityGraphЗдесь функция getVisibleVertices(<tex> v </tex>) возвращает все видимые из <tex> v </tex> вершины и выглядит так: Set<Vertex> getVisibleVertices(Vertex <tex>v</tex>, Set<Segment> segments) Set<Vertex> answer '''for''' Segment <tex>s</tex> '''in''' segments '''if''' intersect(<tex> s </tex>, <tex> l </tex>) status.add(<tex>s</tex>) '''for''' Point <tex>w</tex> '''in''' segments '''if''' <tex>v.x \leqslant w.x</bittex> currentVertices.lyadd(<tex>w</tex>) sort(currentVertices) by angle '''for''' Point <tex>w</1eEqTzk rotation tree] можно достичь асимптотики tex> '''in''' currentVertices '''if''' '''not''' intersect(<tex>vw</tex>, status.closest) answer.add(<tex>w</tex>) '''for''' Segment <tex>s</tex> ending in <tex>w</tex> status.delete(<tex>s</tex>) '''for''' Segment <tex>s</tex> beginning in <tex>w</tex> status.add(<tex>s</tex>) '''return''' answerВ качестве статуса нужно использовать структуру данных, позволяющую добавлять и удалять из нее отрезки за <tex> O(\log n) </tex> и извлекать минимум за <tex> O(1) </tex> или <tex> O(\log n) </tex>. В этом случае достигается асимптотика <tex> O(n^2\log n) </tex>, так как для каждой из <tex> n </tex> точек выполняется сортировка за <tex> O(n \log n) </tex>, обновление статуса (суммарно <tex> O(n \log n) </tex>, так как каждый отрезок добавляется и удаляется из статуса не более одного раза) и запросы ближайшего отрезка (<tex> O(\log n) </tex> или <tex> O(1) </tex> на точку, то есть <tex> O(n \log n) </tex> или <tex> O(n) </tex>). |[[Файл:Zamrefr1.png|250px|thumb|right|Обновление статуса заметающего луча: добавляем ребра <tex> w_1 w_5 </tex> и <tex> w_1 w_2 </tex> в статус]][[Файл:Zamrefr2.png|250px|thumb|right|Добавляем ребра <tex> w_3 w_2 </tex> и <tex> w_3 w_4 </tex> в статус]][[Файл:Zamrefr3.png|250px|thumb|right|Удаляем ребра <tex> w_3 w_2 </tex> и <tex> w_1 w_2 </tex> из статуса]]|}
== Motion planning ==
[[Файл:mink.png|200px|thumb|left|Раздуваем Изменяем препятствия]]
[[Файл:mink2.png|400px|thumb|right|Ищем путь для точки]]
Тут мы двигаем не точкуРассмотрим задачу нахождения кратчайшего пути, а произвольный когда движимый объект {{---}} это выпуклый полигон. Например, робот, которого надо доставить из начальной в конечную точку. Если мы его не можем полигон вращатьнельзя, просто считаем configuration spaceзадачу сводится к движению точки так: выбирается точка на полигоне, ткоторая принимается за начало координат. е. "обводим" В такой системе координат для каждого препятствия нашим полигоном (делаем считается [[Сумма Минковского (определение, вычисление)|сумму сумма Минковского]] препятствий и полигона, сдвинутого в начало координат какой-нибудь точкой) и получаем другие с полигоном. Получаются бОльшие препятствия, но зато теперь мы двигаем достаточно двигать выбранную точку. А это мы уже научились делать , что было описано выше.
Теперь рассмотрим случай, когда мы можем вращать полигон. Для начала построим [[Трапецоидная карта|трапецоидную карту]], как будто мы не можем вращать Если полигон. Теперь будем можно вращать полигон на малый угол, пока он не сделает полный оборот вокруг своей осизадача нахождения ''кратчайшего'' пути становится достаточно ресурсоёмка, и для каждого угла сделаем трапецоидную карту. Теперь разместим(мысленно) все карты друг над другом. Таким образом получится, что поворот на малый угол {{поэтому обычно рассматривают задачу нахождения какого---}} это движение вверх/вниз между слоями. Осталось [[Пересечение многоугольников (PSLG overlaying)|попересекать]] соседние слои и добавить нибудь пути между ними ребра (помним что первый и последний слои одинаковы) и уже в этом графе найти путьконечными точками.
При такой реализации в некоторых случаях у нас может возникнуть ошибка в повороте, так как в одной плоскости мы все можем делать точноПервый шаг решения этой задачи совпадает с предыдущим случаем: положения на соседних слоях могут допускатьсявыберем точку и построим [[Сумма Минковского (определение, а повернуть мы не сможемвычисление)|сумму Минковского]] препятствий с полигоном. Это лечится в основном двумя способами: измельчением угла поворота и изначальным сглаживанием углов полигона {{---}} повращать полигон на Рассмотрим малый угол <tex> +\epsilon </tex> и <tex> . Представим, что поворот полигона на этот угол {{---}} это движение вверх-\epsilon </tex> и объединить вниз между слоями, на каждом из которых посчитана сумма Минковского с исходнымполигоном, получив новый полигонповернутым на этот угол.
Так На каждом слое построим трапецоидную карту и граф, как эта задача достаточно ресурсоемкаописано в [[Visibility graph и motion planning#Нахождение пути между точками с препятствиями|начале]]. Если [[Пересечение многоугольников (PSLG overlaying)|пересечь]] соседние слои и добавить между их графами ребра, мы рассматриваем только наличие путиполучится один большой граф, а не нахождение кратчайшегов котором ищется кратчайший путь.
При таком подходе может возникнуть ошибка при пересечении слоев: на каждом слое состояния будут допустимые, а осуществить поворот физически будет невозможно. Обычно, эту проблему решают двумя способами: измельчением угла поворота и изначальным сглаживанием углов полигона. Первый способ повышает не только точность решения, но и вычислительную сложность задачи. Второй подход практически исключает возможность нахождения пути, когда его нет, но повышает вероятность "ненахождения" пути, когда он есть. == Источники информации ==* Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars (2008), Computational Geometry: Algorithms and Applications (3rd {{---}} Third edition){{---}} Springer, Springer2008. {{---}} Chapter 15. {{---Verlag, }} ISBN 978-3-540-77973-5 Chapter 15 page 324-331* [http://www.academia.edu/2845047/3D_Visibility_Graph статья про visibility graphsAcademia.edu]{{---}} 3D Visibility Graph* [http://habrahabr.ru/post/199256/ ХабрХабрахабр] {{---}} Motion planning: граф видимости, дорожные карты* [http://igitur-archive.library.uu.nl/math/2006-1214-201604/overmars_88_new_methods.pdf igitur-archive.library.uu.nl]{{---}} Visibility graph при помощи rotation tree за <tex>O(n^2)</tex>.
[[Категория: Вычислительная геометрия]]
 
== Ссылки ==
* [https://github.com/Igorjan94/cg/blob/master/include/cg/algorithms/visibilityGraph.h Github] {{---}} Реализация алгоритма за <tex> O(n^2 \log n) </tex>
1632
правки

Навигация