Mathematica: Непересекающиеся отрезки

Как мы можем сказать Mathematica, чтобы он дал нам набор непересекающихся прямых? В этом случае две прямые пересекаются, если они имеют общую точку (не конечную точку). Рассмотрим этот простой случай:

l1 = {{-1, 0}, {1, 0}};
l2 = {{0, -1}, {0, 1}};
lines = {l1, l2};

Идея состоит в том, чтобы создать функцию, которая по заданному набору строк возвращает набор непересекающихся линий. Если такая функция существует, скажем, split, тогда вывод

split[lines]

было бы

{
 {{-1, 0}, {0,0}},
 {{ 0, 0}, {1,0}}, 
 {{ 0,-1}, {0,0}}, 
 {{ 0, 0}, {0,1}}
}

Функция обнаружила, что {0,0} является пересечением между двумя линиями в наборе, и, чтобы иметь непересекающиеся линии, она разбила сегменты линий на пересечениях, создав еще 2 линии. Этот процесс усложняется, если исходный ввод содержит больше строк. Кто-нибудь знает, как сделать это эффективно в Mathematica без использования циклов? Может быть полезно знать алгоритм, чтобы найти, если две линии пересекаются.

Примечание

Этот вопрос является второй частью моей попытки выяснить, как создавать каркасы в Mathematica с удалением скрытой строки. Пожалуйста, не стесняйтесь добавлять более подходящие теги.


person jmlopez    schedule 16.06.2011    source источник
comment
У меня здесь нет Mma, но вы хотите использовать стандартную линейную алгебру для представления каждой линии как A.{x, y}=c и найти точку, в которой уравнения для обеих линий верны, используя LinearSolve. Затем убедитесь, что решение находится между концами двух заданных отрезков. Если это так, разорвите линии в этой точке. Как и в случае с моим ответом на ваш предыдущий вопрос, вы хотите сделать это с Tuples[Sort[lines],{2}].   -  person Verbeia    schedule 16.06.2011
comment
@Verbeia, что должен делать Tuples[Sort[lines], {2}]? давайте предположим, что lines определено, как в моем посте.   -  person jmlopez    schedule 16.06.2011
comment
Функция Tuples необходима только в том случае, если у вас есть список из более чем двух строк, которые вы проверяете, и вы хотите проверить все возможные пары строк. Все, что он делает, это создает список всех возможных пар элементов из списка, содержащего более двух элементов. Ответ ACL ниже касается проблемы дубликатов и может быть лучшим решением, чем Tuples.   -  person Verbeia    schedule 16.06.2011


Ответы (2)


если вы предполагаете, что разделение существует, вам нужно применить его ко всем парам; они могут быть произведены

