Двойная рекурсия по kd-деревьям для нахождения наиболее близкого подхода между двумя наборами точек - PullRequest
1 голос
/ 30 октября 2019

Я построил kd-деревья для двух наборов точек, чтобы найти наиболее близкое бихроматическое спаривание между двумя наборами:

enter image description here

kd-деревья хранятся в виде словарей python, которые можно найти в приведенном ниже коде, и передаются в функцию ('closest'), которая предназначена для одновременного рекурсивного анализа обоих деревьев, чтобы найти наиболее близкий подход между наборами. Это сделано для того, чтобы избежать грубой силы проблемы.

Моя первая попытка основана на ответе на этот вопрос . С этой попыткой я не могу найти условие, которое заставляет функцию «отскакивать», когда она попадает на лист, то есть оператор if, предназначенный для возврата минимального расстояния между листьями и существующего минимума, никогда не достигается.

Первая попытка - полный код предоставлен для контекста, этот вопрос относится только к функции 'ближайший':

from operator import itemgetter
import math
import time
import pprint
import numpy as np


# builds the trees
def build_kd_tree(ar, depth=0, k=2):
    if len(ar) <= 0:
        return None
    axis = depth % k
    sorted_ar = sorted(ar, key=itemgetter(axis))
    idx = int(math.floor(len(ar)/2))
    return {
       'point': sorted_ar[idx],
       'left': build_kd_tree(sorted_ar[:idx], depth + 1),
       'right': build_kd_tree(sorted_ar[idx+1:], depth + 1)
    }


def min_dist(p1, p2):
    d1 = math.hypot(p1[0] - p2[0], p1[1] - p2[1])
    return d1


# function designed to simultaneously recurse two trees to find the closest approach
def closest(k1,k2,lim=float("inf")):

    cc1 = [k1[value] for value in k1 if k1[value] is not None and type(k1[value]) == dict]
    cc2 = [k2[value] for value in k2 if k2[value] is not None and type(k2[value]) == dict]

    if len(cc1) == 0 and len(cc2) == 0:
        return min(lim, min_dist(k1['point'], k2['point']))

    for md, c1, c2 in sorted((min_dist(c1['point'], c2['point']), c1, c2) for c1 in cc1 for c2 in cc2):
        if md >= lim: break
        lim = min(lim, closest(c1, c2, lim))
    return lim

# some example coordinates
px_coords=np.array([299398.56,299402.16,299410.25,299419.7,299434.97,299443.75,299454.1,299465.3,299477.,299488.25,299496.8,299499.5,299501.28,299504.,299511.62,299520.62,299527.8,299530.06,299530.06,299525.12,299520.2,299513.88,299508.5,299500.84,299487.34,299474.78,299458.6,299444.66,299429.8,299415.4,299404.84,299399.47,299398.56,299398.56])
py_coords=np.array([822975.2,822989.56,823001.25,823005.3,823006.7,823005.06,823001.06,822993.4,822977.2,822961.,822943.94,822933.6,822925.06,822919.7,822916.94,822912.94,822906.6,822897.6,822886.8,822869.75,822860.75,822855.8,822855.4,822857.2,822863.44,822866.6,822870.6,822876.94,822886.8,822903.,822920.3,822937.44,822954.94,822975.2])
qx_coords=np.array([384072.1,384073.2,384078.9,384085.7,384092.47,384095.3,384097.12,384097.12,384093.9,384088.9,384082.47,384078.9,384076.03,384074.97,384073.53,384072.1])
qy_coords=np.array([780996.8,781001.1,781003.6,781003.6,780998.25,780993.25,780987.9,780981.8,780977.5,780974.7,780974.7,780977.2,780982.2,780988.25,780992.5,780996.8])

# some more example coordinates
#px_coords = np.array([299398,299402,299410.25,299419.7,299398])
#py_coords = np.array([822975.2,822920.3,822937.44,822954.94,822975.2])
#qx_coords = np.array([292316,292331.22,292329.72,292324.72,292319.44,292317.2,292316])
#qy_coords = np.array([663781,663788.25,663794,663798.06,663800.06,663799.3,663781])

