spatial-graph 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ from .rtree import RTree
2
+ import numpy as np
3
+
4
+
5
+ class LineRTree(RTree):
6
+ pyx_item_t_declaration = """
7
+ cdef struct item_t:
8
+ item_base_t u
9
+ item_base_t v
10
+ bool corner_mask[DIMS]
11
+ """
12
+
13
+ c_item_t_declaration = """
14
+ typedef struct item_t {
15
+ item_base_t u;
16
+ item_base_t v;
17
+ bool corner_mask[DIMS];
18
+ } item_t;
19
+ """
20
+
21
+ c_converter_functions = """
22
+ inline item_t convert_pyx_to_c_item(pyx_item_t *pyx_item, coord_t *start, coord_t *end) {
23
+ item_t item;
24
+ coord_t tmp;
25
+ item.u = (*pyx_item)[0];
26
+ item.v = (*pyx_item)[1];
27
+ for (int d = 0; d < DIMS; d++) {
28
+ item.corner_mask[d] = (start[d] < end[d]);
29
+ if (!item.corner_mask[d]) {
30
+ // swap coordinates to create bounding box
31
+ tmp = start[d];
32
+ start[d] = end[d];
33
+ end[d] = tmp;
34
+ }
35
+ }
36
+ return item;
37
+ }
38
+ inline void copy_c_to_pyx_item(const item_t c_item, pyx_item_t *pyx_item) {
39
+ (*pyx_item)[0] = c_item.u;
40
+ (*pyx_item)[1] = c_item.v;
41
+ }
42
+ """
43
+
44
+ c_equal_function = """
45
+ inline int equal(const item_t a, const item_t b) {
46
+ return (a.u == b.u && a.v == b.v);
47
+ }
48
+ """
49
+
50
+ c_distance_function = """
51
+ inline coord_t length2(const coord_t x[]) {
52
+ coord_t length2 = 0;
53
+ for (int d = 0; d < DIMS; d++) {
54
+ length2 += pow(x[d], 2);
55
+ }
56
+ return length2;
57
+ }
58
+
59
+ inline coord_t point_segment_dist2(const coord_t point[], const coord_t start[], const coord_t end[]) {
60
+
61
+ coord_t a[DIMS];
62
+ coord_t b[DIMS];
63
+ coord_t alpha = 0;
64
+
65
+ for (int d = 0; d < DIMS; d++) {
66
+
67
+ // subtract "start" from "end" and "point" to get "a" and "b"
68
+ a[d] = end[d] - start[d];
69
+ b[d] = point[d] - start[d];
70
+
71
+ // compute dot product "alpha" of "a" and "b"
72
+ alpha += a[d] * b[d];
73
+ }
74
+
75
+ // normalize dot product
76
+ alpha /= length2(a);
77
+
78
+ // clip at 0 and 1 (beginning and end of line segment)
79
+ alpha = min0(1, max0(0, alpha));
80
+
81
+ for (int d = 0; d < DIMS; d++) {
82
+
83
+ // multiply "a" by "alpha" to obtain closest segment point to "b"
84
+ a[d] *= alpha;
85
+
86
+ // subtract "b" from "a" to get offset
87
+ a[d] -= b[d];
88
+ }
89
+
90
+ // compute squared length of offset
91
+ return length2(a);
92
+ }
93
+
94
+ inline coord_t distance(const coord_t point[], const struct rect *rect, const struct item_t item) {
95
+ coord_t start[DIMS];
96
+ coord_t end[DIMS];
97
+ for (int d = 0; d < DIMS; d++) {
98
+ if (item.corner_mask[d]) {
99
+ start[d] = rect->min[d];
100
+ end[d] = rect->max[d];
101
+ } else {
102
+ start[d] = rect->max[d];
103
+ end[d] = rect->min[d];
104
+ }
105
+ }
106
+ return point_segment_dist2(point, start, end);
107
+ }
108
+ """
109
+
110
+ def insert_line(self, line, start, end):
111
+ """Convenience function to insert a single line. To insert multiple
112
+ lines in bulk, please use the faster `insert_lines`.
113
+
114
+ Args:
115
+
116
+ line (`item_dtype`):
117
+
118
+ The line identifier (as passed as the `item_dtype` to the
119
+ constructor).
120
+
121
+ start (`ndarray`, shape `(d,)`):
122
+
123
+ The coordinates of the start of the line.
124
+
125
+ end (`ndarray`, shape `(d,)`):
126
+
127
+ The coordinates of the end of the line.
128
+ """
129
+ lines = np.array([line], dtype=self.item_dtype.base)
130
+ starts = start[np.newaxis]
131
+ ends = end[np.newaxis]
132
+ return self.insert_bb_items(lines, starts, ends)
133
+
134
+ def insert_lines(self, lines, starts, ends):
135
+ """Insert a list of lines.
136
+
137
+ Args:
138
+
139
+ lines (`ndarray`, shape `(n, [m])`):
140
+
141
+ Array containing the line identifiers (as passed as the
142
+ `item_dtype` to the constructor). If the identifiers are an
143
+ array of size `m`, the expected shape is `(n, m)` where `n` is
144
+ the number of lines, otherwise the shape is just `(n,)`.
145
+
146
+ starts (`ndarray`, shape `(n, d)`):
147
+
148
+ The coordinates of the start of each line.
149
+
150
+ ends (`ndarray`, shape `(n, d)`):
151
+
152
+ The coordinates of the end of each line.
153
+ """
154
+ # we just forward to bb insert, "start" and "end" will be used to compute
155
+ # the bounding box in our custom converter above
156
+ return self.insert_bb_items(lines, starts, ends)
@@ -0,0 +1,6 @@
1
+ from .rtree import RTree
2
+
3
+
4
+ # no modifications to plain RTree needed
5
+ class PointRTree(RTree):
6
+ pass
@@ -0,0 +1,125 @@
1
+ from typing import ClassVar
2
+ import sys
3
+ import witty
4
+ import numpy as np
5
+ from Cheetah.Template import Template
6
+ from pathlib import Path
7
+ from ..dtypes import DType
8
+
9
+ DEFINE_MACROS = [("RTREE_NOATOMICS", "1")] if sys.platform == "win32" else []
10
+
11
+
12
+ class RTree:
13
+ """A generic RTree implementation, compiled on-the-fly during
14
+ instantiation.
15
+
16
+ Args:
17
+
18
+ item_dtype (``string``):
19
+
20
+ The C type of the items to hold. Can be a scalar (e.g. ``uint64``)
21
+ or an array of scalars (e.g., "uint64[3]").
22
+
23
+ coord_dtype (``string``):
24
+
25
+ The scalar C type to use for coordinates (e.g., ``float``).
26
+
27
+ dims (``int``):
28
+
29
+ The dimension of the r-tree.
30
+
31
+ Subclassing:
32
+
33
+ This generic implementation can be subclassed and modified in the
34
+ following ways:
35
+
36
+ The class members ``pyx_item_t_declaration`` and
37
+ ``c_item_t_declaration`` can be overwritten to use custom ``item_t``
38
+ structures. This will also require overwriting the
39
+ ``c_converter_functions`` to translate between the PYX interface (where
40
+ items are scalars or C arrays of scalars) and the C interface (the
41
+ custom ``item_t`` type.
42
+
43
+ The following constants and typedefs are available to use in the
44
+ provided code:
45
+
46
+ DIMS:
47
+
48
+ A constant set to the value of ``dims``.
49
+
50
+ item_base_t:
51
+
52
+ The scalar type of the item (e.g., ``uint64``), regardless of
53
+ whether this is a scalar or array item.
54
+ """
55
+
56
+ # overwrite in subclasses for custom item_t structures
57
+ pyx_item_t_declaration: ClassVar[str] = ""
58
+ c_item_t_declaration: ClassVar[str] = ""
59
+
60
+ # overwrite in subclasses for custom converters
61
+ c_converter_functions: ClassVar[str] = ""
62
+
63
+ # overwrite in subclasses for custom item comparison code
64
+ c_equal_function: ClassVar[str] = ""
65
+
66
+ # overwrite in subclasses for custom distance computation
67
+ c_distance_function: ClassVar[str] = ""
68
+
69
+ def __new__(
70
+ cls,
71
+ item_dtype,
72
+ coord_dtype,
73
+ dims,
74
+ ):
75
+ item_dtype = DType(item_dtype)
76
+ coord_dtype = DType(coord_dtype)
77
+
78
+ ############################################
79
+ # create wrapper from template and compile #
80
+ ############################################
81
+
82
+ src_dir = Path(__file__).parent
83
+ wrapper_template = Template(
84
+ file=str(src_dir / "wrapper_template.pyx"),
85
+ compilerSettings={"directiveStartToken": "%"},
86
+ )
87
+ wrapper_template.item_dtype = item_dtype
88
+ wrapper_template.coord_dtype = coord_dtype
89
+ wrapper_template.dims = dims
90
+ wrapper_template.c_distance_function = cls.c_distance_function
91
+ wrapper_template.pyx_item_t_declaration = cls.pyx_item_t_declaration
92
+ wrapper_template.c_item_t_declaration = cls.c_item_t_declaration
93
+ wrapper_template.c_converter_functions = cls.c_converter_functions
94
+ wrapper_template.c_equal_function = cls.c_equal_function
95
+
96
+ wrapper = witty.compile_module(
97
+ str(wrapper_template),
98
+ source_files=[
99
+ src_dir / "src" / "rtree.h",
100
+ src_dir / "src" / "rtree.c",
101
+ src_dir / "src" / "config.h",
102
+ ],
103
+ extra_compile_args=["/O2" if sys.platform == "win32" else "-O3"],
104
+ include_dirs=[str(src_dir)],
105
+ language="c",
106
+ quiet=True,
107
+ define_macros=DEFINE_MACROS,
108
+ )
109
+ RTreeType = type(cls.__name__, (cls, wrapper.RTree), {})
110
+ return wrapper.RTree.__new__(RTreeType)
111
+
112
+ def __init__(self, item_dtype, coord_dtype, dims):
113
+ super().__init__()
114
+ self.item_dtype = DType(item_dtype)
115
+
116
+ def insert_point_item(self, item, position):
117
+ items = np.array([item], dtype=self.item_dtype.base)
118
+ positions = position[np.newaxis]
119
+ return self.insert_point_items(items, positions)
120
+
121
+ def delete_item(self, item, bb_min, bb_max=None):
122
+ items = np.array([item], dtype=self.item_dtype.base)
123
+ bb_mins = bb_min[np.newaxis, :]
124
+ bb_maxs = None if bb_max is None else bb_max[np.newaxis, :]
125
+ return self.delete_items(items, bb_mins, bb_maxs)
@@ -0,0 +1,162 @@
1
+ History
2
+ =======
3
+
4
+ [Implementation from Joshua J Baker](https://github.com/tidwall/rtree.c), under
5
+ an MIT-style license (see LICENSE).
6
+
7
+ * modified to work with [witty](https://github.com/funkelab/witty)
8
+ * added kNN search
9
+
10
+ Data Structures
11
+ ===============
12
+
13
+ R-Tree Itself
14
+ -------------
15
+
16
+ `rect`
17
+ `item`
18
+ * `data` = node ID (type `item_data_t` defined in wrapper)
19
+ `node`
20
+ * `rects`: list of `rect`s
21
+ * `kind`:
22
+ * `LEAF`: contains a list of `item`s, one for each `rect`
23
+ * `BRANCH`: contains a list of `node`s, one for each `rect`
24
+
25
+ Priority Queue for kNN
26
+ ----------------------
27
+
28
+ `element`
29
+ * `distance`
30
+ * `kind` (same enum as rtree kind):
31
+ * `LEAF`: contains a `node*`
32
+ * `BRANCH`: contains a `node*`
33
+ * `ITEM`: contains an `item`
34
+ `priority_queue`
35
+ * dynamically allocated array of `element`s
36
+
37
+ Implementation for Line Segments
38
+ ================================
39
+
40
+ `item`:
41
+ * `u`, `v`: IDs of endpoint nodes
42
+ * `corner_mask`: (n-1)D bitmask for line segment corner in bounding box
43
+
44
+ `distance(coord_t point[], rect *bb, item *segment)`
45
+ * compute distance of `point` to line `segment` bound by `bb`
46
+ `distance_bb(coord_t point[], rect *bb)`
47
+ * previous implementation, computes distance to bounding box
48
+
49
+ `element`
50
+ * `kind`
51
+ * `LEAF`, `BRANCH` as before
52
+ * `ITEM_BY_BB`: item, distance computed using bounding box
53
+ * `ITEM`: item, distance computed exactly
54
+
55
+
56
+ Generalize for Arbitrary Items
57
+ ==============================
58
+
59
+ User-provided "templates":
60
+
61
+ Level 0 (C)
62
+ -----------
63
+
64
+ `DIMS`
65
+ * a `#define`, the number of dimensions
66
+
67
+ `KNN_USE_EXACT_DISTANCE`
68
+ * an optional `#define`
69
+ * if defined, the distance function below needs to be provided
70
+ * if not defined, only distances to bounding boxes are used for the kNN search
71
+
72
+ `coord_t`
73
+ * the scalar type of coordinates
74
+
75
+ `item_t`
76
+ * a type (can be a `struct`) representing an item
77
+ * keep small (e.g., only the ID of a node)
78
+
79
+ `distance(coord_t point[], rect *bb, item_t *item)`
80
+ * a function that computes the exact distance of a point to the item
81
+ * `point`: the query point
82
+ * `bb`: the bounding box of the item (as given during insert)
83
+ * `item`: the item to compute the distance for
84
+ * if not given, should not attempt to compute exact distance and return
85
+ item based on `distance_bb` right away
86
+
87
+ Level 1 (PYX)
88
+ -------------
89
+
90
+ This level holds glue code to bridge between C and Python.
91
+
92
+
93
+ ### Definitions
94
+
95
+ `NUM_DIMS`
96
+ * the number of dimensions
97
+ * sets definition of `DIMS` used in Level 0
98
+
99
+ `KNN_USE_EXACT_DISTANCE`
100
+ * as described above
101
+
102
+ ### Data Types
103
+
104
+ `coord_t`
105
+ * needs to be provided as:
106
+ * C code: `typedef`
107
+ * PYX code: `ctypedef`
108
+
109
+ `item_t`
110
+ * needs to be provided as
111
+ * C code: this is the `item_t` used in Level 0 (`typedef` or `struct`)
112
+ * PYX code: this is just to wrap `item_t` to be used in PYX code (not user facing)
113
+ ⇒ TODO: is that really needed?
114
+
115
+ `pyx_item_t`
116
+ * semantically, the Python-wrapped version of `item_t` (user facing for
117
+ insert and query)
118
+ * has to be something that can be placed in a numpy array / memory view
119
+ * this is part of the interface for efficient insert and query
120
+ * similar to attributes, nodes, and edge lists in the graph, the
121
+ interface uses numpy arrays
122
+ * this means it is either a numpy type or an array of that, i.e.:
123
+ * dtype -> dtype[::1] for efficient insert/query
124
+ * dtype[n] -> dtype[:, ::1] for efficient insert/query (where n is the size of the item)
125
+ * needs to be provided as:
126
+ * C code: `typedef`
127
+ * PYX code: `ctypedef`
128
+
129
+ `NP_ITEM_DTYPE`
130
+ * the `dtype` of `pyx_item_t` (e.g., `uint64`), used to create numpy arrays
131
+
132
+ `API_ITEMS_MEMVIEW_TYPE`
133
+ * the type of a list of `pyx_item_t`s, either
134
+ * dtype[::1] or
135
+ * dtype[:, ::1]
136
+
137
+ ### Strings
138
+
139
+ `ITEM_LENGTH`
140
+ * `d` if an array type, empty string otherwise
141
+
142
+ `ITEMS_EXTRA_DIMS_0`
143
+ * extra indices to index an element: `item[i, 0]` would be ", 0"
144
+
145
+ ### Functions
146
+
147
+ `item_t pyx_to_c_item(pyx_item_t pyx_item, *)`
148
+ * needs to be provided as:
149
+ * C code: function implementation
150
+ * PYX code: `cdef` function declaration
151
+ ⇒ TODO: can be PYX function only?
152
+
153
+ `void copy_c_to_pyx_item(const item_t item, pyx_item_t *pyx_item)`
154
+ * copy and convert C-item `item` to address of `pyx_item`
155
+ * needs to be provided as:
156
+ * C code: function implementation
157
+ * PYX code: `cdef` function declaration
158
+ ⇒ TODO: can be PYX function only?
159
+
160
+ `distance(coord_t point[], rect *bb, item_t *item)`
161
+ * only C implementation needed
162
+ * skip if `KNN_USE_EXACT_DISTANCE` is not set
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Joshua J Baker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,13 @@
1
+ // rtree configuration header
2
+
3
+ #ifndef __RTREE_CONFIG_H
4
+ #define __RTREE_CONFIG_H
5
+
6
+ // the maximal number of items per node in the rtree
7
+ #define MAXITEMS 64
8
+
9
+ // the initial number of items in the priority queue to find nearest neighbors
10
+ // (will keep doubling as needed to accommodate more)
11
+ #define INITIAL_QUEUE_SIZE 256
12
+
13
+ #endif // __RTREE_CONFIG_H