feat: add Camera Work AI package

This commit is contained in:
KINDNICK
2026-08-03 04:42:44 +09:00
parent 26c578416f
commit 02d76bc87c
2187 changed files with 119610 additions and 1 deletions
@@ -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
@@ -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
@@ -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
@@ -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