# this is all just formatting the coordinates - only important thing to know is that p_midpoints and q_midpoints are two distinct sets of points, and are the targets in this question
px_edges = np.stack((px_coords, np.roll(px_coords, -1)),1)
px_midpoints = np.array(abs(px_coords + np.roll(px_coords, -1))/2)
py_edges = np.stack((py_coords, np.roll(py_coords, -1)),1)
py_midpoints = np.array(abs(py_coords + np.roll(py_coords, -1))/2)

p_edges = np.stack((px_edges, py_edges), axis=-1)[:-1]
p_midpoints = np.stack((px_midpoints, py_midpoints), axis=-1)[:-1]

qx_edges = np.stack((qx_coords, np.roll(qx_coords, -1)),1)
qx_midpoints = np.array(abs(qx_coords + np.roll(qx_coords, -1))/2)
qy_edges = np.stack((qy_coords, np.roll(qy_coords, -1)),1)
qy_midpoints = np.array(abs(qy_coords + np.roll(qy_coords, -1))/2)

q_edges = np.stack((qx_edges, qy_edges), axis=-1)[:-1]
q_midpoints = np.stack((qx_midpoints, qy_midpoints), axis=-1)[:-1]

# where the tree is actually built
p_tree = build_kd_tree(p_midpoints)
q_tree = build_kd_tree(q_midpoints)

# uncommect to see structure of tree
#pprint.pprint(p_tree)

near_distance = closest(p_tree, q_tree)

# brute force for testing
#distances = []
#for p_point in p_midpoints:
#    for q_point in q_midpoints:
#        distances.append(min_dist(p_point, q_point))
#
#m_dist = sorted(distances)[0]
#print(m_dist)

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

Вторая попытка - только «ближайшая» функция, можно поменять местамикак у аналога с тезкой в ​​приведенном выше коде:

def closest(k1,k2,lim=float("inf")):
    cc1 = [k1]
    cc1 = cc1 + [k1[value] for value in k1 if k1[value] is not None and type(k1[value]) == dict]
    cc2 = [k2]
    cc2 = cc2 + [k2[value] for value in k2 if k2[value] is not None and type(k2[value]) == dict]

    if len(cc1) == 1 and len(cc2) == 1:
        return min(lim, min_dist(k1['point'], k2['point']))

    md = [[min_dist(cc1[i]['point'], cc2[j]['point']), i, j, (cc1[i]['point'], cc2[j]['point'])] for i in range(len(cc1) >> 1, len(cc1)) for j in range(len(cc1) >> 1, len(cc2))]
    md = sorted(md, key=itemgetter(0))
    for h in range(0, len(md)):
        lim = min(lim, closest(cc1[md[h][1]], cc2[md[h][2]],lim))
    return lim

Я знаю, что существуют нестандартные решения для решения этой проблемы, но это область, в которой я хотел былучше понять, построив свой с нуля. Любая помощь приветствуется.

1 Ответ

0 голосов
/ 12 ноября 2019

Принцип работы kD-дерева заключается в том, что вы можете быстро находить границы на самом коротком и самом длинном расстоянии точки запроса (скажем, красного) до подмножества точек, содержащихся в известном прямоугольнике (скажем, расположенных всинее дерево). Кроме того, прямоугольники получают путем последовательных делений, что делает вычисления еще проще для вычисления.

Если вы хотите адаптироваться к бихроматическому случаю, вы можете обработать прямоугольники, сгенерированные красным деревом, вместо одногокрасная точка и адаптация правила для оценки самого короткого (0 в случае наложения) и самого длинного расстояния до синих прямоугольников.

Существуют различные способы организации подразделений обоих деревьев, например

  • для каждого уровня подразделения красного дерева, подразделите синее дерево до листьев,

  • и наоборот для каждого уровня подразделения голубого дерева, подразделите красноедерево вниз до листьев,

  • или на каждом уровне подразделения, подразделите красный и синий и рассмотрите все комбинации.

Понятия не имеюкак выбрать один из этих вариантов (кроме как полностью их опробовать).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...