Skip to content

Commit 79372b8

Browse files
committed
Updated the S1 distance function coordinate range from [-pi...+pi] to [0...1].
Added search_box for topological spaces. * Wrapping box support added for topological metrics.
1 parent 51b95fa commit 79372b8

File tree

8 files changed

+285
-141
lines changed

8 files changed

+285
-141
lines changed

examples/kd_tree/kd_tree_search.cpp

+8-6
Original file line numberDiff line numberDiff line change
@@ -73,23 +73,25 @@ void search_r3() {
7373
}
7474
}
7575

76-
// Search on the circle.
76+
// This example shows how to search on the unit circle. Point coordinates must
77+
// lie within the range of [0...1]. Point coordinates wrap at 0 or 1. A point
78+
// with a coordinate value of 0 is considered the same as one with a coordinate
79+
// value of 1.
7780
void search_s1() {
7881
using point = pico_tree::point_1f;
82+
using scalar = typename pico_tree::point_1f::scalar_type;
7983
using space = std::vector<point>;
8084
using neighbor =
8185
pico_tree::kd_tree<space, pico_tree::metric_so2>::neighbor_type;
8286

83-
const auto pi = typename point::scalar_type(3.1415926537);
84-
8587
pico_tree::kd_tree<space, pico_tree::metric_so2> tree(
86-
pico_tree::generate_random_n<point>(512, -pi, pi),
88+
pico_tree::generate_random_n<point>(512, scalar(0.0), scalar(1.0)),
8789
pico_tree::max_leaf_size_t(10));
8890

8991
std::array<neighbor, 8> knn;
90-
tree.search_knn(point{pi}, knn.begin(), knn.end());
92+
tree.search_knn(point{1.0}, knn.begin(), knn.end());
9193

92-
// These prints show that wrapping around near point -PI ~ PI is supported.
94+
// These prints show that wrapping near values 0 ~ 1 is supported.
9395
std::cout << "Closest angles (index, distance, value): " << std::endl;
9496
for (auto const& nn : knn) {
9597
std::cout << " " << nn.index << ", " << nn.distance << ", "

setup.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
#!/usr/bin/env python3
2-
32
from skbuild import setup
43

54

6-
setup(name='pico_tree',
7-
# The same as the CMake project version.
8-
version='0.8.3',
9-
description='PicoTree Python Bindings',
10-
author='Jonathan Broere',
11-
url='https://github.com/Jaybro/pico_tree',
12-
license='MIT',
13-
packages=['pico_tree'],
14-
package_dir={'': 'src/pyco_tree'},
15-
cmake_install_dir='src/pyco_tree/pico_tree',
16-
python_requires='>=3.10',
17-
install_requires=['numpy'],
18-
)
5+
setup(
6+
name='pico_tree',
7+
# The same as the CMake project version.
8+
version='0.8.3',
9+
description='PicoTree Python Bindings',
10+
author='Jonathan Broere',
11+
url='https://github.com/Jaybro/pico_tree',
12+
license='MIT',
13+
packages=['pico_tree'],
14+
package_dir={'': 'src/pyco_tree'},
15+
cmake_install_dir='src/pyco_tree/pico_tree',
16+
python_requires='>=3.10',
17+
install_requires=['numpy'],
18+
)

src/pico_tree/pico_tree/internal/kd_tree_search.hpp

+74-22
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,14 @@ class search_nearest_topological {
165165
v,
166166
node->data.branch.left_min,
167167
node->data.branch.left_max,
168-
node->data.branch.split_dim);
168+
node->data.branch.split_dim,
169+
euclidean_space_tag{});
169170
scalar_type const d2 = metric_(
170171
v,
171172
node->data.branch.right_min,
172173
node->data.branch.right_max,
173-
node->data.branch.split_dim);
174+
node->data.branch.split_dim,
175+
euclidean_space_tag{});
174176
node_type const* node_1st;
175177
node_type const* node_2nd;
176178
scalar_type new_offset;
@@ -210,27 +212,28 @@ class search_nearest_topological {
210212
Visitor_& visitor_;
211213
};
212214

