Commit acfcdbe0 authored by Julien Jerphanion's avatar Julien Jerphanion

[WIP] Adapt querying logic

This change the logic to query the tree for nearest neighbors.
Start with a simple sequential query for each point.
parent d49871ec
...@@ -6,7 +6,7 @@ import numpy as np ...@@ -6,7 +6,7 @@ import numpy as np
np.import_array() np.import_array()
from runtime.runtime cimport BatchMailBox, NullResult, Scheduler, WaitResult from runtime.runtime cimport BatchMailBox, NullResult, Scheduler, WaitResult
from libc.math cimport log2, fmax, fmin from libc.math cimport log2, fmax, fmin, fabs
from libc.stdio cimport printf from libc.stdio cimport printf
from libc.stdlib cimport malloc, free from libc.stdlib cimport malloc, free
from openmp cimport omp_get_max_threads from openmp cimport omp_get_max_threads
...@@ -175,10 +175,21 @@ cdef cypclass Container activable: ...@@ -175,10 +175,21 @@ cdef cypclass Container activable:
self._n_updates += 1 self._n_updates += 1
self._array[idx] = value self._array[idx] = value
int n_updates(self): I_t n_updates(self):
return self._n_updates return self._n_updates
cdef inline D_t sqeuclidean_dist(D_t* x1, D_t* x2, I_t k) nogil:
cdef:
I_t i = 0
D_t distance = 0
for i in range(k):
distance += (x1[i] - x2[i]) ** 2
return distance
cdef inline void dual_swap(D_t* darr, I_t* iarr, I_t i1, I_t i2) nogil: cdef inline void dual_swap(D_t* darr, I_t* iarr, I_t i1, I_t i2) nogil:
"""swap the values at inex i1 and i2 of both darr and iarr""" """swap the values at inex i1 and i2 of both darr and iarr"""
cdef D_t dtmp = darr[i1] cdef D_t dtmp = darr[i1]
...@@ -254,7 +265,7 @@ cdef void _simultaneous_sort( ...@@ -254,7 +265,7 @@ cdef void _simultaneous_sort(
size - pivot_idx - 1) size - pivot_idx - 1)
cdef cypclass NeighborsHeaps activable: cdef cypclass NeighborsHeaps:
"""A max-heap structure to keep track of distances/indices of neighbors """A max-heap structure to keep track of distances/indices of neighbors
This implements an efficient pre-allocated set of fixed-size heaps This implements an efficient pre-allocated set of fixed-size heaps
...@@ -265,12 +276,6 @@ cdef cypclass NeighborsHeaps activable: ...@@ -265,12 +276,6 @@ cdef cypclass NeighborsHeaps activable:
Taken and adapted from: Taken and adapted from:
https://github.com/scikit-learn/scikit-learn/blob/e4bb9fa86b0df873ad750b6d59090843d9d23d50/sklearn/neighbors/_binary_tree.pxi#L513 https://github.com/scikit-learn/scikit-learn/blob/e4bb9fa86b0df873ad750b6d59090843d9d23d50/sklearn/neighbors/_binary_tree.pxi#L513
The initial caller is responsible for providing the array of indices
which will be modified in place by the actors logic.
n_pushes and is_sorted can be used by the initial caller to know
when to pursue.
Parameters Parameters
---------- ----------
n_pts : int n_pts : int
...@@ -290,8 +295,6 @@ cdef cypclass NeighborsHeaps activable: ...@@ -290,8 +295,6 @@ cdef cypclass NeighborsHeaps activable:
cdef I_t i cdef I_t i
self._n_pts = n_pts self._n_pts = n_pts
self._n_nbrs = n_nbrs self._n_nbrs = n_nbrs
self._active_result_class = WaitResult.construct
self._active_queue_class = consume BatchMailBox(scheduler)
self._distances = <D_t *> malloc(n_pts * n_nbrs * sizeof(D_t)) self._distances = <D_t *> malloc(n_pts * n_nbrs * sizeof(D_t))
self._indices = indices self._indices = indices
self._n_pushes = 0 self._n_pushes = 0
...@@ -355,7 +358,6 @@ cdef cypclass NeighborsHeaps activable: ...@@ -355,7 +358,6 @@ cdef cypclass NeighborsHeaps activable:
distances[i] = val distances[i] = val
indices[i] = i_val indices[i] = i_val
void sort(self): void sort(self):
# NOTE: Ideally we could sort results in parallel, but # NOTE: Ideally we could sort results in parallel, but
# OpenMP threadpool and this runtime's aren't working # OpenMP threadpool and this runtime's aren't working
...@@ -381,6 +383,9 @@ cdef cypclass NeighborsHeaps activable: ...@@ -381,6 +383,9 @@ cdef cypclass NeighborsHeaps activable:
# use of getIntResult # use of getIntResult
return 1 if self._sorted else 0 return 1 if self._sorted else 0
I_t largest(self, I_t index):
return self._indices[index * self._n_nbrs]
cdef cypclass Node activable: cdef cypclass Node activable:
"""A KDTree Node """A KDTree Node
...@@ -488,6 +493,8 @@ cdef cypclass KDTree: ...@@ -488,6 +493,8 @@ cdef cypclass KDTree:
NodeData_t *_node_data_ptr NodeData_t *_node_data_ptr
D_t * _node_bounds_ptr D_t * _node_bounds_ptr
I_t _node_bounds_ptr_offset
__init__(self, __init__(self,
np.ndarray X, np.ndarray X,
...@@ -515,12 +522,17 @@ cdef cypclass KDTree: ...@@ -515,12 +522,17 @@ cdef cypclass KDTree:
self._indices_ptr = <I_t *> malloc(n * sizeof(I_t)) self._indices_ptr = <I_t *> malloc(n * sizeof(I_t))
self._node_data_ptr = <NodeData_t *> malloc(self._n_nodes * sizeof(NodeData_t)) self._node_data_ptr = <NodeData_t *> malloc(self._n_nodes * sizeof(NodeData_t))
self._node_bounds_ptr = <D_t *> malloc(2 * self._n_nodes * self._d * sizeof(D_t)) self._node_bounds_ptr_offset = self._n_nodes * self._d
# To be seen as a [2, n_nodes, d] with:
# - elements in [0, :, :] as min
# - elements in [1, :, :] as max
self._node_bounds_ptr = <D_t *> malloc(2 * self._node_bounds_ptr_offset * sizeof(D_t))
for i in range(n): for i in range(n):
self._indices_ptr[i] = i self._indices_ptr[i] = i
# Recurvisely building the tree here # Recursively building the tree here
global scheduler global scheduler
scheduler = Scheduler(num_workers) scheduler = Scheduler(num_workers)
...@@ -567,20 +579,18 @@ cdef cypclass KDTree: ...@@ -567,20 +579,18 @@ cdef cypclass KDTree:
free(self._node_bounds_ptr) free(self._node_bounds_ptr)
cdef int _query_single_depthfirst(self, int _query_single_depthfirst(self,
ITYPE_t i_node, I_t i_node,
DTYPE_t* pt, D_t* pt,
ITYPE_t i_pt, I_t i_pt,
NeighborsHeaps heaps, NeighborsHeaps heaps,
DTYPE_t reduced_dist_LB, D_t reduced_dist_LB,
) nogil except -1: ) nogil except -1:
"""Recursive Single-tree k-neighbors query, depth-first approach""" """Recursive Single-tree k-neighbors query, depth-first approach"""
cdef NodeData_t node_info = self._node_data_ptr[i_node] cdef NodeData_t node_info = self._node_data_ptr[i_node]
cdef DTYPE_t dist_pt, reduced_dist_LB_1, reduced_dist_LB_2 cdef D_t dist_pt, reduced_dist_LB_1, reduced_dist_LB_2
cdef ITYPE_t i, i1, i2 cdef I_t i, i1, i2
cdef DTYPE_t* data = &self.data[0, 0]
#------------------------------------------------------------ #------------------------------------------------------------
# Case 1: query point is outside node radius: # Case 1: query point is outside node radius:
...@@ -593,11 +603,11 @@ cdef cypclass KDTree: ...@@ -593,11 +603,11 @@ cdef cypclass KDTree:
elif node_info.is_leaf: elif node_info.is_leaf:
for i in range(node_info.idx_start, node_info.idx_end): for i in range(node_info.idx_start, node_info.idx_end):
dist_pt = sqeuclidean_dist( dist_pt = sqeuclidean_dist(
pt, x1=pt,
&self.data[self.idx_array[i], 0], x2=self._data_ptr + self._indices_ptr[i] * self._d,
self.data.shape[1] k=self._d,
) )
heaps._push(i_pt, dist_pt, self.idx_array[i]) heaps.push(i_pt, dist_pt, self._indices_ptr[i])
#------------------------------------------------------------ #------------------------------------------------------------
# Case 3: Node is not a leaf. Recursively query subnodes # Case 3: Node is not a leaf. Recursively query subnodes
...@@ -605,8 +615,8 @@ cdef cypclass KDTree: ...@@ -605,8 +615,8 @@ cdef cypclass KDTree:
else: else:
i1 = 2 * i_node + 1 i1 = 2 * i_node + 1
i2 = i1 + 1 i2 = i1 + 1
reduced_dist_LB_1 = min_rdist(self, i1, pt) reduced_dist_LB_1 = self.min_rdist(i1, pt)
reduced_dist_LB_2 = min_rdist(self, i2, pt) reduced_dist_LB_2 = self.min_rdist(i2, pt)
# recursively query subnodes # recursively query subnodes
if reduced_dist_LB_1 <= reduced_dist_LB_2: if reduced_dist_LB_1 <= reduced_dist_LB_2:
...@@ -629,48 +639,45 @@ cdef cypclass KDTree: ...@@ -629,48 +639,45 @@ cdef cypclass KDTree:
I_t n_features = query_points.shape[1] I_t n_features = query_points.shape[1]
I_t n_neighbors = closests.shape[1] I_t n_neighbors = closests.shape[1]
I_t total_n_pushes = n_query * self._n I_t total_n_pushes = n_query * self._n
D_t reduced_dist_LB D_t * _query_points_ptr = <D_t *> query_points.data
D_t * query_point = query_points.data D_t rdist_lower_bound
active NeighborsHeaps heaps
heaps = consume NeighborsHeaps(<I_t *> closests.data, NeighborsHeaps heaps = NeighborsHeaps(<I_t *> closests.data, n_query, n_neighbors)
n_query,
n_neighbors)
for i in range(Xarr.shape[0]): for i in range(n_query):
rdist_lower_bound = min_rdist(self, 0, query_point) rdist_lower_bound = self.min_rdist(0, _query_points_ptr + i * n_features)
_query_single_depthfirst(0, pt, i, heaps, rdist_lower_bound) self._query_single_depthfirst(0, _query_points_ptr, i, heaps, rdist_lower_bound)
query_point += n_features
heaps.sort(NULL)
while not(heaps.is_sorted(NULL).getIntResult()):
pass
heaps.sort()
cdef inline DTYPE_t min_rdist(
KDTree tree,
ITYPE_t i_node,
DTYPE_t* pt,
) nogil except -1:
"""Compute the minimum reduced-distance between a point and a node"""
cdef ITYPE_t n_features = tree.data.shape[1]
cdef DTYPE_t d, d_lo, d_hi, rdist=0.0
cdef ITYPE_t j
for j in range(n_features): D_t min_rdist(
d_lo = tree._node_bounds_ptr[0, i_node, j] - pt[j] KDTree self,
d_hi = pt[j] - tree._node_bounds_ptr[1, i_node, j] I_t i_node,
# We use the following identity: D_t* pt,
# ) nogil except -1:
# 0.5 * (x + abs(x) = max(x, 0) """Compute the minimum reduced-distance between a point and a node"""
# cdef I_t node_idx
# twice. cdef D_t d, d_lo, d_hi, rdist=0.0
d = 0.5 (d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi)) cdef I_t j
rdist += d ** 2
cdef D_t node_min_j, node_max_j
return rdist
for j in range(self._d):
node_min_j = deref(self._node_bounds_ptr + node_idx + j)
node_max_j = deref(self._node_bounds_ptr + node_idx + j + self._node_bounds_ptr_offset)
d_lo = node_min_j - pt[j]
d_hi = pt[j] - node_max_j
# We use the following identity:
#
# 0.5 * (x + abs(x)) = 0.5 * max(x, 0)
#
# twice.
d = 0.5 * ((d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi)))
rdist += d ** 2
return rdist
cdef public int main() nogil: cdef public int main() nogil:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment