Skip to content

Commit 9d83fa7

Browse files
committed
Python interface search_box input updated.
1 parent fb2a166 commit 9d83fa7

File tree

5 files changed

+72
-42
lines changed

5 files changed

+72
-42
lines changed

examples/python/kd_tree.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,20 @@ def tree_creation_and_query_types():
7474
# A box search returns the same data structure as a radius search.
7575
# However, instead of containing neighbors it simply contains
7676
# indices.
77-
min = np.array([[0, 0], [2, 2], [0, 0], [6, 6]], dtype=np.float32)
78-
max = np.array([[3, 3], [3, 3], [9, 9], [9, 9]], dtype=np.float32)
79-
bnns = t.search_box(min, max)
80-
t.search_box(min, max, bnns)
77+
# An array of input boxes is defined as follows:
78+
# [min_0, max_0, min_1, max_1, ...]
79+
boxes = np.array(
80+
[[0, 0],
81+
[3, 3],
82+
[2, 2],
83+
[3, 3],
84+
[0, 0],
85+
[9, 9],
86+
[6, 6],
87+
[9, 9]],
88+
dtype=np.float32)
89+
bnns = t.search_box(boxes)
90+
t.search_box(boxes, bnns)
8191
print("Results for the orthogonal box search:")
8292
for bnn in bnns:
8393
print(f"{bnn}")

src/pyco_tree/pico_tree/_pyco_tree/darray.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ class DArrayImpl : public DArrayImplBase {
4545
// It is important that at the binding side of things we ensure that the
4646
// array is kept alive while the view is alive.
4747
// NOTE: At the time of writing an undocumented feature.
48+
49+
// In case the size of a vector equals 0, its data pointer can equal
50+
// nullptr. When this happens, the library interface of numpy (as wrapped by
51+
// pybind11) will allocate some memory and store the address to that memory
52+
// instead of storing the nullptr address of the vector. This means that
53+
// each time we create a view for the same empty vector, the memory address
54+
// it stores may randomly change. This is not an issue, but good to document
55+
// here. See:
56+
// * PyArray_NewFromDescr(...)
57+
// * https://numpy.org/doc/1.13/reference/c-api.array.html
4858
return pybind11::array_t<T, 0>(
4959
array_[i].size(), array_[i].data(), pybind11::none());
5060
}

src/pyco_tree/pico_tree/_pyco_tree/def_kd_tree.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,23 +201,19 @@ Search for all neighbors within a radius of each of the input points.
201201
.def(
202202
"search_box",
203203
static_cast<void (KdTree::*)(
204-
py::array_t<Scalar, 0> const,
205-
py::array_t<Scalar, 0> const,
206-
Neighborhoods&) const>(&KdTree::SearchBox),
207-
py::arg("min").noconvert().none(false),
208-
py::arg("max").noconvert().none(false),
209-
py::arg("box").noconvert().none(false),
204+
py::array_t<Scalar, 0> const, Neighborhoods&) const>(
205+
&KdTree::SearchBox),
206+
py::arg("boxes").noconvert().none(false),
207+
py::arg("indices").noconvert().none(false),
210208
R"ptdoc(
211209
Search for all points within each of the axis aligned input boxes and
212210
store the result in the specified output.
213211
)ptdoc")
214212
.def(
215213
"search_box",
216-
static_cast<Neighborhoods (KdTree::*)(
217-
py::array_t<Scalar, 0> const, py::array_t<Scalar, 0> const)
214+
static_cast<Neighborhoods (KdTree::*)(py::array_t<Scalar, 0> const)
218215
const>(&KdTree::SearchBox),
219-
py::arg("min").noconvert().none(false),
220-
py::arg("max").noconvert().none(false),
216+
py::arg("boxes").noconvert().none(false),
221217
"Search for all points within each of the axis aligned input boxes.");
222218
}
223219

src/pyco_tree/pico_tree/_pyco_tree/kd_tree.hpp

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,32 +112,29 @@ class KdTree : public pico_tree::KdTree<Space_, Metric_> {
112112
}
113113

