feat: add Camera Work AI package
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,284 @@
|
||||
{{py:
|
||||
|
||||
# Generated file: _ball_tree.pyx
|
||||
|
||||
implementation_specific_values = [
|
||||
# The values are arranged as follows:
|
||||
#
|
||||
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
|
||||
#
|
||||
('64', 'float64_t', 'np.float64'),
|
||||
('32', 'float32_t', 'np.float32')
|
||||
]
|
||||
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
}}
|
||||
|
||||
|
||||
__all__ = ['BallTree', 'BallTree64', 'BallTree32']
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
DOC_DICT{{name_suffix}} = {
|
||||
'BinaryTree': 'BallTree{{name_suffix}}',
|
||||
'binary_tree': 'ball_tree{{name_suffix}}',
|
||||
}
|
||||
|
||||
VALID_METRICS{{name_suffix}} = [
|
||||
'BrayCurtisDistance{{name_suffix}}',
|
||||
'CanberraDistance{{name_suffix}}',
|
||||
'ChebyshevDistance{{name_suffix}}',
|
||||
'DiceDistance{{name_suffix}}',
|
||||
'EuclideanDistance{{name_suffix}}',
|
||||
'HammingDistance{{name_suffix}}',
|
||||
'HaversineDistance{{name_suffix}}',
|
||||
'JaccardDistance{{name_suffix}}',
|
||||
'MahalanobisDistance{{name_suffix}}',
|
||||
'ManhattanDistance{{name_suffix}}',
|
||||
'MinkowskiDistance{{name_suffix}}',
|
||||
'PyFuncDistance{{name_suffix}}',
|
||||
'RogersTanimotoDistance{{name_suffix}}',
|
||||
'RussellRaoDistance{{name_suffix}}',
|
||||
'SEuclideanDistance{{name_suffix}}',
|
||||
'SokalMichenerDistance{{name_suffix}}',
|
||||
'SokalSneathDistance{{name_suffix}}',
|
||||
'WMinkowskiDistance{{name_suffix}}',
|
||||
]
|
||||
|
||||
{{endfor}}
|
||||
|
||||
include "_binary_tree.pxi"
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
# Inherit BallTree{{name_suffix}} from BinaryTree{{name_suffix}}
|
||||
cdef class BallTree{{name_suffix}}(BinaryTree{{name_suffix}}):
|
||||
__doc__ = CLASS_DOC.format(**DOC_DICT{{name_suffix}})
|
||||
pass
|
||||
|
||||
{{endfor}}
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# The functions below specialized the Binary Tree as a Ball Tree
|
||||
#
|
||||
# Note that these functions use the concept of "reduced distance".
|
||||
# The reduced distance, defined for some metrics, is a quantity which
|
||||
# is more efficient to compute than the distance, but preserves the
|
||||
# relative rankings of the true distance. For example, the reduced
|
||||
# distance for the Euclidean metric is the squared-euclidean distance.
|
||||
# For some metrics, the reduced distance is simply the distance.
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
cdef int allocate_data{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t n_nodes,
|
||||
intp_t n_features,
|
||||
) except -1:
|
||||
"""Allocate arrays needed for the KD Tree"""
|
||||
tree.node_bounds = np.zeros((1, n_nodes, n_features), dtype={{INPUT_DTYPE}})
|
||||
return 0
|
||||
|
||||
|
||||
cdef int init_node{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
NodeData_t[::1] node_data,
|
||||
intp_t i_node,
|
||||
intp_t idx_start,
|
||||
intp_t idx_end,
|
||||
) except -1:
|
||||
"""Initialize the node for the dataset stored in tree.data"""
|
||||
cdef intp_t n_features = tree.data.shape[1]
|
||||
cdef intp_t n_points = idx_end - idx_start
|
||||
|
||||
cdef intp_t i, j
|
||||
cdef float64_t radius
|
||||
cdef const {{INPUT_DTYPE_t}} *this_pt
|
||||
|
||||
cdef intp_t* idx_array = &tree.idx_array[0]
|
||||
cdef const {{INPUT_DTYPE_t}}* data = &tree.data[0, 0]
|
||||
cdef {{INPUT_DTYPE_t}}* centroid = &tree.node_bounds[0, i_node, 0]
|
||||
|
||||
cdef bint with_sample_weight = tree.sample_weight is not None
|
||||
cdef const {{INPUT_DTYPE_t}}* sample_weight
|
||||
cdef float64_t sum_weight_node
|
||||
if with_sample_weight:
|
||||
sample_weight = &tree.sample_weight[0]
|
||||
|
||||
# determine Node centroid
|
||||
for j in range(n_features):
|
||||
centroid[j] = 0
|
||||
|
||||
if with_sample_weight:
|
||||
sum_weight_node = 0
|
||||
for i in range(idx_start, idx_end):
|
||||
sum_weight_node += sample_weight[idx_array[i]]
|
||||
this_pt = data + n_features * idx_array[i]
|
||||
for j from 0 <= j < n_features:
|
||||
centroid[j] += this_pt[j] * sample_weight[idx_array[i]]
|
||||
|
||||
for j in range(n_features):
|
||||
centroid[j] /= sum_weight_node
|
||||
else:
|
||||
for i in range(idx_start, idx_end):
|
||||
this_pt = data + n_features * idx_array[i]
|
||||
for j from 0 <= j < n_features:
|
||||
centroid[j] += this_pt[j]
|
||||
|
||||
for j in range(n_features):
|
||||
centroid[j] /= n_points
|
||||
|
||||
# determine Node radius
|
||||
radius = 0
|
||||
for i in range(idx_start, idx_end):
|
||||
radius = fmax(radius,
|
||||
tree.rdist(centroid,
|
||||
data + n_features * idx_array[i],
|
||||
n_features))
|
||||
|
||||
node_data[i_node].radius = tree.dist_metric._rdist_to_dist(radius)
|
||||
node_data[i_node].idx_start = idx_start
|
||||
node_data[i_node].idx_end = idx_end
|
||||
return 0
|
||||
|
||||
|
||||
cdef inline float64_t min_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1 nogil:
|
||||
"""Compute the minimum distance between a point and a node"""
|
||||
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
|
||||
tree.data.shape[1])
|
||||
return fmax(0, dist_pt - tree.node_data[i_node].radius)
|
||||
|
||||
|
||||
cdef inline float64_t max_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1:
|
||||
"""Compute the maximum distance between a point and a node"""
|
||||
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
|
||||
tree.data.shape[1])
|
||||
return dist_pt + tree.node_data[i_node].radius
|
||||
|
||||
|
||||
cdef inline int min_max_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
float64_t* min_dist,
|
||||
float64_t* max_dist,
|
||||
) except -1 nogil:
|
||||
"""Compute the minimum and maximum distance between a point and a node"""
|
||||
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
|
||||
tree.data.shape[1])
|
||||
cdef float64_t rad = tree.node_data[i_node].radius
|
||||
min_dist[0] = fmax(0, dist_pt - rad)
|
||||
max_dist[0] = dist_pt + rad
|
||||
return 0
|
||||
|
||||
|
||||
cdef inline float64_t min_rdist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1 nogil:
|
||||
"""Compute the minimum reduced-distance between a point and a node"""
|
||||
if tree.euclidean:
|
||||
return euclidean_dist_to_rdist{{name_suffix}}(
|
||||
min_dist{{name_suffix}}(tree, i_node, pt)
|
||||
)
|
||||
else:
|
||||
return tree.dist_metric._dist_to_rdist(
|
||||
min_dist{{name_suffix}}(tree, i_node, pt)
|
||||
)
|
||||
|
||||
|
||||
cdef inline float64_t max_rdist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1:
|
||||
"""Compute the maximum reduced-distance between a point and a node"""
|
||||
if tree.euclidean:
|
||||
return euclidean_dist_to_rdist{{name_suffix}}(
|
||||
max_dist{{name_suffix}}(tree, i_node, pt)
|
||||
)
|
||||
else:
|
||||
return tree.dist_metric._dist_to_rdist(
|
||||
max_dist{{name_suffix}}(tree, i_node, pt)
|
||||
)
|
||||
|
||||
|
||||
cdef inline float64_t min_dist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""compute the minimum distance between two nodes"""
|
||||
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
|
||||
&tree1.node_bounds[0, i_node1, 0],
|
||||
tree1.data.shape[1])
|
||||
return fmax(0, (dist_pt - tree1.node_data[i_node1].radius
|
||||
- tree2.node_data[i_node2].radius))
|
||||
|
||||
|
||||
cdef inline float64_t max_dist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""compute the maximum distance between two nodes"""
|
||||
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
|
||||
&tree1.node_bounds[0, i_node1, 0],
|
||||
tree1.data.shape[1])
|
||||
return (dist_pt + tree1.node_data[i_node1].radius
|
||||
+ tree2.node_data[i_node2].radius)
|
||||
|
||||
|
||||
cdef inline float64_t min_rdist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""compute the minimum reduced distance between two nodes"""
|
||||
if tree1.euclidean:
|
||||
return euclidean_dist_to_rdist{{name_suffix}}(
|
||||
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
else:
|
||||
return tree1.dist_metric._dist_to_rdist(
|
||||
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
|
||||
|
||||
cdef inline float64_t max_rdist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""compute the maximum reduced distance between two nodes"""
|
||||
if tree1.euclidean:
|
||||
return euclidean_dist_to_rdist{{name_suffix}}(
|
||||
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
else:
|
||||
return tree1.dist_metric._dist_to_rdist(
|
||||
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
|
||||
{{endfor}}
|
||||
|
||||
|
||||
class BallTree(BallTree64):
|
||||
__doc__ = CLASS_DOC.format(BinaryTree="BallTree")
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,336 @@
|
||||
{{py:
|
||||
|
||||
# Generated file: _kd_tree.pyx
|
||||
|
||||
implementation_specific_values = [
|
||||
# The values are arranged as follows:
|
||||
#
|
||||
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
|
||||
#
|
||||
('64', 'float64_t', 'np.float64'),
|
||||
('32', 'float32_t', 'np.float32')
|
||||
]
|
||||
|
||||
# By Jake Vanderplas (2013) <jakevdp@cs.washington.edu>
|
||||
# written for the scikit-learn project
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
}}
|
||||
|
||||
|
||||
__all__ = ['KDTree', 'KDTree64', 'KDTree32']
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
DOC_DICT{{name_suffix}} = {
|
||||
'BinaryTree': 'KDTree{{name_suffix}}',
|
||||
'binary_tree': 'kd_tree{{name_suffix}}',
|
||||
}
|
||||
|
||||
VALID_METRICS{{name_suffix}} = [
|
||||
'EuclideanDistance{{name_suffix}}',
|
||||
'ManhattanDistance{{name_suffix}}',
|
||||
'ChebyshevDistance{{name_suffix}}',
|
||||
'MinkowskiDistance{{name_suffix}}'
|
||||
]
|
||||
|
||||
{{endfor}}
|
||||
|
||||
include "_binary_tree.pxi"
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
# Inherit KDTree{{name_suffix}} from BinaryTree{{name_suffix}}
|
||||
cdef class KDTree{{name_suffix}}(BinaryTree{{name_suffix}}):
|
||||
__doc__ = CLASS_DOC.format(**DOC_DICT{{name_suffix}})
|
||||
pass
|
||||
|
||||
{{endfor}}
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# The functions below specialized the Binary Tree as a KD Tree
|
||||
#
|
||||
# Note that these functions use the concept of "reduced distance".
|
||||
# The reduced distance, defined for some metrics, is a quantity which
|
||||
# is more efficient to compute than the distance, but preserves the
|
||||
# relative rankings of the true distance. For example, the reduced
|
||||
# distance for the Euclidean metric is the squared-euclidean distance.
|
||||
# For some metrics, the reduced distance is simply the distance.
|
||||
|
||||
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
|
||||
|
||||
cdef int allocate_data{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t n_nodes,
|
||||
intp_t n_features,
|
||||
) except -1:
|
||||
"""Allocate arrays needed for the KD Tree"""
|
||||
tree.node_bounds = np.zeros((2, n_nodes, n_features), dtype={{INPUT_DTYPE}})
|
||||
return 0
|
||||
|
||||
|
||||
cdef int init_node{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
NodeData_t[::1] node_data,
|
||||
intp_t i_node,
|
||||
intp_t idx_start,
|
||||
intp_t idx_end,
|
||||
) except -1:
|
||||
"""Initialize the node for the dataset stored in tree.data"""
|
||||
cdef intp_t n_features = tree.data.shape[1]
|
||||
cdef intp_t i, j
|
||||
cdef float64_t rad = 0
|
||||
|
||||
cdef {{INPUT_DTYPE_t}}* lower_bounds = &tree.node_bounds[0, i_node, 0]
|
||||
cdef {{INPUT_DTYPE_t}}* upper_bounds = &tree.node_bounds[1, i_node, 0]
|
||||
cdef const {{INPUT_DTYPE_t}}* data = &tree.data[0, 0]
|
||||
cdef const intp_t* idx_array = &tree.idx_array[0]
|
||||
|
||||
cdef const {{INPUT_DTYPE_t}}* data_row
|
||||
|
||||
# determine Node bounds
|
||||
for j in range(n_features):
|
||||
lower_bounds[j] = INF
|
||||
upper_bounds[j] = -INF
|
||||
|
||||
# Compute the actual data range. At build time, this is slightly
|
||||
# slower than using the previously-computed bounds of the parent node,
|
||||
# but leads to more compact trees and thus faster queries.
|
||||
for i in range(idx_start, idx_end):
|
||||
data_row = data + idx_array[i] * n_features
|
||||
for j in range(n_features):
|
||||
lower_bounds[j] = fmin(lower_bounds[j], data_row[j])
|
||||
upper_bounds[j] = fmax(upper_bounds[j], data_row[j])
|
||||
|
||||
for j in range(n_features):
|
||||
if tree.dist_metric.p == INF:
|
||||
rad = fmax(rad, 0.5 * (upper_bounds[j] - lower_bounds[j]))
|
||||
else:
|
||||
rad += pow(0.5 * abs(upper_bounds[j] - lower_bounds[j]),
|
||||
tree.dist_metric.p)
|
||||
|
||||
node_data[i_node].idx_start = idx_start
|
||||
node_data[i_node].idx_end = idx_end
|
||||
|
||||
# The radius will hold the size of the circumscribed hypersphere measured
|
||||
# with the specified metric: in querying, this is used as a measure of the
|
||||
# size of each node when deciding which nodes to split.
|
||||
node_data[i_node].radius = pow(rad, 1. / tree.dist_metric.p)
|
||||
return 0
|
||||
|
||||
|
||||
cdef float64_t min_rdist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1 nogil:
|
||||
"""Compute the minimum reduced-distance between a point and a node"""
|
||||
cdef intp_t n_features = tree.data.shape[1]
|
||||
cdef float64_t d, d_lo, d_hi, rdist=0.0
|
||||
cdef intp_t j
|
||||
|
||||
if tree.dist_metric.p == INF:
|
||||
for j in range(n_features):
|
||||
d_lo = tree.node_bounds[0, i_node, j] - pt[j]
|
||||
d_hi = pt[j] - tree.node_bounds[1, i_node, j]
|
||||
d = (d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi))
|
||||
rdist = fmax(rdist, 0.5 * d)
|
||||
else:
|
||||
# here we'll use the fact that x + abs(x) = 2 * max(x, 0)
|
||||
for j in range(n_features):
|
||||
d_lo = tree.node_bounds[0, i_node, j] - pt[j]
|
||||
d_hi = pt[j] - tree.node_bounds[1, i_node, j]
|
||||
d = (d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi))
|
||||
rdist += pow(0.5 * d, tree.dist_metric.p)
|
||||
|
||||
return rdist
|
||||
|
||||
|
||||
cdef float64_t min_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1:
|
||||
"""Compute the minimum distance between a point and a node"""
|
||||
if tree.dist_metric.p == INF:
|
||||
return min_rdist{{name_suffix}}(tree, i_node, pt)
|
||||
else:
|
||||
return pow(
|
||||
min_rdist{{name_suffix}}(tree, i_node, pt),
|
||||
1. / tree.dist_metric.p
|
||||
)
|
||||
|
||||
|
||||
cdef float64_t max_rdist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1:
|
||||
"""Compute the maximum reduced-distance between a point and a node"""
|
||||
cdef intp_t n_features = tree.data.shape[1]
|
||||
|
||||
cdef float64_t d_lo, d_hi, rdist=0.0
|
||||
cdef intp_t j
|
||||
|
||||
if tree.dist_metric.p == INF:
|
||||
for j in range(n_features):
|
||||
rdist = fmax(rdist, fabs(pt[j] - tree.node_bounds[0, i_node, j]))
|
||||
rdist = fmax(rdist, fabs(pt[j] - tree.node_bounds[1, i_node, j]))
|
||||
else:
|
||||
for j in range(n_features):
|
||||
d_lo = fabs(pt[j] - tree.node_bounds[0, i_node, j])
|
||||
d_hi = fabs(pt[j] - tree.node_bounds[1, i_node, j])
|
||||
rdist += pow(fmax(d_lo, d_hi), tree.dist_metric.p)
|
||||
|
||||
return rdist
|
||||
|
||||
|
||||
cdef float64_t max_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
) except -1:
|
||||
"""Compute the maximum distance between a point and a node"""
|
||||
if tree.dist_metric.p == INF:
|
||||
return max_rdist{{name_suffix}}(tree, i_node, pt)
|
||||
else:
|
||||
return pow(
|
||||
max_rdist{{name_suffix}}(tree, i_node, pt),
|
||||
1. / tree.dist_metric.p
|
||||
)
|
||||
|
||||
|
||||
cdef inline int min_max_dist{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree,
|
||||
intp_t i_node,
|
||||
const {{INPUT_DTYPE_t}}* pt,
|
||||
float64_t* min_dist,
|
||||
float64_t* max_dist,
|
||||
) except -1 nogil:
|
||||
"""Compute the minimum and maximum distance between a point and a node"""
|
||||
cdef intp_t n_features = tree.data.shape[1]
|
||||
|
||||
cdef float64_t d, d_lo, d_hi
|
||||
cdef intp_t j
|
||||
|
||||
min_dist[0] = 0.0
|
||||
max_dist[0] = 0.0
|
||||
|
||||
if tree.dist_metric.p == INF:
|
||||
for j in range(n_features):
|
||||
d_lo = tree.node_bounds[0, i_node, j] - pt[j]
|
||||
d_hi = pt[j] - tree.node_bounds[1, i_node, j]
|
||||
d = (d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi))
|
||||
min_dist[0] = fmax(min_dist[0], 0.5 * d)
|
||||
max_dist[0] = fmax(max_dist[0], fabs(d_lo))
|
||||
max_dist[0] = fmax(max_dist[0], fabs(d_hi))
|
||||
else:
|
||||
# as above, use the fact that x + abs(x) = 2 * max(x, 0)
|
||||
for j in range(n_features):
|
||||
d_lo = tree.node_bounds[0, i_node, j] - pt[j]
|
||||
d_hi = pt[j] - tree.node_bounds[1, i_node, j]
|
||||
d = (d_lo + fabs(d_lo)) + (d_hi + fabs(d_hi))
|
||||
min_dist[0] += pow(0.5 * d, tree.dist_metric.p)
|
||||
max_dist[0] += pow(fmax(fabs(d_lo), fabs(d_hi)),
|
||||
tree.dist_metric.p)
|
||||
|
||||
min_dist[0] = pow(min_dist[0], 1. / tree.dist_metric.p)
|
||||
max_dist[0] = pow(max_dist[0], 1. / tree.dist_metric.p)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
cdef inline float64_t min_rdist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""Compute the minimum reduced distance between two nodes"""
|
||||
cdef intp_t n_features = tree1.data.shape[1]
|
||||
|
||||
cdef float64_t d, d1, d2, rdist=0.0
|
||||
cdef intp_t j
|
||||
|
||||
if tree1.dist_metric.p == INF:
|
||||
for j in range(n_features):
|
||||
d1 = (tree1.node_bounds[0, i_node1, j]
|
||||
- tree2.node_bounds[1, i_node2, j])
|
||||
d2 = (tree2.node_bounds[0, i_node2, j]
|
||||
- tree1.node_bounds[1, i_node1, j])
|
||||
d = (d1 + fabs(d1)) + (d2 + fabs(d2))
|
||||
|
||||
rdist = fmax(rdist, 0.5 * d)
|
||||
else:
|
||||
# here we'll use the fact that x + abs(x) = 2 * max(x, 0)
|
||||
for j in range(n_features):
|
||||
d1 = (tree1.node_bounds[0, i_node1, j]
|
||||
- tree2.node_bounds[1, i_node2, j])
|
||||
d2 = (tree2.node_bounds[0, i_node2, j]
|
||||
- tree1.node_bounds[1, i_node1, j])
|
||||
d = (d1 + fabs(d1)) + (d2 + fabs(d2))
|
||||
|
||||
rdist += pow(0.5 * d, tree1.dist_metric.p)
|
||||
|
||||
return rdist
|
||||
|
||||
|
||||
cdef inline float64_t min_dist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""Compute the minimum distance between two nodes"""
|
||||
return tree1.dist_metric._rdist_to_dist(
|
||||
min_rdist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
|
||||
|
||||
cdef inline float64_t max_rdist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""Compute the maximum reduced distance between two nodes"""
|
||||
cdef intp_t n_features = tree1.data.shape[1]
|
||||
|
||||
cdef float64_t d1, d2, rdist=0.0
|
||||
cdef intp_t j
|
||||
|
||||
if tree1.dist_metric.p == INF:
|
||||
for j in range(n_features):
|
||||
rdist = fmax(rdist, fabs(tree1.node_bounds[0, i_node1, j]
|
||||
- tree2.node_bounds[1, i_node2, j]))
|
||||
rdist = fmax(rdist, fabs(tree1.node_bounds[1, i_node1, j]
|
||||
- tree2.node_bounds[0, i_node2, j]))
|
||||
else:
|
||||
for j in range(n_features):
|
||||
d1 = fabs(tree1.node_bounds[0, i_node1, j]
|
||||
- tree2.node_bounds[1, i_node2, j])
|
||||
d2 = fabs(tree1.node_bounds[1, i_node1, j]
|
||||
- tree2.node_bounds[0, i_node2, j])
|
||||
rdist += pow(fmax(d1, d2), tree1.dist_metric.p)
|
||||
|
||||
return rdist
|
||||
|
||||
|
||||
cdef inline float64_t max_dist_dual{{name_suffix}}(
|
||||
BinaryTree{{name_suffix}} tree1,
|
||||
intp_t i_node1,
|
||||
BinaryTree{{name_suffix}} tree2,
|
||||
intp_t i_node2,
|
||||
) except -1:
|
||||
"""Compute the maximum distance between two nodes"""
|
||||
return tree1.dist_metric._rdist_to_dist(
|
||||
max_rdist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
|
||||
)
|
||||
|
||||
{{endfor}}
|
||||
|
||||
|
||||
class KDTree(KDTree64):
|
||||
__doc__ = CLASS_DOC.format(BinaryTree="KDTree")
|
||||
pass
|
||||
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
from cython cimport floating
|
||||
from ..utils._typedefs cimport float64_t, intp_t
|
||||
|
||||
cdef int partition_node_indices(
|
||||
const floating *data,
|
||||
intp_t *node_indices,
|
||||
intp_t split_dim,
|
||||
intp_t split_index,
|
||||
intp_t n_features,
|
||||
intp_t n_points) except -1
|
||||
@@ -0,0 +1,122 @@
|
||||
# BinaryTrees rely on partial sorts to partition their nodes during their
|
||||
# initialisation.
|
||||
#
|
||||
# The C++ std library exposes nth_element, an efficient partial sort for this
|
||||
# situation which has a linear time complexity as well as the best performances.
|
||||
#
|
||||
# To use std::algorithm::nth_element, a few fixture are defined using Cython:
|
||||
# - partition_node_indices, a Cython function used in BinaryTrees, that calls
|
||||
# - partition_node_indices_inner, a C++ function that wraps nth_element and uses
|
||||
# - an IndexComparator to state how to compare KDTrees' indices
|
||||
#
|
||||
# IndexComparator has been defined so that partial sorts are stable with
|
||||
# respect to the nodes initial indices.
|
||||
#
|
||||
# See for reference:
|
||||
# - https://en.cppreference.com/w/cpp/algorithm/nth_element.
|
||||
# - https://github.com/scikit-learn/scikit-learn/pull/11103
|
||||
# - https://github.com/scikit-learn/scikit-learn/pull/19473
|
||||
from cython cimport floating
|
||||
|
||||
|
||||
cdef extern from *:
|
||||
"""
|
||||
#include <algorithm>
|
||||
|
||||
template<class D, class I>
|
||||
class IndexComparator {
|
||||
private:
|
||||
const D *data;
|
||||
I split_dim, n_features;
|
||||
public:
|
||||
IndexComparator(const D *data, const I &split_dim, const I &n_features):
|
||||
data(data), split_dim(split_dim), n_features(n_features) {}
|
||||
|
||||
bool operator()(const I &a, const I &b) const {
|
||||
D a_value = data[a * n_features + split_dim];
|
||||
D b_value = data[b * n_features + split_dim];
|
||||
return a_value == b_value ? a < b : a_value < b_value;
|
||||
}
|
||||
};
|
||||
|
||||
template<class D, class I>
|
||||
void partition_node_indices_inner(
|
||||
const D *data,
|
||||
I *node_indices,
|
||||
const I &split_dim,
|
||||
const I &split_index,
|
||||
const I &n_features,
|
||||
const I &n_points) {
|
||||
IndexComparator<D, I> index_comparator(data, split_dim, n_features);
|
||||
std::nth_element(
|
||||
node_indices,
|
||||
node_indices + split_index,
|
||||
node_indices + n_points,
|
||||
index_comparator);
|
||||
}
|
||||
"""
|
||||
void partition_node_indices_inner[D, I](
|
||||
const D *data,
|
||||
I *node_indices,
|
||||
I split_dim,
|
||||
I split_index,
|
||||
I n_features,
|
||||
I n_points) except +
|
||||
|
||||
|
||||
cdef int partition_node_indices(
|
||||
const floating *data,
|
||||
intp_t *node_indices,
|
||||
intp_t split_dim,
|
||||
intp_t split_index,
|
||||
intp_t n_features,
|
||||
intp_t n_points) except -1:
|
||||
"""Partition points in the node into two equal-sized groups.
|
||||
|
||||
Upon return, the values in node_indices will be rearranged such that
|
||||
(assuming numpy-style indexing):
|
||||
|
||||
data[node_indices[0:split_index], split_dim]
|
||||
<= data[node_indices[split_index], split_dim]
|
||||
|
||||
and
|
||||
|
||||
data[node_indices[split_index], split_dim]
|
||||
<= data[node_indices[split_index:n_points], split_dim]
|
||||
|
||||
The algorithm is essentially a partial in-place quicksort around a
|
||||
set pivot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : double pointer
|
||||
Pointer to a 2D array of the training data, of shape [N, n_features].
|
||||
N must be greater than any of the values in node_indices.
|
||||
node_indices : int pointer
|
||||
Pointer to a 1D array of length n_points. This lists the indices of
|
||||
each of the points within the current node. This will be modified
|
||||
in-place.
|
||||
split_dim : int
|
||||
the dimension on which to split. This will usually be computed via
|
||||
the routine ``find_node_split_dim``.
|
||||
split_index : int
|
||||
the index within node_indices around which to split the points.
|
||||
n_features: int
|
||||
the number of features (i.e columns) in the 2D array pointed by data.
|
||||
n_points : int
|
||||
the length of node_indices. This is also the number of points in
|
||||
the original dataset.
|
||||
Returns
|
||||
-------
|
||||
status : int
|
||||
integer exit status. On return, the contents of node_indices are
|
||||
modified as noted above.
|
||||
"""
|
||||
partition_node_indices_inner(
|
||||
data,
|
||||
node_indices,
|
||||
split_dim,
|
||||
split_index,
|
||||
n_features,
|
||||
n_points)
|
||||
return 0
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,92 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See quad_tree.pyx for details.
|
||||
|
||||
cimport numpy as cnp
|
||||
from ..utils._typedefs cimport float32_t, intp_t
|
||||
|
||||
# This is effectively an ifdef statement in Cython
|
||||
# It allows us to write printf debugging lines
|
||||
# and remove them at compile time
|
||||
cdef enum:
|
||||
DEBUGFLAG = 0
|
||||
|
||||
cdef float EPSILON = 1e-6
|
||||
|
||||
# XXX: Careful to not change the order of the arguments. It is important to
|
||||
# have is_leaf and max_width consecutive as it permits to avoid padding by
|
||||
# the compiler and keep the size coherent for both C and numpy data structures.
|
||||
cdef struct Cell:
|
||||
# Base storage structure for cells in a QuadTree object
|
||||
|
||||
# Tree structure
|
||||
intp_t parent # Parent cell of this cell
|
||||
intp_t[8] children # Array pointing to children of this cell
|
||||
|
||||
# Cell description
|
||||
intp_t cell_id # Id of the cell in the cells array in the Tree
|
||||
intp_t point_index # Index of the point at this cell (only defined
|
||||
# # in non empty leaf)
|
||||
bint is_leaf # Does this cell have children?
|
||||
float32_t squared_max_width # Squared value of the maximum width w
|
||||
intp_t depth # Depth of the cell in the tree
|
||||
intp_t cumulative_size # Number of points included in the subtree with
|
||||
# # this cell as a root.
|
||||
|
||||
# Internal constants
|
||||
float32_t[3] center # Store the center for quick split of cells
|
||||
float32_t[3] barycenter # Keep track of the center of mass of the cell
|
||||
|
||||
# Cell boundaries
|
||||
float32_t[3] min_bounds # Inferior boundaries of this cell (inclusive)
|
||||
float32_t[3] max_bounds # Superior boundaries of this cell (exclusive)
|
||||
|
||||
|
||||
cdef class _QuadTree:
|
||||
# The QuadTree object is a quad tree structure constructed by inserting
|
||||
# recursively points in the tree and splitting cells in 4 so that each
|
||||
# leaf cell contains at most one point.
|
||||
# This structure also handle 3D data, inserted in trees with 8 children
|
||||
# for each node.
|
||||
|
||||
# Parameters of the tree
|
||||
cdef public int n_dimensions # Number of dimensions in X
|
||||
cdef public int verbose # Verbosity of the output
|
||||
cdef intp_t n_cells_per_cell # Number of children per node. (2 ** n_dimension)
|
||||
|
||||
# Tree inner structure
|
||||
cdef public intp_t max_depth # Max depth of the tree
|
||||
cdef public intp_t cell_count # Counter for node IDs
|
||||
cdef public intp_t capacity # Capacity of tree, in terms of nodes
|
||||
cdef public intp_t n_points # Total number of points
|
||||
cdef Cell* cells # Array of nodes
|
||||
|
||||
# Point insertion methods
|
||||
cdef int insert_point(self, float32_t[3] point, intp_t point_index,
|
||||
intp_t cell_id=*) except -1 nogil
|
||||
cdef intp_t _insert_point_in_new_child(self, float32_t[3] point, Cell* cell,
|
||||
intp_t point_index, intp_t size=*
|
||||
) noexcept nogil
|
||||
cdef intp_t _select_child(self, float32_t[3] point, Cell* cell) noexcept nogil
|
||||
cdef bint _is_duplicate(self, float32_t[3] point1, float32_t[3] point2) noexcept nogil
|
||||
|
||||
# Create a summary of the Tree compare to a query point
|
||||
cdef long summarize(self, float32_t[3] point, float32_t* results,
|
||||
float squared_theta=*, intp_t cell_id=*, long idx=*
|
||||
) noexcept nogil
|
||||
|
||||
# Internal cell initialization methods
|
||||
cdef void _init_cell(self, Cell* cell, intp_t parent, intp_t depth) noexcept nogil
|
||||
cdef void _init_root(self, float32_t[3] min_bounds, float32_t[3] max_bounds
|
||||
) noexcept nogil
|
||||
|
||||
# Private methods
|
||||
cdef int _check_point_in_cell(self, float32_t[3] point, Cell* cell
|
||||
) except -1 nogil
|
||||
|
||||
# Private array manipulation to manage the ``cells`` array
|
||||
cdef int _resize(self, intp_t capacity) except -1 nogil
|
||||
cdef int _resize_c(self, intp_t capacity=*) except -1 nogil
|
||||
cdef int _get_cell(self, float32_t[3] point, intp_t cell_id=*) except -1 nogil
|
||||
cdef Cell[:] _get_cell_ndarray(self)
|
||||
@@ -0,0 +1,609 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
|
||||
from cpython cimport Py_INCREF, PyObject, PyTypeObject
|
||||
|
||||
from libc.math cimport fabsf
|
||||
from libc.stdlib cimport free
|
||||
from libc.string cimport memcpy
|
||||
from libc.stdio cimport printf
|
||||
from libc.stdint cimport SIZE_MAX
|
||||
|
||||
from ..tree._utils cimport safe_realloc
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as cnp
|
||||
cnp.import_array()
|
||||
|
||||
cdef extern from "numpy/arrayobject.h":
|
||||
object PyArray_NewFromDescr(PyTypeObject* subtype, cnp.dtype descr,
|
||||
int nd, cnp.npy_intp* dims,
|
||||
cnp.npy_intp* strides,
|
||||
void* data, int flags, object obj)
|
||||
int PyArray_SetBaseObject(cnp.ndarray arr, PyObject* obj)
|
||||
|
||||
# Build the corresponding numpy dtype for Cell.
|
||||
# This works by casting `dummy` to an array of Cell of length 1, which numpy
|
||||
# can construct a `dtype`-object for. See https://stackoverflow.com/q/62448946
|
||||
# for a more detailed explanation.
|
||||
cdef Cell dummy
|
||||
CELL_DTYPE = np.asarray(<Cell[:1]>(&dummy)).dtype
|
||||
|
||||
assert CELL_DTYPE.itemsize == sizeof(Cell)
|
||||
|
||||
|
||||
cdef class _QuadTree:
|
||||
"""Array-based representation of a QuadTree.
|
||||
|
||||
This class is currently working for indexing 2D data (regular QuadTree) and
|
||||
for indexing 3D data (OcTree). It is planned to split the 2 implementations
|
||||
using `Cython.Tempita` to save some memory for QuadTree.
|
||||
|
||||
Note that this code is currently internally used only by the Barnes-Hut
|
||||
method in `sklearn.manifold.TSNE`. It is planned to be refactored and
|
||||
generalized in the future to be compatible with nearest neighbors API of
|
||||
`sklearn.neighbors` with 2D and 3D data.
|
||||
"""
|
||||
def __cinit__(self, int n_dimensions, int verbose):
|
||||
"""Constructor."""
|
||||
# Parameters of the tree
|
||||
self.n_dimensions = n_dimensions
|
||||
self.verbose = verbose
|
||||
self.n_cells_per_cell = <int> (2 ** self.n_dimensions)
|
||||
|
||||
# Inner structures
|
||||
self.max_depth = 0
|
||||
self.cell_count = 0
|
||||
self.capacity = 0
|
||||
self.n_points = 0
|
||||
self.cells = NULL
|
||||
|
||||
def __dealloc__(self):
|
||||
"""Destructor."""
|
||||
# Free all inner structures
|
||||
free(self.cells)
|
||||
|
||||
@property
|
||||
def cumulative_size(self):
|
||||
cdef Cell[:] cell_mem_view = self._get_cell_ndarray()
|
||||
return cell_mem_view.base['cumulative_size'][:self.cell_count]
|
||||
|
||||
@property
|
||||
def leafs(self):
|
||||
cdef Cell[:] cell_mem_view = self._get_cell_ndarray()
|
||||
return cell_mem_view.base['is_leaf'][:self.cell_count]
|
||||
|
||||
def build_tree(self, X):
|
||||
"""Build a tree from an array of points X."""
|
||||
cdef:
|
||||
int i
|
||||
float32_t[3] pt
|
||||
float32_t[3] min_bounds, max_bounds
|
||||
|
||||
# validate X and prepare for query
|
||||
# X = check_array(X, dtype=float32_t, order='C')
|
||||
n_samples = X.shape[0]
|
||||
|
||||
capacity = 100
|
||||
self._resize(capacity)
|
||||
m = np.min(X, axis=0)
|
||||
M = np.max(X, axis=0)
|
||||
# Scale the maximum to get all points strictly in the tree bounding box
|
||||
# The 3 bounds are for positive, negative and small values
|
||||
M = np.maximum(M * (1. + 1e-3 * np.sign(M)), M + 1e-3)
|
||||
for i in range(self.n_dimensions):
|
||||
min_bounds[i] = m[i]
|
||||
max_bounds[i] = M[i]
|
||||
|
||||
if self.verbose > 10:
|
||||
printf("[QuadTree] bounding box axis %i : [%f, %f]\n",
|
||||
i, min_bounds[i], max_bounds[i])
|
||||
|
||||
# Create the initial node with boundaries from the dataset
|
||||
self._init_root(min_bounds, max_bounds)
|
||||
|
||||
for i in range(n_samples):
|
||||
for j in range(self.n_dimensions):
|
||||
pt[j] = X[i, j]
|
||||
self.insert_point(pt, i)
|
||||
|
||||
# Shrink the cells array to reduce memory usage
|
||||
self._resize(capacity=self.cell_count)
|
||||
|
||||
cdef int insert_point(self, float32_t[3] point, intp_t point_index,
|
||||
intp_t cell_id=0) except -1 nogil:
|
||||
"""Insert a point in the QuadTree."""
|
||||
cdef int ax
|
||||
cdef intp_t selected_child
|
||||
cdef Cell* cell = &self.cells[cell_id]
|
||||
cdef intp_t n_point = cell.cumulative_size
|
||||
|
||||
if self.verbose > 10:
|
||||
printf("[QuadTree] Inserting depth %li\n", cell.depth)
|
||||
|
||||
# Assert that the point is in the right range
|
||||
if DEBUGFLAG:
|
||||
self._check_point_in_cell(point, cell)
|
||||
|
||||
# If the cell is an empty leaf, insert the point in it
|
||||
if cell.cumulative_size == 0:
|
||||
cell.cumulative_size = 1
|
||||
self.n_points += 1
|
||||
for i in range(self.n_dimensions):
|
||||
cell.barycenter[i] = point[i]
|
||||
cell.point_index = point_index
|
||||
if self.verbose > 10:
|
||||
printf("[QuadTree] inserted point %li in cell %li\n",
|
||||
point_index, cell_id)
|
||||
return cell_id
|
||||
|
||||
# If the cell is not a leaf, update cell internals and
|
||||
# recurse in selected child
|
||||
if not cell.is_leaf:
|
||||
for ax in range(self.n_dimensions):
|
||||
# barycenter update using a weighted mean
|
||||
cell.barycenter[ax] = (
|
||||
n_point * cell.barycenter[ax] + point[ax]) / (n_point + 1)
|
||||
|
||||
# Increase the size of the subtree starting from this cell
|
||||
cell.cumulative_size += 1
|
||||
|
||||
# Insert child in the correct subtree
|
||||
selected_child = self._select_child(point, cell)
|
||||
if self.verbose > 49:
|
||||
printf("[QuadTree] selected child %li\n", selected_child)
|
||||
if selected_child == -1:
|
||||
self.n_points += 1
|
||||
return self._insert_point_in_new_child(point, cell, point_index)
|
||||
return self.insert_point(point, point_index, selected_child)
|
||||
|
||||
# Finally, if the cell is a leaf with a point already inserted,
|
||||
# split the cell in n_cells_per_cell if the point is not a duplicate.
|
||||
# If it is a duplicate, increase the size of the leaf and return.
|
||||
if self._is_duplicate(point, cell.barycenter):
|
||||
if self.verbose > 10:
|
||||
printf("[QuadTree] found a duplicate!\n")
|
||||
cell.cumulative_size += 1
|
||||
self.n_points += 1
|
||||
return cell_id
|
||||
|
||||
# In a leaf, the barycenter correspond to the only point included
|
||||
# in it.
|
||||
self._insert_point_in_new_child(cell.barycenter, cell, cell.point_index,
|
||||
cell.cumulative_size)
|
||||
return self.insert_point(point, point_index, cell_id)
|
||||
|
||||
# XXX: This operation is not Thread safe
|
||||
cdef intp_t _insert_point_in_new_child(
|
||||
self, float32_t[3] point, Cell* cell, intp_t point_index, intp_t size=1
|
||||
) noexcept nogil:
|
||||
"""Create a child of cell which will contain point."""
|
||||
|
||||
# Local variable definition
|
||||
cdef:
|
||||
intp_t cell_id, cell_child_id, parent_id
|
||||
float32_t[3] save_point
|
||||
float32_t width
|
||||
Cell* child
|
||||
int i
|
||||
|
||||
# If the maximal capacity of the Tree have been reached, double the capacity
|
||||
# We need to save the current cell id and the current point to retrieve them
|
||||
# in case the reallocation
|
||||
if self.cell_count + 1 > self.capacity:
|
||||
parent_id = cell.cell_id
|
||||
for i in range(self.n_dimensions):
|
||||
save_point[i] = point[i]
|
||||
self._resize(SIZE_MAX)
|
||||
cell = &self.cells[parent_id]
|
||||
point = save_point
|
||||
|
||||
# Get an empty cell and initialize it
|
||||
cell_id = self.cell_count
|
||||
self.cell_count += 1
|
||||
child = &self.cells[cell_id]
|
||||
|
||||
self._init_cell(child, cell.cell_id, cell.depth + 1)
|
||||
child.cell_id = cell_id
|
||||
|
||||
# Set the cell as an inner cell of the Tree
|
||||
cell.is_leaf = False
|
||||
cell.point_index = -1
|
||||
|
||||
# Set the correct boundary for the cell, store the point in the cell
|
||||
# and compute its index in the children array.
|
||||
cell_child_id = 0
|
||||
for i in range(self.n_dimensions):
|
||||
cell_child_id *= 2
|
||||
if point[i] >= cell.center[i]:
|
||||
cell_child_id += 1
|
||||
child.min_bounds[i] = cell.center[i]
|
||||
child.max_bounds[i] = cell.max_bounds[i]
|
||||
else:
|
||||
child.min_bounds[i] = cell.min_bounds[i]
|
||||
child.max_bounds[i] = cell.center[i]
|
||||
child.center[i] = (child.min_bounds[i] + child.max_bounds[i]) / 2.
|
||||
width = child.max_bounds[i] - child.min_bounds[i]
|
||||
|
||||
child.barycenter[i] = point[i]
|
||||
child.squared_max_width = max(child.squared_max_width, width*width)
|
||||
|
||||
# Store the point info and the size to account for duplicated points
|
||||
child.point_index = point_index
|
||||
child.cumulative_size = size
|
||||
|
||||
# Store the child cell in the correct place in children
|
||||
cell.children[cell_child_id] = child.cell_id
|
||||
|
||||
if DEBUGFLAG:
|
||||
# Assert that the point is in the right range
|
||||
self._check_point_in_cell(point, child)
|
||||
if self.verbose > 10:
|
||||
printf("[QuadTree] inserted point %li in new child %li\n",
|
||||
point_index, cell_id)
|
||||
|
||||
return cell_id
|
||||
|
||||
cdef bint _is_duplicate(self, float32_t[3] point1, float32_t[3] point2) noexcept nogil:
|
||||
"""Check if the two given points are equals."""
|
||||
cdef int i
|
||||
cdef bint res = True
|
||||
for i in range(self.n_dimensions):
|
||||
# Use EPSILON to avoid numerical error that would overgrow the tree
|
||||
res &= fabsf(point1[i] - point2[i]) <= EPSILON
|
||||
return res
|
||||
|
||||
cdef intp_t _select_child(self, float32_t[3] point, Cell* cell) noexcept nogil:
|
||||
"""Select the child of cell which contains the given query point."""
|
||||
cdef:
|
||||
int i
|
||||
intp_t selected_child = 0
|
||||
|
||||
for i in range(self.n_dimensions):
|
||||
# Select the correct child cell to insert the point by comparing
|
||||
# it to the borders of the cells using precomputed center.
|
||||
selected_child *= 2
|
||||
if point[i] >= cell.center[i]:
|
||||
selected_child += 1
|
||||
return cell.children[selected_child]
|
||||
|
||||
cdef void _init_cell(self, Cell* cell, intp_t parent, intp_t depth) noexcept nogil:
|
||||
"""Initialize a cell structure with some constants."""
|
||||
cell.parent = parent
|
||||
cell.is_leaf = True
|
||||
cell.depth = depth
|
||||
cell.squared_max_width = 0
|
||||
cell.cumulative_size = 0
|
||||
for i in range(self.n_cells_per_cell):
|
||||
cell.children[i] = SIZE_MAX
|
||||
|
||||
cdef void _init_root(self, float32_t[3] min_bounds, float32_t[3] max_bounds
|
||||
) noexcept nogil:
|
||||
"""Initialize the root node with the given space boundaries"""
|
||||
cdef:
|
||||
int i
|
||||
float32_t width
|
||||
Cell* root = &self.cells[0]
|
||||
|
||||
self._init_cell(root, -1, 0)
|
||||
for i in range(self.n_dimensions):
|
||||
root.min_bounds[i] = min_bounds[i]
|
||||
root.max_bounds[i] = max_bounds[i]
|
||||
root.center[i] = (max_bounds[i] + min_bounds[i]) / 2.
|
||||
width = max_bounds[i] - min_bounds[i]
|
||||
root.squared_max_width = max(root.squared_max_width, width*width)
|
||||
root.cell_id = 0
|
||||
|
||||
self.cell_count += 1
|
||||
|
||||
cdef int _check_point_in_cell(self, float32_t[3] point, Cell* cell
|
||||
) except -1 nogil:
|
||||
"""Check that the given point is in the cell boundaries."""
|
||||
|
||||
if self.verbose >= 50:
|
||||
if self.n_dimensions == 3:
|
||||
printf("[QuadTree] Checking point (%f, %f, %f) in cell %li "
|
||||
"([%f/%f, %f/%f, %f/%f], size %li)\n",
|
||||
point[0], point[1], point[2], cell.cell_id,
|
||||
cell.min_bounds[0], cell.max_bounds[0], cell.min_bounds[1],
|
||||
cell.max_bounds[1], cell.min_bounds[2], cell.max_bounds[2],
|
||||
cell.cumulative_size)
|
||||
else:
|
||||
printf("[QuadTree] Checking point (%f, %f) in cell %li "
|
||||
"([%f/%f, %f/%f], size %li)\n",
|
||||
point[0], point[1], cell.cell_id, cell.min_bounds[0],
|
||||
cell.max_bounds[0], cell.min_bounds[1],
|
||||
cell.max_bounds[1], cell.cumulative_size)
|
||||
|
||||
for i in range(self.n_dimensions):
|
||||
if (cell.min_bounds[i] > point[i] or
|
||||
cell.max_bounds[i] <= point[i]):
|
||||
with gil:
|
||||
msg = "[QuadTree] InsertionError: point out of cell "
|
||||
msg += "boundary.\nAxis %li: cell [%f, %f]; point %f\n"
|
||||
|
||||
msg %= i, cell.min_bounds[i], cell.max_bounds[i], point[i]
|
||||
raise ValueError(msg)
|
||||
|
||||
def _check_coherence(self):
|
||||
"""Check the coherence of the cells of the tree.
|
||||
|
||||
Check that the info stored in each cell is compatible with the info
|
||||
stored in descendent and sibling cells. Raise a ValueError if this
|
||||
fails.
|
||||
"""
|
||||
for cell in self.cells[:self.cell_count]:
|
||||
# Check that the barycenter of inserted point is within the cell
|
||||
# boundaries
|
||||
self._check_point_in_cell(cell.barycenter, &cell)
|
||||
|
||||
if not cell.is_leaf:
|
||||
# Compute the number of point in children and compare with
|
||||
# its cummulative_size.
|
||||
n_points = 0
|
||||
for idx in range(self.n_cells_per_cell):
|
||||
child_id = cell.children[idx]
|
||||
if child_id != -1:
|
||||
child = self.cells[child_id]
|
||||
n_points += child.cumulative_size
|
||||
assert child.cell_id == child_id, (
|
||||
"Cell id not correctly initialized.")
|
||||
if n_points != cell.cumulative_size:
|
||||
raise ValueError(
|
||||
"Cell {} is incoherent. Size={} but found {} points "
|
||||
"in children. ({})"
|
||||
.format(cell.cell_id, cell.cumulative_size,
|
||||
n_points, cell.children))
|
||||
|
||||
# Make sure that the number of point in the tree correspond to the
|
||||
# cumulative size in root cell.
|
||||
if self.n_points != self.cells[0].cumulative_size:
|
||||
raise ValueError(
|
||||
"QuadTree is incoherent. Size={} but found {} points "
|
||||
"in children."
|
||||
.format(self.n_points, self.cells[0].cumulative_size))
|
||||
|
||||
cdef long summarize(self, float32_t[3] point, float32_t* results,
|
||||
float squared_theta=.5, intp_t cell_id=0, long idx=0
|
||||
) noexcept nogil:
|
||||
"""Summarize the tree compared to a query point.
|
||||
|
||||
Input arguments
|
||||
---------------
|
||||
point : array (n_dimensions)
|
||||
query point to construct the summary.
|
||||
cell_id : integer, optional (default: 0)
|
||||
current cell of the tree summarized. This should be set to 0 for
|
||||
external calls.
|
||||
idx : integer, optional (default: 0)
|
||||
current index in the result array. This should be set to 0 for
|
||||
external calls
|
||||
squared_theta: float, optional (default: .5)
|
||||
threshold to decide whether the node is sufficiently far
|
||||
from the query point to be a good summary. The formula is such that
|
||||
the node is a summary if
|
||||
node_width^2 / dist_node_point^2 < squared_theta.
|
||||
Note that the argument should be passed as theta^2 to avoid
|
||||
computing square roots of the distances.
|
||||
|
||||
Output arguments
|
||||
----------------
|
||||
results : array (n_samples * (n_dimensions+2))
|
||||
result will contain a summary of the tree information compared to
|
||||
the query point:
|
||||
- results[idx:idx+n_dimensions] contains the coordinate-wise
|
||||
difference between the query point and the summary cell idx.
|
||||
This is useful in t-SNE to compute the negative forces.
|
||||
- result[idx+n_dimensions+1] contains the squared euclidean
|
||||
distance to the summary cell idx.
|
||||
- result[idx+n_dimensions+2] contains the number of point of the
|
||||
tree contained in the summary cell idx.
|
||||
|
||||
Return
|
||||
------
|
||||
idx : integer
|
||||
number of elements in the results array.
|
||||
"""
|
||||
cdef:
|
||||
int i, idx_d = idx + self.n_dimensions
|
||||
bint duplicate = True
|
||||
Cell* cell = &self.cells[cell_id]
|
||||
|
||||
results[idx_d] = 0.
|
||||
for i in range(self.n_dimensions):
|
||||
results[idx + i] = point[i] - cell.barycenter[i]
|
||||
results[idx_d] += results[idx + i] * results[idx + i]
|
||||
duplicate &= fabsf(results[idx + i]) <= EPSILON
|
||||
|
||||
# Do not compute self interactions
|
||||
if duplicate and cell.is_leaf:
|
||||
return idx
|
||||
|
||||
# Check whether we can use this node as a summary
|
||||
# It's a summary node if the angular size as measured from the point
|
||||
# is relatively small (w.r.t. theta) or if it is a leaf node.
|
||||
# If it can be summarized, we use the cell center of mass
|
||||
# Otherwise, we go a higher level of resolution and into the leaves.
|
||||
if cell.is_leaf or (
|
||||
(cell.squared_max_width / results[idx_d]) < squared_theta):
|
||||
results[idx_d + 1] = <float32_t> cell.cumulative_size
|
||||
return idx + self.n_dimensions + 2
|
||||
|
||||
else:
|
||||
# Recursively compute the summary in nodes
|
||||
for c in range(self.n_cells_per_cell):
|
||||
child_id = cell.children[c]
|
||||
if child_id != -1:
|
||||
idx = self.summarize(point, results, squared_theta,
|
||||
child_id, idx)
|
||||
|
||||
return idx
|
||||
|
||||
def get_cell(self, point):
|
||||
"""return the id of the cell containing the query point or raise
|
||||
ValueError if the point is not in the tree
|
||||
"""
|
||||
cdef float32_t[3] query_pt
|
||||
cdef int i
|
||||
|
||||
assert len(point) == self.n_dimensions, (
|
||||
"Query point should be a point in dimension {}."
|
||||
.format(self.n_dimensions))
|
||||
|
||||
for i in range(self.n_dimensions):
|
||||
query_pt[i] = point[i]
|
||||
|
||||
return self._get_cell(query_pt, 0)
|
||||
|
||||
cdef int _get_cell(self, float32_t[3] point, intp_t cell_id=0
|
||||
) except -1 nogil:
|
||||
"""guts of get_cell.
|
||||
|
||||
Return the id of the cell containing the query point or raise ValueError
|
||||
if the point is not in the tree"""
|
||||
cdef:
|
||||
intp_t selected_child
|
||||
Cell* cell = &self.cells[cell_id]
|
||||
|
||||
if cell.is_leaf:
|
||||
if self._is_duplicate(cell.barycenter, point):
|
||||
if self.verbose > 99:
|
||||
printf("[QuadTree] Found point in cell: %li\n",
|
||||
cell.cell_id)
|
||||
return cell_id
|
||||
with gil:
|
||||
raise ValueError("Query point not in the Tree.")
|
||||
|
||||
selected_child = self._select_child(point, cell)
|
||||
if selected_child > 0:
|
||||
if self.verbose > 99:
|
||||
printf("[QuadTree] Selected_child: %li\n", selected_child)
|
||||
return self._get_cell(point, selected_child)
|
||||
with gil:
|
||||
raise ValueError("Query point not in the Tree.")
|
||||
|
||||
# Pickling primitives
|
||||
|
||||
def __reduce__(self):
|
||||
"""Reduce re-implementation, for pickling."""
|
||||
return (_QuadTree, (self.n_dimensions, self.verbose), self.__getstate__())
|
||||
|
||||
def __getstate__(self):
|
||||
"""Getstate re-implementation, for pickling."""
|
||||
d = {}
|
||||
# capacity is inferred during the __setstate__ using nodes
|
||||
d["max_depth"] = self.max_depth
|
||||
d["cell_count"] = self.cell_count
|
||||
d["capacity"] = self.capacity
|
||||
d["n_points"] = self.n_points
|
||||
d["cells"] = self._get_cell_ndarray().base
|
||||
return d
|
||||
|
||||
def __setstate__(self, d):
|
||||
"""Setstate re-implementation, for unpickling."""
|
||||
self.max_depth = d["max_depth"]
|
||||
self.cell_count = d["cell_count"]
|
||||
self.capacity = d["capacity"]
|
||||
self.n_points = d["n_points"]
|
||||
|
||||
if 'cells' not in d:
|
||||
raise ValueError('You have loaded Tree version which '
|
||||
'cannot be imported')
|
||||
|
||||
cell_ndarray = d['cells']
|
||||
|
||||
if (cell_ndarray.ndim != 1 or
|
||||
cell_ndarray.dtype != CELL_DTYPE or
|
||||
not cell_ndarray.flags.c_contiguous):
|
||||
raise ValueError('Did not recognise loaded array layout')
|
||||
|
||||
self.capacity = cell_ndarray.shape[0]
|
||||
if self._resize_c(self.capacity) != 0:
|
||||
raise MemoryError("resizing tree to %d" % self.capacity)
|
||||
|
||||
cdef Cell[:] cell_mem_view = cell_ndarray
|
||||
memcpy(
|
||||
pto=self.cells,
|
||||
pfrom=&cell_mem_view[0],
|
||||
size=self.capacity * sizeof(Cell),
|
||||
)
|
||||
|
||||
# Array manipulation methods, to convert it to numpy or to resize
|
||||
# self.cells array
|
||||
|
||||
cdef Cell[:] _get_cell_ndarray(self):
|
||||
"""Wraps nodes as a NumPy struct array.
|
||||
|
||||
The array keeps a reference to this Tree, which manages the underlying
|
||||
memory. Individual fields are publicly accessible as properties of the
|
||||
Tree.
|
||||
"""
|
||||
cdef cnp.npy_intp shape[1]
|
||||
shape[0] = <cnp.npy_intp> self.cell_count
|
||||
cdef cnp.npy_intp strides[1]
|
||||
strides[0] = sizeof(Cell)
|
||||
cdef Cell[:] arr
|
||||
Py_INCREF(CELL_DTYPE)
|
||||
arr = PyArray_NewFromDescr(
|
||||
subtype=<PyTypeObject *> np.ndarray,
|
||||
descr=CELL_DTYPE,
|
||||
nd=1,
|
||||
dims=shape,
|
||||
strides=strides,
|
||||
data=<void*> self.cells,
|
||||
flags=cnp.NPY_ARRAY_DEFAULT,
|
||||
obj=None,
|
||||
)
|
||||
Py_INCREF(self)
|
||||
if PyArray_SetBaseObject(arr.base, <PyObject*> self) < 0:
|
||||
raise ValueError("Can't initialize array!")
|
||||
return arr
|
||||
|
||||
cdef int _resize(self, intp_t capacity) except -1 nogil:
|
||||
"""Resize all inner arrays to `capacity`, if `capacity` == -1, then
|
||||
double the size of the inner arrays.
|
||||
|
||||
Returns -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
if self._resize_c(capacity) != 0:
|
||||
# Acquire gil only if we need to raise
|
||||
with gil:
|
||||
raise MemoryError()
|
||||
|
||||
cdef int _resize_c(self, intp_t capacity=SIZE_MAX) except -1 nogil:
|
||||
"""Guts of _resize
|
||||
|
||||
Returns -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
if capacity == self.capacity and self.cells != NULL:
|
||||
return 0
|
||||
|
||||
if <size_t> capacity == SIZE_MAX:
|
||||
if self.capacity == 0:
|
||||
capacity = 9 # default initial value to min
|
||||
else:
|
||||
capacity = 2 * self.capacity
|
||||
|
||||
safe_realloc(&self.cells, capacity)
|
||||
|
||||
# if capacity smaller than cell_count, adjust the counter
|
||||
if capacity < self.cell_count:
|
||||
self.cell_count = capacity
|
||||
|
||||
self.capacity = capacity
|
||||
return 0
|
||||
|
||||
def _py_summarize(self, float32_t[:] query_pt, float32_t[:, :] X, float angle):
|
||||
# Used for testing summarize
|
||||
cdef:
|
||||
float32_t[:] summary
|
||||
int n_samples
|
||||
|
||||
n_samples = X.shape[0]
|
||||
summary = np.empty(4 * n_samples, dtype=np.float32)
|
||||
|
||||
idx = self.summarize(&query_pt[0], &summary[0], angle * angle)
|
||||
return idx, summary
|
||||
@@ -0,0 +1,53 @@
|
||||
_binary_tree_pxi = custom_target(
|
||||
'_binary_tree_pxi',
|
||||
output: '_binary_tree.pxi',
|
||||
input: '_binary_tree.pxi.tp',
|
||||
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
|
||||
)
|
||||
|
||||
# .pyx is generated so this is needed to make Cython compilation work. The pxi
|
||||
# file is included avoid "missing dependency paths" with ninja -t missindeps
|
||||
neighbors_cython_tree = [
|
||||
fs.copyfile('__init__.py'),
|
||||
fs.copyfile('_partition_nodes.pxd'),
|
||||
_binary_tree_pxi,
|
||||
]
|
||||
|
||||
name_list = ['_ball_tree', '_kd_tree']
|
||||
|
||||
foreach name: name_list
|
||||
pyx = custom_target(
|
||||
name + '_pyx',
|
||||
output: name + '.pyx',
|
||||
input: name + '.pyx.tp',
|
||||
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
|
||||
# TODO in principle this should go in py.exension_module below. This is
|
||||
# temporary work-around for dependency issue with .pyx.tp files. For more
|
||||
# details, see https://github.com/mesonbuild/meson/issues/13212
|
||||
depends: [neighbors_cython_tree, utils_cython_tree, metrics_cython_tree],
|
||||
)
|
||||
py.extension_module(
|
||||
name,
|
||||
cython_gen.process(pyx),
|
||||
dependencies: [np_dep],
|
||||
subdir: 'sklearn/neighbors',
|
||||
install: true
|
||||
)
|
||||
endforeach
|
||||
|
||||
neighbors_extension_metadata = {
|
||||
'_partition_nodes':
|
||||
{'sources': [cython_gen_cpp.process('_partition_nodes.pyx')],
|
||||
'dependencies': [np_dep]},
|
||||
'_quad_tree': {'sources': [cython_gen.process('_quad_tree.pyx')], 'dependencies': [np_dep]},
|
||||
}
|
||||
|
||||
foreach ext_name, ext_dict : neighbors_extension_metadata
|
||||
py.extension_module(
|
||||
ext_name,
|
||||
[ext_dict.get('sources'), utils_cython_tree],
|
||||
dependencies: ext_dict.get('dependencies'),
|
||||
subdir: 'sklearn/neighbors',
|
||||
install: true
|
||||
)
|
||||
endforeach
|
||||
Reference in New Issue
Block a user