213-
//! \brief A functor that provides range searches for Euclidean spaces. Query
214-
//! time is bounded by O(n^(1-1/dimension)+k).
215+
//! \brief A functor that provides range searches for both Euclidean and
216+
//! topological spaces. Query time is bounded by O(n^(1-1/dimension)+k).
215217
//! \details Many tree nodes are excluded by checking if they intersect with the
216218
//! box of the query. We don't store the bounding box of each node but calculate
217219
//! them at run time. This slows down search_box in favor of having faster
218220
//! nearest neighbor searches.
219221
template <typename SpaceWrapper_, typename Metric_, typename Index_>
220-
class search_box_euclidean {
221-
public:
222-
// TODO Perhaps we can support it for both topological and Euclidean spaces.
223-
static_assert(
224-
std::is_same_v<typename Metric_::space_category, euclidean_space_tag>,
225-
"SEARCH_BOX_ONLY_SUPPORTED_FOR_EUCLIDEAN_SPACES");
222+
class search_box {
223+
using space_category = typename Metric_::space_category;
224+
static constexpr bool is_euclidean_space_v =
225+
std::is_same_v<space_category, euclidean_space_tag>;
226226

227+
public:
227228
using index_type = Index_;
228229
using scalar_type = typename SpaceWrapper_::scalar_type;
229230
static size_t constexpr dim = SpaceWrapper_::dim;
230231
using box_type = box<scalar_type, dim>;
231232
using box_map_type = box_map<scalar_type const, dim>;
233+
using node_type = typename kd_tree_space_tag_traits<
234+
space_category>::template node_type<index_type, scalar_type>;
232235

233-
inline search_box_euclidean(
236+
inline search_box(
234237
SpaceWrapper_ space,
235238
Metric_ metric,
236239
std::vector<index_type> const& indices,
@@ -245,13 +248,12 @@ class search_box_euclidean {
245248
idxs_(idxs) {}
246249

247250
//! \brief Range search starting from \p node.
248-
template <typename Node_>
249-
inline void operator()(Node_ const* const node) {
251+
inline void operator()(node_type const* const node) {
250252
if (node->is_leaf()) {
251253
auto begin = indices_.begin() + node->data.leaf.begin_idx;
252254
auto const end = indices_.begin() + node->data.leaf.end_idx;
253255
for (; begin < end; ++begin) {
254-
if (query_.contains(space_[*begin])) {
256+
if (contains(*begin)) {
255257
idxs_.push_back(*begin);
256258
}
257259
}
@@ -265,7 +267,7 @@ class search_box_euclidean {
265267
// down the left node.
266268
if (query_.contains(box_)) {
267269
report_node(node->left);
268-
} else if (query_.min(split_dim) <= node->data.branch.left_max) {
270+
} else if (intersects_left(split_dim, node)) {
269271
operator()(node->left);
270272
}
271273

@@ -276,7 +278,7 @@ class search_box_euclidean {
276278
// Same as the left side.
277279
if (query_.contains(box_)) {
278280
report_node(node->right);
279-
} else if (query_.max(split_dim) >= node->data.branch.right_min) {
281+
} else if (intersects_right(split_dim, node)) {
280282
operator()(node->right);
281283
}
282284

@@ -285,9 +287,61 @@ class search_box_euclidean {
285287
}
286288

287289
private:
290+
// TODO We could add an extra class layer to the box_base, box, and box_map
291+
// hierarchy, to support topological boxes. However, this is a lot more
292+
// code/work and we currently only use this feature here and in a unit test.
293+
bool contains(scalar_type const* const p) const {
294+
for (size_t i = 0; i < query_.size(); ++i) {
295+
if (metric_(
296+
p[i],
297+
query_.min(i),
298+
query_.max(i),
299+
static_cast<int>(i),
300+
topological_space_tag{}) > scalar_type(0.0)) {
301+
return false;
302+
}
303+
}
304+
return true;
305+
}
306+
307+
bool contains(index_type const idx) const {
308+
if constexpr (is_euclidean_space_v) {
309+
return query_.contains(space_[idx]);
310+
} else {
311+
return contains(space_[idx]);
312+
}
313+
}
314+
315+
bool contains() const {
316+
if constexpr (is_euclidean_space_v) {
317+
return query_.contains(box_);
318+
} else {
319+
return contains(box_.min()) && contains(box_.max());
320+
}
321+
}
322+
323+
bool intersects_left(
324+
size_t const split_dim, node_type const* const node) const {
325+
if constexpr (is_euclidean_space_v) {
326+
return query_.min(split_dim) <= node->data.branch.left_max;
327+
} else {
328+
return query_.min(split_dim) <= node->data.branch.left_max ||
329+
query_.max(split_dim) >= node->data.branch.left_min;
330+
}
331+
}
332+
333+
bool intersects_right(
334+
size_t const split_dim, node_type const* const node) const {
335+
if constexpr (is_euclidean_space_v) {
336+
return query_.max(split_dim) >= node->data.branch.right_min;
337+
} else {
338+
return query_.max(split_dim) >= node->data.branch.right_min ||
339+
query_.min(split_dim) <= node->data.branch.right_max;
340+
}
341+
}
342+
288343
//! \brief Reports all indices contained by \p node.
289-
template <typename Node_>
290-
inline void report_node(Node_ const* const node) const {
344+
inline void report_node(node_type const* const node) const {
291345
index_type begin;
292346
index_type end;
293347

@@ -309,17 +363,15 @@ class search_box_euclidean {
309363
std::back_inserter(idxs_));
310364
}
311365

312-
template <typename Node_>
313-
inline index_type report_left(Node_ const* const node) const {
366+
inline index_type report_left(node_type const* const node) const {
314367
if (node->is_leaf()) {
315368
return node->data.leaf.begin_idx;
316369
} else {
317370
return report_left(node->left);
318371
}
319372
}
320373

321-
template <typename Node_>
322-
inline index_type report_right(Node_ const* const node) const {
374+
inline index_type report_right(node_type const* const node) const {
323375
if (node->is_leaf()) {
324376
return node->data.leaf.end_idx;
325377
} else {

src/pico_tree/pico_tree/kd_tree.hpp

+7-3
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@ class kd_tree {
7171
splitter_rule_t<Rule_> const& rule = Rule_{})
7272
: space_(std::move(space)),
7373
metric_(),
74-
data_(internal::build_kd_tree<kd_tree_data_type, dim>()(
75-
space_wrapper_type(space_), stop_condition, start_bounds, rule)) {}
74+
data_(
75+
internal::build_kd_tree<kd_tree_data_type, dim>()(
76+
space_wrapper_type(space_),
77+
stop_condition,
78+
start_bounds,
79+
rule)) {}
7680

7781
//! \brief The kd_tree cannot be copied.
7882
//! \details The kd_tree uses pointers to nodes and copying pointers is not
@@ -287,7 +291,7 @@ class kd_tree {
287291
// now it is assumed that this check is not worth it: If there is any
288292
// overlap then the search is slower. So unless many queries don't intersect
289293
// there is no point in adding it.
290-
internal::search_box_euclidean<space_wrapper_type, Metric_, index_type>(
294+
internal::search_box<space_wrapper_type, Metric_, index_type>(
291295
space,
292296
metric_,
293297
data_.indices,

0 commit comments

Comments
 (0)