114114
void SearchBox(
115-
py::array_t<ScalarType, 0> const min,
116-
py::array_t<ScalarType, 0> const max,
117-
DArray& box) const {
118-
auto query_min = MakeMap<Dim>(min);
119-
auto query_max = MakeMap<Dim>(max);
115+
py::array_t<ScalarType, 0> const boxes, DArray& indices) const {
116+
auto query = MakeMap<Dim>(boxes);
120117

121-
if (query_min.size() != query_max.size()) {
118+
if (query.size() % 2 != 0) {
122119
throw std::invalid_argument("Query min and max don't have equal size.");
123120
}
124121

125-
auto& box_data = box.data<IndexType>();
126-
box_data.resize(query_min.size());
122+
std::size_t box_count = query.size() / std::size_t(2);
123+
auto& indices_data = indices.data<IndexType>();
124+
indices_data.resize(box_count);
127125

128126
#pragma omp parallel for schedule(dynamic, kChunkSize)
129127
// TODO Reduce the vector resize overhead
130-
for (SSize i = 0; i < static_cast<SSize>(query_min.size()); ++i) {
131-
Base::SearchBox(query_min[i], query_max[i], box_data[i]);
128+
for (SSize i = 0; i < static_cast<SSize>(box_count); ++i) {
129+
auto index = static_cast<pico_tree::Size>(i * 2);
130+
Base::SearchBox(query[index + 0], query[index + 1], indices_data[i]);
132131
}
133132
}
134133

135-
DArray SearchBox(
136-
py::array_t<ScalarType, 0> const min,
137-
py::array_t<ScalarType, 0> const max) const {
138-
DArray box = DArray(std::vector<std::vector<IndexType>>());
139-
SearchBox(min, max, box);
140-
return box;
134+
DArray SearchBox(py::array_t<ScalarType, 0> const boxes) const {
135+
DArray indices = DArray(std::vector<std::vector<IndexType>>());
136+
SearchBox(boxes, indices);
137+
return indices;
141138
}
142139

143140
inline ScalarType const* data() const { return points().data(); }

test/pyco_tree/kd_tree_test.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,28 +107,45 @@ def test_search_radius(self):
107107
self.assertEqual(nns[i][0][0], i)
108108
self.assertAlmostEqual(nns[i][0][1], 0)
109109

110-
# Test that the memory is re-used
111-
datas = [x.ctypes.data for x in nns]
110+
# Test that the memory is re-used by comparing memory addresses.
111+
# In case the size of an array equals zero, its memory address is
112+
# random. See darray.hpp for more details.
113+
def addresses(nns):
114+
return [x.ctypes.data if len(x) else 0 for x in nns]
115+
116+
datas = addresses(nns)
112117
t.search_radius(a, search_radius, nns)
113-
self.assertEqual([x.ctypes.data for x in nns], datas)
118+
self.assertEqual(addresses(nns), datas)
114119
t.search_radius(a, search_radius**2, nns)
115-
self.assertNotEqual([x.ctypes.data for x in nns], datas)
120+
self.assertNotEqual(addresses(nns), datas)
116121

117122
def test_search_box(self):
118123
a = np.array([[2, 1], [4, 3], [8, 7]], dtype=np.float32)
119124
t = pt.KdTree(a, pt.Metric.L2Squared, 10)
120-
121-
min = np.array([[0, 0], [2, 2], [0, 0], [6, 6]], dtype=np.float32)
122-
max = np.array([[3, 3], [3, 3], [9, 9], [9, 9]], dtype=np.float32)
123-
nns = t.search_box(min, max)
125+
boxes = np.array(
126+
[[0, 0],
127+
[3, 3],
128+
[2, 2],
129+
[3, 3],
130+
[0, 0],
131+
[9, 9],
132+
[6, 6],
133+
[9, 9]],
134+
dtype=np.float32)
135+
nns = t.search_box(boxes)
124136
self.assertEqual(len(nns), 4)
125137
self.assertEqual(nns.dtype, t.dtype_index)
126138
self.assertTrue(nns)
127139

128-
# Test that the memory is re-used
129-
nns[0][0] = 42
130-
t.search_box(min, max, nns)
131-
self.assertEqual(nns[0][0], 0)
140+
# Test that the memory is re-used by comparing memory addresses.
141+
# In case the size of an array equals zero, its memory address is
142+
# random. See darray.hpp for more details.
143+
def addresses(nns):
144+
return [x.ctypes.data if len(x) else 0 for x in nns]
145+
146+
datas = addresses(nns)
147+
t.search_box(boxes, nns)
148+
self.assertEqual(addresses(nns), datas)
132149

133150
# Check the number of indices found.
134151
sizes = [1, 0, 3, 1]

0 commit comments

Comments
 (0)