ClearAll[permsnodups];
permsnodups[lp_] := DeleteDuplicates[Permutations[lp, {2}],
   ((#1[[1]] == #2[[1]]) &&(#1[[2]] == #2[[2]]) || 
   (#1[[1]] == #2[[2]]) && (#1[[2]] == #2[[1]])) &]

который делает это: permsnodups[{a, b, c, d}] дает {{a, b}, {a, c}, {a, d}, {b, c}, {b, d}, {c, d}}, поверх которого вы можете отобразить свою функцию split (т.е. это все пары, убедившись, что если {a,b} есть, то {b,a} нет, так как тогда вы выполняете вдвое больше работы без причины - это похоже на выполнение $\sum_{i,j>i}$ в отличие от $\sum_{i,j}$).

РЕДАКТИРОВАТЬ: Вот реализация split (я застрял без доступа к Интернету в течение получаса или около того, поэтому разработал соответствующие уравнения вручную, и это не основано на ссылке, которую вы дали, и это не оптимизировано или красиво):

ClearAll[split2]
split2[{{ai_, bi_}, {ci_, di_}}] := Module[
{g1, g2, a, b, c, d, x0, y0, alpha, beta},
(*make sure that a is to the left of b*)

If[ai[[1]] > bi[[1]], {a, b} = {bi, ai}, {a, b} = {ai, bi}];
If[ci[[1]] > di[[1]], {c, d} = {di, ci}, {c, d} = {ci, di}];
g1 = (b[[2]] - a[[2]])/(b[[1]] - a[[1]]);
g2 = (d[[2]] - c[[2]])/(d[[1]] - c[[1]]);
If[g2 \[Equal] g1,
    {{a, b}, {c, d}},(*they're parallel*)

alpha = a[[2]] - g1*a[[1]];
    beta = c[[2]] - g2*c[[1]];
    x0 = (alpha - beta)/(g2 - g1);(*intersection x*)

If[(a[[1]] < x0 < b[[1]]) && (c[[1]] < x0 < 
   d[[1]]),(*they do intersect*)
            y0 = alpha + g1*x0;
            {{a, #}, {#, b}, {c, #}, {#, d}} &@{x0, y0},
            {{a, b}, {c, d}}(*they don't intersect after all*)]]]

(на самом деле это ужасно медленно и уродливо). В любом случае, вы можете видеть, что это работает следующим образом:

Manipulate[
Grid[{{Graphics[{Line[{p1, p2}, VertexColors \[Rule] {Red, Green}], 
  Line[{p3, p4}]},
        PlotRange \[Rule] 3, Axes \[Rule] True],
        (*Reap@split2[{{p1,p2},{p3,p4}}]//Last,*)
        If[
            Length@split2[{{p1, p2}, {p3, p4}}] \[Equal] 2,
            "not intersecting",
            "intersecting"]}}],
{{p1, {0, 1}}, Locator}, {{p2, {1, 1}}, Locator},
{{p3, {2.3, -.1}}, Locator}, {{p4, {2, 1}}, Locator}]

который производит такие вещи, как

введите здесь описание изображения

и

введите здесь описание изображения

(вы можете перемещать локаторы). Имейте в виду, мой split2 делится на ноль всякий раз, когда одна из линий вертикальна (это можно исправить, но я этого не сделал).

В любом случае это все очень медленно и некрасиво. Это можно было бы сделать быстрее, скомпилировав и сделав список (и используя ссылку, которую вы дали), но мой текущий перерыв на кофе закончился (или закончился полчаса назад). Я постараюсь вернуться к этому позже.

Между тем, спросите, есть ли какие-либо конкретные вопросы (например, если вы не видите, что разрывается для вертикальных линий). И обратите внимание, что хотя это делает то, что вы просите, если вы сопоставляете список строк, вы получите рваный список, который вам придется сгладить. Но это то, что вы просили :)

person acl    schedule 16.06.2011
comment
Не могли бы вы быстро реализовать split для полноты картины? Я не очень хорошо знаком с функциями отображения в Mathematica. Извините, что спрашиваю, но если вы отредактируете свой ответ с помощью функции разделения, не могли бы вы применить его к примеру с 3 пересекающимися линиями. Произнесите: l1 = {{-1, 0}, {1, 0}}; l2 = {{0, -1}, {0, 1}}; l3 = {{-1,1},{1,1}}; lines = {l1, l2, l3}. - person jmlopez; 16.06.2011
comment
вы ответили на вопрос, но это принесло еще один. Я подумаю над этим еще немного и опубликую в редактировании, так как нет смысла начинать еще один. - person jmlopez; 17.06.2011

Для определения пересечения вы также можете использовать следующий параметрический подход, который не страдает от обычных проблем методов, включающих декартовы уравнения (т.е. деление на ноль...):

f[t_, l_List] := l[[1]] + t (l[[2]] - l[[1]])
split[l1_, l2_] := Module[{s},
  If[(s = ToRules@
       Reduce[f[t1, l1]==f[t2, l2] && 0 <t2< 1 && 0 <t1< 1, {t1,t2},Reals]) =={},
   Return[{l1, l2}],
   Return[{{f[0, l1], f[t1, l1] /. s}, {f[1, l1], f[t1, l1] /. s},
           {f[0, l2], f[t2, l2] /. s}, {f[1, l2], f[t2, l2] /. s}}]
   ]]
person Dr. belisarius    schedule 17.06.2011
comment
Спасибо, Велизарий. Это очень хороший способ разделения линий на пересечении. Меня немного беспокоит эффективность. Эту функцию придется выполнять довольно много. В любом случае, я думаю, что смогу что-то придумать с вашим кодом и кодом, предоставленным acl. Я отредактирую свой вопрос, как только у меня будет более солидный случай. - person jmlopez; 17.06.2011
comment
@jmlopez в моем бедном ноутбуке приблизительная производительность составляет 8 секунд на 10 тысяч пересечений. - person Dr. belisarius; 17.06.2011
comment
@belisarius Я не думаю, что Reduce можно скомпилировать. Тот, который я дал, неуклюж, но, по крайней мере, может быть автоматически скомпилирован в C (после небольшого массажа). - person acl; 17.06.2011
comment
@acl Мой пытался тонким способом сказать, что ваш split2[{{{1, 0}, {-1, 0}}, {{0, 1}, {0, -1}}}] терпит неудачу :) - person Dr. belisarius; 17.06.2011
comment
@acl Я уверен, что почти все лучше, чем Reduce от точки зрения производительности :) - person Dr. belisarius; 17.06.2011
comment
@belisarius да, он делится на ноль :) ничего, что нельзя было бы исправить парой дополнительных If (которые также можно скомпилировать) :) - person acl; 17.06.2011
comment
@belisarius хорошо, я должен был проверить свои старые школьные учебники: метод, указанный в ссылке в вопросе, нуждался бы только в одном If для проверки деления на ноль, и он тоже компилируется ... Ну ладно. - person acl; 17.06.2011