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,15 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+
3
+ try:
4
+ __version__ = version("spatial_graph")
5
+ except PackageNotFoundError:
6
+ __version__ = "unknown"
7
+
8
+
9
+ from .rtree import PointRTree
10
+ from .rtree import LineRTree
11
+ from .graph import Graph
12
+ from .spatial_graph import SpatialGraph
13
+
14
+
15
+ __all__ = ["PointRTree", "LineRTree", "Graph", "SpatialGraph"]
@@ -0,0 +1,130 @@
1
+ import numpy as np
2
+
3
+
4
+ class DType:
5
+ def __init__(self, dtype_str):
6
+ self.as_string = dtype_str
7
+ self.is_array = self.__is_array(dtype_str)
8
+
9
+ if self.is_array:
10
+ self.base, self.size = self.__parse_array_dtype(dtype_str)
11
+ self.shape = (self.size,)
12
+ else:
13
+ self.base = dtype_str
14
+ self.size = None
15
+ self.shape = ()
16
+
17
+ def __is_array(self, dtype):
18
+ if "[" in dtype:
19
+ if "]" not in dtype:
20
+ raise RuntimeError(f"invalid array(?) dtype {dtype}")
21
+ return True
22
+ return False
23
+
24
+ def __parse_array_dtype(self, dtype):
25
+ dtype, size = dtype.split("[")
26
+ size = int(size.split("]")[0])
27
+
28
+ return dtype, size
29
+
30
+ @property
31
+ def base_c_type(self):
32
+ """Convert the base of this DType into the equivalent C/C++ type."""
33
+
34
+ if self.base == "float32" or self.base == "float":
35
+ return "float"
36
+ elif self.base == "float64" or self.base == "double":
37
+ return "double"
38
+ else:
39
+ # this might not work for all of them, this is just a fallback
40
+ return np.dtype(self.base).name + "_t"
41
+
42
+ def to_c_decl(self, name):
43
+ """Convert this dtype to the equivalent C/C++ declaration with the
44
+ given name:
45
+
46
+ "base_c_type name" if not an array
47
+ "base_c_type name[size]" if an array type
48
+ """
49
+ # is this an array type?
50
+ if self.is_array:
51
+ suffix = f"[{self.size}]"
52
+ else:
53
+ suffix = ""
54
+
55
+ return self.base_c_type + " " + name + suffix
56
+
57
+ def to_pyxtype(self, use_memory_view=False, add_dim=False):
58
+ """Convert this dtype to the equivalent PYX type.
59
+
60
+ "base_c_type"
61
+ "base_c_type[size]" if an array type
62
+ "base_c_type[::1]" if an array type and use_memory_view
63
+ "base_c_type[::1]" if not an array type and add_dim
64
+ "base_c_type[:, ::1]" if an array type and add_dim
65
+
66
+ Args:
67
+
68
+ use_memory_view:
69
+
70
+ If set, will produce "dtype[::1]" instead of "dtype[dim]" for
71
+ array types.
72
+
73
+ add_dim:
74
+
75
+ Append a dim to the type, e.g., "int32_t[::1]" instead of
76
+ "int32_t" for dtype "int32". If this DType is already an array,
77
+ will create a 2D array, e.g., "int32_t[:, ::1]".
78
+ """
79
+
80
+ # is this an array type?
81
+ if self.is_array:
82
+ if add_dim:
83
+ suffix = "[:, ::1]"
84
+ else:
85
+ if use_memory_view:
86
+ suffix = "[::1]"
87
+ else:
88
+ suffix = f"[{self.size}]"
89
+ else:
90
+ suffix = "[::1]" if add_dim else ""
91
+
92
+ return self.base_c_type + suffix
93
+
94
+ def to_rvalue(self, name, array_index=None):
95
+ """Convert this dtype into an r-value to be used in PYX files for
96
+ assignments.
97
+
98
+ "name" default
99
+ "name[array_index]" if array_index is given
100
+ "{name[0], ..., name[size-1]}"
101
+ if an array type
102
+ "{name[array_index, 0], ..., name[array_index, size-1]}"
103
+ if an array type and array_index is given
104
+ """
105
+
106
+ if self.is_array:
107
+ if array_index:
108
+ return (
109
+ "{"
110
+ + ", ".join(
111
+ [name + f"[{array_index}, {i}]" for i in range(self.size)]
112
+ )
113
+ + "}"
114
+ )
115
+ else:
116
+ return (
117
+ "{" + ", ".join([name + f"[{i}]" for i in range(self.size)]) + "}"
118
+ )
119
+ else:
120
+ if array_index:
121
+ return f"{name}[{array_index}]"
122
+ else:
123
+ return name
124
+
125
+
126
+ def dtypes_to_struct(struct_name, dtypes):
127
+ pyx_code = f"cdef struct {struct_name}:\n"
128
+ for name, dtype in dtypes.items():
129
+ pyx_code += f" {dtype.to_pyxtype()} {name}\n"
130
+ return pyx_code
@@ -0,0 +1,3 @@
1
+ from .graph import Graph
2
+
3
+ __all__ = ["Graph"]
@@ -0,0 +1,226 @@
1
+ import sys
2
+ import witty
3
+ from pathlib import Path
4
+ import numpy as np
5
+ from Cheetah.Template import Template
6
+ from ..dtypes import DType
7
+
8
+ # Set platform-specific compile arguments
9
+ if sys.platform == "win32":
10
+ # Use /O2 for optimization and /std:c++20 for C++20
11
+ EXTRA_COMPILE_ARGS = ["/O2", "/std:c++20"]
12
+ else:
13
+ # -O3 for optimization and -std=c++20 for C++20
14
+ EXTRA_COMPILE_ARGS = ["-O3", "-std=c++20"]
15
+
16
+
17
+ class Graph:
18
+ def __new__(
19
+ cls,
20
+ node_dtype,
21
+ node_attr_dtypes=None,
22
+ edge_attr_dtypes=None,
23
+ directed=False,
24
+ *args,
25
+ **kwargs,
26
+ ):
27
+ if node_attr_dtypes is None:
28
+ node_attr_dtypes = {}
29
+ if edge_attr_dtypes is None:
30
+ edge_attr_dtypes = {}
31
+
32
+ node_dtype = DType(node_dtype)
33
+ node_attr_dtypes = {
34
+ name: DType(dtype) for name, dtype in node_attr_dtypes.items()
35
+ }
36
+ edge_attr_dtypes = {
37
+ name: DType(dtype) for name, dtype in edge_attr_dtypes.items()
38
+ }
39
+
40
+ src_dir = Path(__file__).parent
41
+ wrapper_template = Template(
42
+ file=str(src_dir / "wrapper_template.pyx"),
43
+ compilerSettings={"directiveStartToken": "%"},
44
+ )
45
+ wrapper_template.node_dtype = node_dtype
46
+ wrapper_template.node_attr_dtypes = node_attr_dtypes
47
+ wrapper_template.edge_attr_dtypes = edge_attr_dtypes
48
+ wrapper_template.directed = directed
49
+
50
+ wrapper = witty.compile_module(
51
+ str(wrapper_template),
52
+ source_files=[str(src_dir / "src" / "graph_lite.h")],
53
+ extra_compile_args=EXTRA_COMPILE_ARGS,
54
+ include_dirs=[str(src_dir)],
55
+ language="c++",
56
+ quiet=True,
57
+ )
58
+ GraphType = type(cls.__name__, (cls, wrapper.Graph), {})
59
+ return wrapper.Graph.__new__(GraphType)
60
+
61
+ def __init__(
62
+ self, node_dtype, node_attr_dtypes=None, edge_attr_dtypes=None, directed=False
63
+ ):
64
+ if node_attr_dtypes is None:
65
+ node_attr_dtypes = {}
66
+ if edge_attr_dtypes is None:
67
+ edge_attr_dtypes = {}
68
+ super().__init__()
69
+ self.node_dtype = node_dtype
70
+ self.node_attr_dtypes = node_attr_dtypes
71
+ self.edge_attr_dtypes = edge_attr_dtypes
72
+ self.directed = directed
73
+
74
+ self.node_attrs = NodeAttrs(self)
75
+ self.edge_attrs = EdgeAttrs(self)
76
+
77
+
78
+ class NodeAttrsView:
79
+ def __init__(self, graph, nodes):
80
+ super().__setattr__("graph", graph)
81
+ for name in graph.node_attr_dtypes.keys():
82
+ super().__setattr__(
83
+ f"get_attr_{name}", getattr(graph, f"get_nodes_data_{name}")
84
+ )
85
+ super().__setattr__(
86
+ f"set_attr_{name}", getattr(graph, f"set_nodes_data_{name}")
87
+ )
88
+
89
+ if nodes is not None and not isinstance(nodes, np.ndarray):
90
+ # nodes is not an ndarray, can it be converted into one?
91
+ try:
92
+ # does it have a length?
93
+ _ = len(nodes)
94
+ # if so, convert to ndarray
95
+ nodes = np.array(nodes, dtype=graph.node_dtype)
96
+ except Exception:
97
+ # must be a single node
98
+ for name in graph.node_attr_dtypes.keys():
99
+ super().__setattr__(
100
+ f"set_attr_{name}", getattr(graph, f"set_node_data_{name}")
101
+ )
102
+ super().__setattr__(
103
+ f"get_attr_{name}", getattr(graph, f"get_node_data_{name}")
104
+ )
105
+
106
+ # at this point, nodes is either
107
+ # 1. a numpy array
108
+ # 2. a scalar (python or numpy)
109
+ # 3. None
110
+ super().__setattr__("nodes", nodes)
111
+
112
+ def __getattr__(self, name):
113
+ if name in self.graph.node_attr_dtypes:
114
+ return getattr(self, f"get_attr_{name}")(self.nodes)
115
+ else:
116
+ raise AttributeError(name)
117
+
118
+ def __setattr__(self, name, values):
119
+ if name in self.graph.node_attr_dtypes:
120
+ return getattr(self, f"set_attr_{name}")(self.nodes, values)
121
+ else:
122
+ return super().__setattr__(name, values)
123
+
124
+ def __iter__(self):
125
+ # TODO: shouldn't be possible if nodes is a single node
126
+ yield from self.graph.nodes_data(self.nodes)
127
+
128
+
129
+ class EdgeAttrsView:
130
+ def __init__(self, graph, edges):
131
+ super().__setattr__("graph", graph)
132
+ for name in graph.edge_attr_dtypes.keys():
133
+ super().__setattr__(
134
+ f"get_attr_{name}", getattr(graph, f"get_edges_data_{name}")
135
+ )
136
+ super().__setattr__(
137
+ f"set_attr_{name}", getattr(graph, f"set_edges_data_{name}")
138
+ )
139
+
140
+ # edges types we support:
141
+ #
142
+ # 1. edges = None all edges leave as is
143
+ # 2. edges = iteratible of 2-tuples selected edges to (n,2) ndarray
144
+ # 3. edges = iteratible of 2-lists selected edges to (n,2) ndarray
145
+ # 4. edges = (n,2) ndarray selected edges leave as is
146
+ # 5. edges = 2-tuple a single edge leave as is
147
+ # 6. edges = (2,) ndarray a single edge to 2-tuple
148
+
149
+ if edges is not None:
150
+ if isinstance(edges, np.ndarray):
151
+ if len(edges) == 2 and len(edges.shape) == 1:
152
+ # case 6
153
+ edges = tuple(edges)
154
+ else:
155
+ # case 4 with multiple edges
156
+ edges = edges.astype(graph.node_dtype)
157
+ elif isinstance(edges, tuple):
158
+ # case 5
159
+ assert len(edges) == 2, "Single edges should be given as a 2-tuple"
160
+ else:
161
+ # edges should be an iteratable
162
+ try:
163
+ # does it have a length?
164
+ len(edges)
165
+ # case 2 and 3
166
+ edges = np.array(edges, dtype=graph.node_dtype)
167
+ except Exception:
168
+ raise RuntimeError(f"Can not handle edges type {type(edges)}")
169
+
170
+ if isinstance(edges, np.ndarray):
171
+ if len(edges) == 0:
172
+ edges = edges.reshape((0, 2))
173
+ assert edges.shape[1] == 2, "Edge arrays should have shape (n, 2)"
174
+ edges = np.ascontiguousarray(edges.T)
175
+ elif isinstance(edges, tuple):
176
+ # a single edge
177
+ for name in graph.edge_attr_dtypes.keys():
178
+ super().__setattr__(
179
+ f"get_attr_{name}", getattr(graph, f"get_edge_data_{name}")
180
+ )
181
+ super().__setattr__(
182
+ f"set_attr_{name}", getattr(graph, f"set_edge_data_{name}")
183
+ )
184
+
185
+ # at this point, edges is either
186
+ # 1. a nx2 numpy array
187
+ # 2. a 2-tuple of scalars (python or numpy)
188
+ # 3. None
189
+ super().__setattr__("edges", edges)
190
+
191
+ def __getattr__(self, name):
192
+ if name in self.graph.edge_attr_dtypes:
193
+ if self.edges is not None:
194
+ return getattr(self, f"get_attr_{name}")(self.edges[0], self.edges[1])
195
+ else:
196
+ return getattr(self, f"get_attr_{name}")(None, None)
197
+ else:
198
+ raise AttributeError(name)
199
+
200
+ def __setattr__(self, name, values):
201
+ if name in self.graph.edge_attr_dtypes:
202
+ return getattr(self, f"set_attr_{name}")(
203
+ self.edges[0], self.edges[1], values
204
+ )
205
+ else:
206
+ return super().__setattr__(name, values)
207
+
208
+ def __iter__(self):
209
+ # TODO: shouldn't be possible if edges is a single edge
210
+ yield from self.graph.edges_data(self.edges)
211
+
212
+
213
+ class NodeAttrs(NodeAttrsView):
214
+ def __init__(self, graph):
215
+ super().__init__(graph, nodes=None)
216
+
217
+ def __getitem__(self, nodes):
218
+ return NodeAttrsView(self.graph, nodes)
219
+
220
+
221
+ class EdgeAttrs(EdgeAttrsView):
222
+ def __init__(self, graph):
223
+ super().__init__(graph, edges=None)
224
+
225
+ def __getitem__(self, edges):
226
+ return EdgeAttrsView(self.graph, edges)
@@ -0,0 +1,7 @@
1
+ Copyright 2021 Guohao Dou
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.